Passed
Push — extents ( 6d8774...01b6e3 )
by Doug
61:12
created

GeographicPoint::madridToED50Polynomial()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 16
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 1
nop 10
dl 0
loc 16
ccs 2
cts 2
cp 1
crap 3
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use function abs;
12
use function asinh;
13
use function atan;
14
use function atan2;
15
use function atanh;
16
use function cos;
17
use function cosh;
18
use function count;
19
use DateTime;
20
use DateTimeImmutable;
21
use DateTimeInterface;
22
use function hypot;
23
use function implode;
24
use function is_nan;
25
use function log;
26
use const M_E;
27
use const M_PI;
28
use function max;
29
use PHPCoord\CoordinateOperation\AutoConversion;
30
use PHPCoord\CoordinateOperation\ComplexNumber;
31
use PHPCoord\CoordinateOperation\ConvertiblePoint;
32
use PHPCoord\CoordinateOperation\GeocentricValue;
33
use PHPCoord\CoordinateOperation\GeographicGeoidHeightGrid;
34
use PHPCoord\CoordinateOperation\GeographicGrid;
35
use PHPCoord\CoordinateOperation\GeographicValue;
36
use PHPCoord\CoordinateOperation\NADCON5Grid;
37
use PHPCoord\CoordinateOperation\NADCON5Grids;
38
use PHPCoord\CoordinateOperation\OSTNOSGM15Grid;
39
use PHPCoord\CoordinateReferenceSystem\Compound;
40
use PHPCoord\CoordinateReferenceSystem\Geocentric;
41
use PHPCoord\CoordinateReferenceSystem\Geographic;
42
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
43
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
44
use PHPCoord\CoordinateReferenceSystem\Projected;
0 ignored issues
show
Bug introduced by
The type PHPCoord\CoordinateReferenceSystem\Projected was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
45
use PHPCoord\CoordinateReferenceSystem\Vertical;
46
use PHPCoord\CoordinateSystem\Axis;
47
use PHPCoord\CoordinateSystem\Cartesian;
48
use PHPCoord\Datum\Datum;
49
use PHPCoord\Datum\Ellipsoid;
50
use PHPCoord\Exception\InvalidCoordinateReferenceSystemException;
51
use PHPCoord\Exception\UnknownAxisException;
52
use PHPCoord\Geometry\BoundingArea;
53
use PHPCoord\UnitOfMeasure\Angle\Angle;
54
use PHPCoord\UnitOfMeasure\Angle\ArcSecond;
55
use PHPCoord\UnitOfMeasure\Angle\Degree;
56
use PHPCoord\UnitOfMeasure\Angle\Radian;
57
use PHPCoord\UnitOfMeasure\Length\Length;
58
use PHPCoord\UnitOfMeasure\Length\Metre;
59
use PHPCoord\UnitOfMeasure\Scale\Coefficient;
60
use PHPCoord\UnitOfMeasure\Scale\Scale;
61
use PHPCoord\UnitOfMeasure\Scale\Unity;
62
use function sin;
63
use function sinh;
64
use function sqrt;
65
use function str_replace;
66
use function tan;
67
68
/**
69
 * Coordinate representing a point on an ellipsoid.
70
 */
71
class GeographicPoint extends Point implements ConvertiblePoint
72
{
73
    use AutoConversion;
74
75
    /**
76
     * Latitude.
77
     */
78
    protected Angle $latitude;
79
80
    /**
81
     * Longitude.
82
     */
83
    protected Angle $longitude;
84
85
    /**
86
     * Height above ellipsoid (N.B. *not* height above ground, sea-level or anything else tangible).
87
     */
88
    protected ?Length $height;
89
90
    /**
91
     * Coordinate reference system.
92
     */
93
    protected Geographic2D|Geographic3D $crs;
94
95
    /**
96
     * Coordinate epoch (date for which the specified coordinates represented this point).
97
     */
98
    protected ?DateTimeImmutable $epoch;
99
100
    protected function __construct(Geographic2D|Geographic3D $crs, Angle $latitude, Angle $longitude, ?Length $height, ?DateTimeInterface $epoch)
101 62151
    {
102
        if ($crs instanceof Geographic2D && $height !== null) {
103 62151
            throw new InvalidCoordinateReferenceSystemException('A 2D geographic point must not include a height');
104
        }
105
106
        if ($crs instanceof Geographic3D && $height === null) {
107 62151
            throw new InvalidCoordinateReferenceSystemException('A 3D geographic point must include a height, none given');
108 9
        }
109
110
        $this->crs = $crs;
111 62142
112 9
        $latitude = $this->normaliseLatitude($latitude);
113
        $longitude = $this->normaliseLongitude($longitude);
114
115 62133
        $this->latitude = $latitude::convert($latitude, $this->crs->getCoordinateSystem()->getAxisByName(Axis::GEODETIC_LATITUDE)->getUnitOfMeasureId());
116
        $this->longitude = $longitude::convert($longitude, $this->crs->getCoordinateSystem()->getAxisByName(Axis::GEODETIC_LONGITUDE)->getUnitOfMeasureId());
117 62133
118 62133
        if ($height) {
119
            $this->height = $height::convert($height, $this->crs->getCoordinateSystem()->getAxisByName(Axis::ELLIPSOIDAL_HEIGHT)->getUnitOfMeasureId());
120 62133
        } else {
121 62133
            $this->height = null;
122
        }
123 62133
124 4582
        if ($epoch instanceof DateTime) {
125
            $epoch = DateTimeImmutable::createFromMutable($epoch);
126 60241
        }
127
        $this->epoch = $epoch;
128
    }
129 62133
130 57056
    /**
131
     * @param ?Length $height    refer to CRS for preferred unit of measure, but any length unit accepted
132 62133
     * @param Angle   $latitude  refer to CRS for preferred unit of measure, but any angle unit accepted
133 62133
     * @param Angle   $longitude refer to CRS for preferred unit of measure, but any angle unit accepted
134
     */
135
    public static function create(Geographic2D|Geographic3D $crs, Angle $latitude, Angle $longitude, ?Length $height = null, ?DateTimeInterface $epoch = null): self
136
    {
137
        return new static($crs, $latitude, $longitude, $height, $epoch);
138
    }
139
140 62151
    public function getLatitude(): Angle
141
    {
142 62151
        return $this->latitude;
143
    }
144
145 59109
    public function getLongitude(): Angle
146
    {
147 59109
        return $this->longitude;
148
    }
149
150 59064
    public function getHeight(): ?Length
151
    {
152 59064
        return $this->height;
153
    }
154
155 57328
    public function getCRS(): Geographic
156
    {
157 57328
        return $this->crs;
158
    }
159
160 60489
    public function getCoordinateEpoch(): ?DateTimeImmutable
161
    {
162 60489
        return $this->epoch;
163
    }
164
165 190
    protected function normaliseLatitude(Angle $latitude): Angle
166
    {
167 190
        if ($latitude->asDegrees()->getValue() > 90) {
168
            return new Degree(90);
169
        }
170 62133
        if ($latitude->asDegrees()->getValue() < -90) {
171
            return new Degree(-90);
172 62133
        }
173
174
        return $latitude;
175 62133
    }
176
177
    protected function normaliseLongitude(Angle $longitude): Angle
178
    {
179 62133
        while ($longitude->asDegrees()->getValue() > 180) {
180
            $longitude = $longitude->subtract(new Degree(360));
181
        }
182 62133
        while ($longitude->asDegrees()->getValue() <= -180) {
183
            $longitude = $longitude->add(new Degree(360));
184 62133
        }
185 99
186
        return $longitude;
187 62133
    }
188 111
189
    /**
190
     * Calculate surface distance between two points.
191 62133
     */
192
    public function calculateDistance(Point $to): Length
193
    {
194
        try {
195
            if ($to instanceof ConvertiblePoint) {
196
                $to = $to->convert($this->crs);
197 153
            }
198
        } finally {
199
            if ($to->getCRS()->getSRID() !== $this->crs->getSRID()) {
200 153
                throw new InvalidCoordinateReferenceSystemException('Can only calculate distances between two points in the same CRS');
201 144
            }
202
203
            /* @var GeographicPoint $to */
204 153
            return static::vincenty($this->asGeographicValue(), $to->asGeographicValue(), $this->getCRS()->getDatum()->getEllipsoid());
205 9
        }
206
    }
207
208
    public function __toString(): string
209 144
    {
210
        $values = [];
211
        foreach ($this->getCRS()->getCoordinateSystem()->getAxes() as $axis) {
212
            if ($axis->getName() === Axis::GEODETIC_LATITUDE) {
213 36
                $values[] = $this->latitude;
214
            } elseif ($axis->getName() === Axis::GEODETIC_LONGITUDE) {
215 36
                $values[] = $this->longitude;
216 36
            } elseif ($axis->getName() === Axis::ELLIPSOIDAL_HEIGHT) {
217 36
                $values[] = $this->height;
218 36
            } else {
219 36
                throw new UnknownAxisException(); // @codeCoverageIgnore
220 36
            }
221 9
        }
222 9
223
        return '(' . implode(', ', $values) . ')';
224
    }
225
226
    /**
227
     * Geographic/geocentric conversions
228 36
     * In applications it is often concatenated with the 3- 7- or 10-parameter transformations 9603, 9606, 9607 or
229
     * 9636 to form a geographic to geographic transformation.
230
     */
231
    public function geographicGeocentric(
232
        Geocentric $to
233
    ): GeocentricPoint {
234
        $geographicValue = new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
235
        $asGeocentric = $geographicValue->asGeocentricValue();
236 1705
237
        return GeocentricPoint::create($to, $asGeocentric->getX(), $asGeocentric->getY(), $asGeocentric->getZ(), $this->epoch);
238
    }
239 1705
240 1705
    /**
241
     * Coordinate Frame rotation (geog2D/geog3D domain)
242 1705
     * Note the analogy with the Position Vector tfm (codes 9606/1037) but beware of the differences!  The Position Vector
243
     * convention is used by IAG and recommended by ISO 19111. See methods 1032/1038/9607 for similar tfms operating
244
     * between other CRS types.
245
     */
246
    public function coordinateFrameRotation(
247
        Geographic2D|Geographic3D $to,
248
        Length $xAxisTranslation,
249
        Length $yAxisTranslation,
250
        Length $zAxisTranslation,
251 1884
        Angle $xAxisRotation,
252
        Angle $yAxisRotation,
253
        Angle $zAxisRotation,
254
        Scale $scaleDifference
255
    ): self {
256
        return $this->coordinateFrameMolodenskyBadekas(
257
            $to,
258
            $xAxisTranslation,
259
            $yAxisTranslation,
260
            $zAxisTranslation,
261 1884
            $xAxisRotation,
262 1884
            $yAxisRotation,
263
            $zAxisRotation,
264
            $scaleDifference,
265
            new Metre(0),
266
            new Metre(0),
267
            new Metre(0)
268
        );
269
    }
270 1884
271 1884
    /**
272 1884
     * Molodensky-Badekas (CF geog2D/geog3D domain)
273
     * See method codes 1034 and 1039/9636 for this operation in other coordinate domains and method code 1062/1063 for the
274
     * opposite rotation convention in geographic 2D domain.
275
     */
276
    public function coordinateFrameMolodenskyBadekas(
277
        Geographic2D|Geographic3D $to,
278
        Length $xAxisTranslation,
279
        Length $yAxisTranslation,
280
        Length $zAxisTranslation,
281 2163
        Angle $xAxisRotation,
282
        Angle $yAxisRotation,
283
        Angle $zAxisRotation,
284
        Scale $scaleDifference,
285
        Length $ordinate1OfEvaluationPoint,
286
        Length $ordinate2OfEvaluationPoint,
287
        Length $ordinate3OfEvaluationPoint
288
    ): self {
289
        $geographicValue = new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
290
        $asGeocentric = $geographicValue->asGeocentricValue();
291
292
        $xs = $asGeocentric->getX()->asMetres()->getValue();
293
        $ys = $asGeocentric->getY()->asMetres()->getValue();
294 2163
        $zs = $asGeocentric->getZ()->asMetres()->getValue();
295 2163
        $tx = $xAxisTranslation->asMetres()->getValue();
296
        $ty = $yAxisTranslation->asMetres()->getValue();
297 2163
        $tz = $zAxisTranslation->asMetres()->getValue();
298 2163
        $rx = $xAxisRotation->asRadians()->getValue();
299 2163
        $ry = $yAxisRotation->asRadians()->getValue();
300 2163
        $rz = $zAxisRotation->asRadians()->getValue();
301 2163
        $M = 1 + $scaleDifference->asUnity()->getValue();
302 2163
        $xp = $ordinate1OfEvaluationPoint->asMetres()->getValue();
303 2163
        $yp = $ordinate2OfEvaluationPoint->asMetres()->getValue();
304 2163
        $zp = $ordinate3OfEvaluationPoint->asMetres()->getValue();
305 2163
306 2163
        $xt = $M * ((($xs - $xp) * 1) + (($ys - $yp) * $rz) + (($zs - $zp) * -$ry)) + $tx + $xp;
307 2163
        $yt = $M * ((($xs - $xp) * -$rz) + (($ys - $yp) * 1) + (($zs - $zp) * $rx)) + $ty + $yp;
308 2163
        $zt = $M * ((($xs - $xp) * $ry) + (($ys - $yp) * -$rx) + (($zs - $zp) * 1)) + $tz + $zp;
309 2163
        $newGeocentric = new GeocentricValue(new Metre($xt), new Metre($yt), new Metre($zt), $to->getDatum());
310
        $newGeographic = $newGeocentric->asGeographicValue();
311 2163
312 2163
        return static::create($to, $newGeographic->getLatitude(), $newGeographic->getLongitude(), $to instanceof Geographic3D ? $newGeographic->getHeight() : null, $this->epoch);
313 2163
    }
314 2163
315 2163
    /**
316
     * Position Vector transformation (geog2D/geog3D domain)
317 2163
     * Note the analogy with the Coordinate Frame rotation (code 9607/1038) but beware of the differences!  The Position
318
     * Vector convention is used by IAG and recommended by ISO 19111. See methods 1033/1037/9606 for similar tfms
319
     * operating between other CRS types.
320
     */
321
    public function positionVectorTransformation(
322
        Geographic2D|Geographic3D $to,
323
        Length $xAxisTranslation,
324
        Length $yAxisTranslation,
325
        Length $zAxisTranslation,
326 8447
        Angle $xAxisRotation,
327
        Angle $yAxisRotation,
328
        Angle $zAxisRotation,
329
        Scale $scaleDifference
330
    ): self {
331
        return $this->positionVectorMolodenskyBadekas(
332
            $to,
333
            $xAxisTranslation,
334
            $yAxisTranslation,
335
            $zAxisTranslation,
336 8447
            $xAxisRotation,
337 8447
            $yAxisRotation,
338
            $zAxisRotation,
339
            $scaleDifference,
340
            new Metre(0),
341
            new Metre(0),
342
            new Metre(0)
343
        );
344
    }
345 8447
346 8447
    /**
347 8447
     * Molodensky-Badekas (PV geog2D/geog3D domain)
348
     * See method codes 1061 and 1062/1063 for this operation in other coordinate domains and method code 1039/9636 for opposite
349
     * rotation in geographic 2D/3D domain.
350
     */
351
    public function positionVectorMolodenskyBadekas(
352
        Geographic2D|Geographic3D $to,
353
        Length $xAxisTranslation,
354
        Length $yAxisTranslation,
355
        Length $zAxisTranslation,
356 8483
        Angle $xAxisRotation,
357
        Angle $yAxisRotation,
358
        Angle $zAxisRotation,
359
        Scale $scaleDifference,
360
        Length $ordinate1OfEvaluationPoint,
361
        Length $ordinate2OfEvaluationPoint,
362
        Length $ordinate3OfEvaluationPoint
363
    ): self {
364
        $geographicValue = new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
365
        $asGeocentric = $geographicValue->asGeocentricValue();
366
367
        $xs = $asGeocentric->getX()->asMetres()->getValue();
368
        $ys = $asGeocentric->getY()->asMetres()->getValue();
369 8483
        $zs = $asGeocentric->getZ()->asMetres()->getValue();
370 8483
        $tx = $xAxisTranslation->asMetres()->getValue();
371
        $ty = $yAxisTranslation->asMetres()->getValue();
372 8483
        $tz = $zAxisTranslation->asMetres()->getValue();
373 8483
        $rx = $xAxisRotation->asRadians()->getValue();
374 8483
        $ry = $yAxisRotation->asRadians()->getValue();
375 8483
        $rz = $zAxisRotation->asRadians()->getValue();
376 8483
        $M = 1 + $scaleDifference->asUnity()->getValue();
377 8483
        $xp = $ordinate1OfEvaluationPoint->asMetres()->getValue();
378 8483
        $yp = $ordinate2OfEvaluationPoint->asMetres()->getValue();
379 8483
        $zp = $ordinate3OfEvaluationPoint->asMetres()->getValue();
380 8483
381 8483
        $xt = $M * ((($xs - $xp) * 1) + (($ys - $yp) * -$rz) + (($zs - $zp) * $ry)) + $tx + $xp;
382 8483
        $yt = $M * ((($xs - $xp) * $rz) + (($ys - $yp) * 1) + (($zs - $zp) * -$rx)) + $ty + $yp;
383 8483
        $zt = $M * ((($xs - $xp) * -$ry) + (($ys - $yp) * $rx) + (($zs - $zp) * 1)) + $tz + $zp;
384 8483
        $newGeocentric = new GeocentricValue(new Metre($xt), new Metre($yt), new Metre($zt), $to->getDatum());
385
        $newGeographic = $newGeocentric->asGeographicValue();
386 8483
387 8483
        return static::create($to, $newGeographic->getLatitude(), $newGeographic->getLongitude(), $to instanceof Geographic3D ? $newGeographic->getHeight() : null, $this->epoch);
388 8483
    }
389 8483
390 8483
    /**
391
     * Geocentric translations
392 8483
     * This method allows calculation of geocentric coords in the target system by adding the parameter values to the
393
     * corresponding coordinates of the point in the source system. See methods 1031 and 1035 for similar tfms
394
     * operating between other CRSs types.
395
     */
396
    public function geocentricTranslation(
397
        Geographic2D|Geographic3D $to,
398
        Length $xAxisTranslation,
399
        Length $yAxisTranslation,
400
        Length $zAxisTranslation
401 6787
    ): self {
402
        return $this->positionVectorTransformation(
403
            $to,
404
            $xAxisTranslation,
405
            $yAxisTranslation,
406
            $zAxisTranslation,
407 6787
            new Radian(0),
408 6787
            new Radian(0),
409
            new Radian(0),
410
            new Unity(0)
411
        );
412 6787
    }
413 6787
414 6787
    /**
415 6787
     * Abridged Molodensky
416
     * This transformation is a truncated Taylor series expansion of a transformation between two geographic coordinate
417
     * systems, modelled as a set of geocentric translations.
418
     */
419
    public function abridgedMolodensky(
420
        Geographic2D|Geographic3D $to,
421
        Length $xAxisTranslation,
422
        Length $yAxisTranslation,
423
        Length $zAxisTranslation,
424 18
        Length $differenceInSemiMajorAxis,
425
        Scale $differenceInFlattening
426
    ): self {
427
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
428
        $latitude = $this->latitude->asRadians()->getValue();
429
        $longitude = $this->longitude->asRadians()->getValue();
430
        $fromHeight = $this->height ? $this->height->asMetres()->getValue() : 0;
431
        $tx = $xAxisTranslation->asMetres()->getValue();
432 18
        $ty = $yAxisTranslation->asMetres()->getValue();
433 18
        $tz = $zAxisTranslation->asMetres()->getValue();
434 18
        $da = $differenceInSemiMajorAxis->asMetres()->getValue();
435 18
        $df = $differenceInFlattening->asUnity()->getValue();
436 18
437 18
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
438 18
        $e2 = $ellipsoid->getEccentricitySquared();
439 18
440 18
        $rho = $a * (1 - $e2) / (1 - $e2 * sin($latitude) ** 2) ** (3 / 2);
441
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
442 18
443 18
        $f = $ellipsoid->getFlattening();
444
445 18
        $dLatitude = ((-$tx * sin($latitude) * cos($longitude)) - ($ty * sin($latitude) * sin($longitude)) + ($tz * cos($latitude)) + ((($a * $df) + ($ellipsoid->getFlattening() * $da)) * sin(2 * $latitude))) / ($rho * sin((new ArcSecond(1))->asRadians()->getValue()));
446 18
        $dLongitude = (-$tx * sin($longitude) + $ty * cos($longitude)) / (($nu * cos($latitude)) * sin((new ArcSecond(1))->asRadians()->getValue()));
447
        $dHeight = ($tx * cos($latitude) * cos($longitude)) + ($ty * cos($latitude) * sin($longitude)) + ($tz * sin($latitude)) + (($a * $df + $f * $da) * (sin($latitude) ** 2)) - $da;
448 18
449
        $toLatitude = $latitude + (new ArcSecond($dLatitude))->asRadians()->getValue();
450 18
        $toLongitude = $longitude + (new ArcSecond($dLongitude))->asRadians()->getValue();
451 18
        $toHeight = $fromHeight + $dHeight;
452 18
453
        return static::create($to, new Radian($toLatitude), new Radian($toLongitude), $to instanceof Geographic3D ? new Metre($toHeight) : null, $this->epoch);
454 18
    }
455 18
456 18
    /**
457
     * Molodensky
458 18
     * See Abridged Molodensky.
459
     */
460
    public function molodensky(
461
        Geographic2D|Geographic3D $to,
462
        Length $xAxisTranslation,
463
        Length $yAxisTranslation,
464
        Length $zAxisTranslation,
465 18
        Length $differenceInSemiMajorAxis,
466
        Scale $differenceInFlattening
467
    ): self {
468
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
469
        $latitude = $this->latitude->asRadians()->getValue();
470
        $longitude = $this->longitude->asRadians()->getValue();
471
        $fromHeight = $this->height ? $this->height->asMetres()->getValue() : 0;
472
        $tx = $xAxisTranslation->asMetres()->getValue();
473 18
        $ty = $yAxisTranslation->asMetres()->getValue();
474 18
        $tz = $zAxisTranslation->asMetres()->getValue();
475 18
        $da = $differenceInSemiMajorAxis->asMetres()->getValue();
476 18
        $df = $differenceInFlattening->asUnity()->getValue();
477 18
478 18
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
479 18
        $b = $ellipsoid->getSemiMinorAxis()->asMetres()->getValue();
480 18
        $e2 = $ellipsoid->getEccentricitySquared();
481 18
482
        $rho = $a * (1 - $e2) / (1 - $e2 * sin($latitude) ** 2) ** (3 / 2);
483 18
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
484 18
485 18
        $f = $ellipsoid->getFlattening();
0 ignored issues
show
Unused Code introduced by
The assignment to $f is dead and can be removed.
Loading history...
486
487 18
        $dLatitude = ((-$tx * sin($latitude) * cos($longitude)) - ($ty * sin($latitude) * sin($longitude)) + ($tz * cos($latitude)) + ($da * ($nu * $e2 * sin($latitude) * cos($latitude)) / $a + $df * ($rho * ($a / $b) + $nu * ($b / $a)) * sin($latitude) * cos($latitude))) / (($rho + $fromHeight) * sin((new ArcSecond(1))->asRadians()->getValue()));
488 18
        $dLongitude = (-$tx * sin($longitude) + $ty * cos($longitude)) / ((($nu + $fromHeight) * cos($latitude)) * sin((new ArcSecond(1))->asRadians()->getValue()));
489
        $dHeight = ($tx * cos($latitude) * cos($longitude)) + ($ty * cos($latitude) * sin($longitude)) + ($tz * sin($latitude)) - $da * $a / $nu + $df * $b / $a * $nu * sin($latitude) ** 2;
490 18
491
        $toLatitude = $latitude + (new ArcSecond($dLatitude))->asRadians()->getValue();
492 18
        $toLongitude = $longitude + (new ArcSecond($dLongitude))->asRadians()->getValue();
493 18
        $toHeight = $fromHeight + $dHeight;
494 18
495
        return static::create($to, new Radian($toLatitude), new Radian($toLongitude), $to instanceof Geographic3D ? new Metre($toHeight) : null, $this->epoch);
496 18
    }
497 18
498 18
    /**
499
     * Albers Equal Area.
500 18
     */
501
    public function albersEqualArea(
502
        Projected $to,
503
        Angle $latitudeOfFalseOrigin,
504
        Angle $longitudeOfFalseOrigin,
505
        Angle $latitudeOf1stStandardParallel,
506 342
        Angle $latitudeOf2ndStandardParallel,
507
        Length $eastingAtFalseOrigin,
508
        Length $northingAtFalseOrigin
509
    ): ProjectedPoint {
510
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
511
        $latitude = $this->latitude->asRadians()->getValue();
512
        $longitude = $this->longitude->asRadians()->getValue();
0 ignored issues
show
Unused Code introduced by
The assignment to $longitude is dead and can be removed.
Loading history...
513
        $phiOrigin = $latitudeOfFalseOrigin->asRadians()->getValue();
514
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
515 342
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
516 342
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
517 342
        $e = $ellipsoid->getEccentricity();
518 342
        $e2 = $ellipsoid->getEccentricitySquared();
519 342
520 342
        $centralMeridianFirstParallel = cos($phi1) / sqrt(1 - ($e2 * sin($phi1) ** 2));
521 342
        $centralMeridianSecondParallel = cos($phi2) / sqrt(1 - ($e2 * sin($phi2) ** 2));
522 342
523 342
        $alpha = (1 - $e2) * (sin($latitude) / (1 - $e2 * sin($latitude) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))));
524
        $alphaOrigin = (1 - $e2) * (sin($phiOrigin) / (1 - $e2 * sin($phiOrigin) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($phiOrigin)) / (1 + $e * sin($phiOrigin))));
525 342
        $alphaFirstParallel = (1 - $e2) * (sin($phi1) / (1 - $e2 * sin($phi1) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))));
526 342
        $alphaSecondParallel = (1 - $e2) * (sin($phi2) / (1 - $e2 * sin($phi2) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))));
527
528 342
        $n = ($centralMeridianFirstParallel ** 2 - $centralMeridianSecondParallel ** 2) / ($alphaSecondParallel - $alphaFirstParallel);
529 342
        $C = $centralMeridianFirstParallel ** 2 + $n * $alphaFirstParallel;
530 342
        $theta = $n * $this->normaliseLongitude($this->longitude->subtract($longitudeOfFalseOrigin))->asRadians()->getValue();
531 342
        $rho = $a * sqrt($C - $n * $alpha) / $n;
532
        $rhoOrigin = ($a * sqrt($C - $n * $alphaOrigin)) / $n;
533 342
534 342
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + ($rho * sin($theta));
535 342
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rhoOrigin - ($rho * cos($theta));
536 342
537 342
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
538
    }
539 342
540 342
    /**
541
     * American Polyconic.
542 342
     */
543
    public function americanPolyconic(
544
        Projected $to,
545
        Angle $latitudeOfNaturalOrigin,
546
        Angle $longitudeOfNaturalOrigin,
547
        Length $falseEasting,
548 45
        Length $falseNorthing
549
    ): ProjectedPoint {
550
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
551
        $latitude = $this->latitude->asRadians()->getValue();
552
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
553
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
554
        $e = $ellipsoid->getEccentricity();
555 45
        $e2 = $ellipsoid->getEccentricitySquared();
556 45
        $e4 = $e ** 4;
557 45
        $e6 = $e ** 6;
558 45
559 45
        $M = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitude - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitude) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitude) - (35 * $e6 / 3072) * sin(6 * $latitude));
560 45
        $MO = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitudeOrigin - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitudeOrigin) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitudeOrigin) - (35 * $e6 / 3072) * sin(6 * $latitudeOrigin));
561 45
562 45
        if ($latitude === 0.0) {
0 ignored issues
show
introduced by
The condition $latitude === 0.0 is always false.
Loading history...
563
            $easting = $falseEasting->asMetres()->getValue() + $a * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
564 45
            $northing = $falseNorthing->asMetres()->getValue() - $MO;
565 45
        } else {
566
            $L = $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue() * sin($latitude);
567 45
            $nu = $a / sqrt(1 - $e2 * sin($latitude) ** 2);
568
569
            $easting = $falseEasting->asMetres()->getValue() + $nu * 1 / tan($latitude) * sin($L);
570
            $northing = $falseNorthing->asMetres()->getValue() + $M - $MO + $nu * 1 / tan($latitude) * (1 - cos($L));
571 45
        }
572 45
573
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
574 45
    }
575 45
576
    /**
577
     * Bonne.
578 45
     */
579
    public function bonne(
580
        Projected $to,
581
        Angle $latitudeOfNaturalOrigin,
582
        Angle $longitudeOfNaturalOrigin,
583
        Length $falseEasting,
584 9
        Length $falseNorthing
585
    ): ProjectedPoint {
586
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
587
        $latitude = $this->latitude->asRadians()->getValue();
588
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
589
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
590
        $e = $ellipsoid->getEccentricity();
591 9
        $e2 = $ellipsoid->getEccentricitySquared();
592 9
        $e4 = $e ** 4;
593 9
        $e6 = $e ** 6;
594 9
595 9
        $m = cos($latitude) / sqrt(1 - $e2 * sin($latitude) ** 2);
596 9
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
597 9
598 9
        $M = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitude - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitude) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitude) - (35 * $e6 / 3072) * sin(6 * $latitude));
599
        $MO = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitudeOrigin - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitudeOrigin) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitudeOrigin) - (35 * $e6 / 3072) * sin(6 * $latitudeOrigin));
600 9
601 9
        $rho = $a * $mO / sin($latitudeOrigin) + $MO - $M;
602
        $tau = $a * $m * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue() / $rho;
603 9
604 9
        $easting = $falseEasting->asMetres()->getValue() + ($rho * sin($tau));
605
        $northing = $falseNorthing->asMetres()->getValue() + (($a * $mO / sin($latitudeOrigin) - $rho * cos($tau)));
606 9
607 9
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
608
    }
609 9
610 9
    /**
611
     * Bonne South Orientated.
612 9
     */
613
    public function bonneSouthOrientated(
614
        Projected $to,
615
        Angle $latitudeOfNaturalOrigin,
616
        Angle $longitudeOfNaturalOrigin,
617
        Length $falseEasting,
618 27
        Length $falseNorthing
619
    ): ProjectedPoint {
620
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
621
        $latitude = $this->latitude->asRadians()->getValue();
622
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
623
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
624
        $e = $ellipsoid->getEccentricity();
625 27
        $e2 = $ellipsoid->getEccentricitySquared();
626 27
        $e4 = $e ** 4;
627 27
        $e6 = $e ** 6;
628 27
629 27
        $m = cos($latitude) / sqrt(1 - $e2 * sin($latitude) ** 2);
630 27
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
631 27
632 27
        $M = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitude - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitude) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitude) - (35 * $e6 / 3072) * sin(6 * $latitude));
633
        $MO = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitudeOrigin - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitudeOrigin) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitudeOrigin) - (35 * $e6 / 3072) * sin(6 * $latitudeOrigin));
634 27
635 27
        $rho = $a * $mO / sin($latitudeOrigin) + $MO - $M;
636
        $tau = $a * $m * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue() / $rho;
637 27
638 27
        $westing = $falseEasting->asMetres()->getValue() - ($rho * sin($tau));
639
        $southing = $falseNorthing->asMetres()->getValue() - (($a * $mO / sin($latitudeOrigin) - $rho * cos($tau)));
640 27
641 27
        return ProjectedPoint::create($to, new Metre(-$westing), new Metre(-$southing), new Metre($westing), new Metre($southing), $this->epoch);
642
    }
643 27
644 27
    /**
645
     * Cassini-Soldner.
646 27
     */
647
    public function cassiniSoldner(
648
        Projected $to,
649
        Angle $latitudeOfNaturalOrigin,
650
        Angle $longitudeOfNaturalOrigin,
651
        Length $falseEasting,
652 279
        Length $falseNorthing
653
    ): ProjectedPoint {
654
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
655
        $latitude = $this->latitude->asRadians()->getValue();
656
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
657
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
658
        $e = $ellipsoid->getEccentricity();
659 279
        $e2 = $ellipsoid->getEccentricitySquared();
660 279
        $e4 = $e ** 4;
661 279
        $e6 = $e ** 6;
662 279
663 279
        $M = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitude - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitude) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitude) - (35 * $e6 / 3072) * sin(6 * $latitude));
664 279
        $MO = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitudeOrigin - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitudeOrigin) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitudeOrigin) - (35 * $e6 / 3072) * sin(6 * $latitudeOrigin));
665 279
666 279
        $A = $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue() * cos($latitude);
667
        $T = tan($latitude) ** 2;
668 279
        $C = $e2 * cos($latitude) ** 2 / (1 - $e2);
669 279
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
670
        $X = $M - $MO + $nu * tan($latitude) * ($A ** 2 / 2 + (5 - $T + 6 * $C) * $A ** 4 / 24);
671 279
672 279
        $easting = $falseEasting->asMetres()->getValue() + $nu * ($A - $T * $A ** 3 / 6 - (8 - $T + 8 * $C) * $T * $A ** 5 / 120);
673 279
        $northing = $falseNorthing->asMetres()->getValue() + $X;
674 279
675 279
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
676
    }
677 279
678 279
    /**
679
     * Hyperbolic Cassini-Soldner.
680 279
     */
681
    public function hyperbolicCassiniSoldner(
682
        Projected $to,
683
        Angle $latitudeOfNaturalOrigin,
684
        Angle $longitudeOfNaturalOrigin,
685
        Length $falseEasting,
686 27
        Length $falseNorthing
687
    ): ProjectedPoint {
688
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
689
        $latitude = $this->latitude->asRadians()->getValue();
690
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
691
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
692
        $e = $ellipsoid->getEccentricity();
693 27
        $e2 = $ellipsoid->getEccentricitySquared();
694 27
        $e4 = $e ** 4;
695 27
        $e6 = $e ** 6;
696 27
697 27
        $M = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitude - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitude) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitude) - (35 * $e6 / 3072) * sin(6 * $latitude));
698 27
        $MO = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitudeOrigin - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitudeOrigin) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitudeOrigin) - (35 * $e6 / 3072) * sin(6 * $latitudeOrigin));
699 27
700 27
        $A = $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue() * cos($latitude);
701
        $T = tan($latitude) ** 2;
702 27
        $C = $e2 * cos($latitude) ** 2 / (1 - $e2);
703 27
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
704
        $rho = $a * (1 - $e2) / (1 - $e2 * sin($latitude) ** 2) ** (3 / 2);
705 27
        $X = $M - $MO + $nu * tan($latitude) * ($A ** 2 / 2 + (5 - $T + 6 * $C) * $A ** 4 / 24);
706 27
707 27
        $easting = $falseEasting->asMetres()->getValue() + $nu * ($A - $T * $A ** 3 / 6 - (8 - $T + 8 * $C) * $T * $A ** 5 / 120);
708 27
        $northing = $falseNorthing->asMetres()->getValue() + $X - ($X ** 3 / (6 * $rho * $nu));
709 27
710 27
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
711
    }
712 27
713 27
    /**
714
     * Colombia Urban.
715 27
     */
716
    public function columbiaUrban(
717
        Projected $to,
718
        Angle $latitudeOfNaturalOrigin,
719
        Angle $longitudeOfNaturalOrigin,
720
        Length $falseEasting,
721 297
        Length $falseNorthing,
722
        Length $projectionPlaneOriginHeight
723
    ): ProjectedPoint {
724
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
725
        $latitude = $this->latitude->asRadians()->getValue();
726
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
727
        $heightOrigin = $projectionPlaneOriginHeight->asMetres()->getValue();
728
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
729 297
        $e2 = $ellipsoid->getEccentricitySquared();
730 297
731 297
        $rhoOrigin = $a * (1 - $e2) / (1 - $e2 * sin($latitudeOrigin) ** 2) ** (3 / 2);
732 297
        $rhoMid = $a * (1 - $e2) / (1 - $e2 * sin(($latitude + $latitudeOrigin) / 2) ** 2) ** (3 / 2);
733 297
734 297
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
735
        $nuOrigin = $a / sqrt(1 - $e2 * (sin($latitudeOrigin) ** 2));
736 297
737 297
        $A = 1 + $heightOrigin / $nuOrigin;
738
        $B = tan($latitudeOrigin) / (2 * $rhoOrigin * $nuOrigin);
739 297
        $G = 1 + $heightOrigin / $rhoMid;
740 297
741
        $easting = $falseEasting->asMetres()->getValue() + $A * $nu * cos($latitude) * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
742 297
        $northing = $falseNorthing->asMetres()->getValue() + $G * $rhoOrigin * (($latitude - $latitudeOrigin) + ($B * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue() ** 2 * $nu ** 2 * cos($latitude) ** 2));
743 297
744 297
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
745
    }
746 297
747 297
    /**
748
     * Equal Earth.
749 297
     */
750
    public function equalEarth(
751
        Projected $to,
752
        Angle $longitudeOfNaturalOrigin,
753
        Length $falseEasting,
754
        Length $falseNorthing
755 36
    ): ProjectedPoint {
756
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
757
        $latitude = $this->latitude->asRadians()->getValue();
758
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
759
        $e = $ellipsoid->getEccentricity();
760
        $e2 = $ellipsoid->getEccentricitySquared();
761 36
762 36
        $q = (1 - $e2) * ((sin($latitude) / (1 - $e2 * sin($latitude) ** 2)) - (1 / (2 * $e) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude)))));
763 36
        $qP = (1 - $e2) * ((1 / (1 - $e2)) - (1 / (2 * $e) * log((1 - $e) / (1 + $e))));
764 36
        $beta = self::asin($q / $qP);
765 36
        $theta = self::asin(sin($beta) * sqrt(3) / 2);
766
        $Rq = $a * sqrt($qP / 2);
767 36
768 36
        $easting = $falseEasting->asMetres()->getValue() + ($Rq * 2 * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue() * cos($theta)) / (sqrt(3) * (1.340264 - 0.243318 * $theta ** 2 + $theta ** 6 * (0.006251 + 0.034164 * $theta ** 2)));
769 36
        $northing = $falseNorthing->asMetres()->getValue() + $Rq * $theta * (1.340264 - 0.081106 * $theta ** 2 + $theta ** 6 * (0.000893 + 0.003796 * $theta ** 2));
770 36
771 36
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
772
    }
773 36
774 36
    /**
775
     * Equidistant Cylindrical
776 36
     * See method code 1029 for spherical development. See also Pseudo Plate Carree, method code 9825.
777
     */
778
    public function equidistantCylindrical(
779
        Projected $to,
780
        Angle $latitudeOf1stStandardParallel,
781
        Angle $longitudeOfNaturalOrigin,
782
        Length $falseEasting,
783 18
        Length $falseNorthing
784
    ): ProjectedPoint {
785
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
786
        $latitude = $this->latitude->asRadians()->getValue();
787
        $latitudeFirstParallel = $latitudeOf1stStandardParallel->asRadians()->getValue();
788
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
789
        $e = $ellipsoid->getEccentricity();
790 18
        $e2 = $ellipsoid->getEccentricitySquared();
791 18
        $e4 = $e ** 4;
792 18
        $e6 = $e ** 6;
793 18
        $e8 = $e ** 8;
794 18
        $e10 = $e ** 10;
795 18
        $e12 = $e ** 12;
796 18
        $e14 = $e ** 14;
797 18
798 18
        $nu1 = $a / sqrt(1 - $e2 * sin($latitudeFirstParallel) ** 2);
799 18
800 18
        $M = $a * (
801 18
            (1 - 1 / 4 * $e2 - 3 / 64 * $e4 - 5 / 256 * $e6 - 175 / 16384 * $e8 - 441 / 65536 * $e10 - 4851 / 1048576 * $e12 - 14157 / 4194304 * $e14) * $latitude +
802
            (-3 / 8 * $e2 - 3 / 32 * $e4 - 45 / 1024 * $e6 - 105 / 4096 * $e8 - 2205 / 131072 * $e10 - 6237 / 524288 * $e12 - 297297 / 33554432 * $e14) * sin(2 * $latitude) +
803 18
            (15 / 256 * $e4 + 45 / 1024 * $e ** 6 + 525 / 16384 * $e ** 8 + 1575 / 65536 * $e10 + 155925 / 8388608 * $e12 + 495495 / 33554432 * $e14) * sin(4 * $latitude) +
804
            (-35 / 3072 * $e6 - 175 / 12288 * $e8 - 3675 / 262144 * $e10 - 13475 / 1048576 * $e12 - 385385 / 33554432 * $e14) * sin(6 * $latitude) +
805 18
            (315 / 131072 * $e8 + 2205 / 524288 * $e10 + 43659 / 8388608 * $e12 + 189189 / 33554432 * $e14) * sin(8 * $latitude) +
806 18
            (-693 / 1310720 * $e10 - 6537 / 5242880 * $e12 - 297297 / 167772160 * $e14) * sin(10 * $latitude) +
807 18
            (1001 / 8388608 * $e12 + 11011 / 33554432 * $e14) * sin(12 * $latitude) +
808 18
            (-6435 / 234881024 * $e ** 14) * sin(14 * $latitude)
809 18
        );
810 18
811 18
        $easting = $falseEasting->asMetres()->getValue() + $nu1 * cos($latitudeFirstParallel) * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
812 18
        $northing = $falseNorthing->asMetres()->getValue() + $M;
813 18
814
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
815
    }
816 18
817 18
    /**
818
     * Guam Projection
819 18
     * Simplified form of Oblique Azimuthal Equidistant projection method.
820
     */
821
    public function guamProjection(
822
        Projected $to,
823
        Angle $latitudeOfNaturalOrigin,
824
        Angle $longitudeOfNaturalOrigin,
825
        Length $falseEasting,
826 18
        Length $falseNorthing
827
    ): ProjectedPoint {
828
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
829
        $latitude = $this->latitude->asRadians()->getValue();
830
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
831
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
832
        $e = $ellipsoid->getEccentricity();
833 18
        $e2 = $ellipsoid->getEccentricitySquared();
834 18
        $e4 = $e ** 4;
835 18
        $e6 = $e ** 6;
836 18
837 18
        $M = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitude - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitude) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitude) - (35 * $e6 / 3072) * sin(6 * $latitude));
838 18
        $MO = $a * ((1 - $e2 / 4 - 3 * $e4 / 64 - 5 * $e6 / 256) * $latitudeOrigin - (3 * $e2 / 8 + 3 * $e4 / 32 + 45 * $e6 / 1024) * sin(2 * $latitudeOrigin) + (15 * $e4 / 256 + 45 * $e6 / 1024) * sin(4 * $latitudeOrigin) - (35 * $e6 / 3072) * sin(6 * $latitudeOrigin));
839 18
        $x = ($a * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue() * cos($latitude)) / sqrt(1 - $e2 * sin($latitude) ** 2);
840 18
841
        $easting = $falseEasting->asMetres()->getValue() + $x;
842 18
        $northing = $falseNorthing->asMetres()->getValue() + $M - $MO + ($x ** 2 * tan($latitude) * sqrt(1 - $e2 * sin($latitude) ** 2) / (2 * $a));
843 18
844 18
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
845
    }
846 18
847 18
    /**
848
     * Krovak.
849 18
     */
850
    public function krovak(
851
        Projected $to,
852
        Angle $latitudeOfProjectionCentre,
853
        Angle $longitudeOfOrigin,
854
        Angle $coLatitudeOfConeAxis,
855 126
        Angle $latitudeOfPseudoStandardParallel,
856
        Scale $scaleFactorOnPseudoStandardParallel,
857
        Length $falseEasting,
858
        Length $falseNorthing
859
    ): ProjectedPoint {
860
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
861
        $longitudeOffset = $to->getDatum()->getPrimeMeridian()->getGreenwichLongitude()->asRadians()->getValue() - $this->getCRS()->getDatum()->getPrimeMeridian()->getGreenwichLongitude()->asRadians()->getValue();
862
        $latitude = $this->latitude->asRadians()->getValue();
863
        $longitude = $this->longitude->asRadians()->getValue() - $longitudeOffset;
864
        $latitudeC = $latitudeOfProjectionCentre->asRadians()->getValue();
865 126
        $longitudeO = $longitudeOfOrigin->asRadians()->getValue();
866 126
        $alphaC = $coLatitudeOfConeAxis->asRadians()->getValue();
867 126
        $latitudeP = $latitudeOfPseudoStandardParallel->asRadians()->getValue();
868 126
        $kP = $scaleFactorOnPseudoStandardParallel->asUnity()->getValue();
869 126
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
870 126
        $e = $ellipsoid->getEccentricity();
871 126
        $e2 = $ellipsoid->getEccentricitySquared();
872 126
873 126
        $A = $a * sqrt(1 - $e2) / (1 - $e2 * sin($latitudeC) ** 2);
874 126
        $B = sqrt(1 + $e2 * cos($latitudeC) ** 4 / (1 - $e2));
875 126
        $upsilonO = self::asin(sin($latitudeC) / $B);
876 126
        $tO = tan(M_PI / 4 + $upsilonO / 2) * ((1 + $e * sin($latitudeC)) / (1 - $e * sin($latitudeC))) ** ($e * $B / 2) / (tan(M_PI / 4 + $latitudeC / 2) ** $B);
877
        $n = sin($latitudeP);
878 126
        $rO = $kP * $A / tan($latitudeP);
879 126
880 126
        $U = 2 * (atan($tO * tan($latitude / 2 + M_PI / 4) ** $B / ((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e * $B / 2)) - M_PI / 4);
881 126
        $V = $B * ($longitudeO - $longitude);
882 126
        $T = self::asin(cos($alphaC) * sin($U) + sin($alphaC) * cos($U) * cos($V));
883 126
        $D = atan2(cos($U) * sin($V) / cos($T), ((cos($alphaC) * sin($T) - sin($U)) / (sin($alphaC) * cos($T))));
884
        $theta = $n * $D;
885 126
        $r = $rO * tan(M_PI / 4 + $latitudeP / 2) ** $n / tan($T / 2 + M_PI / 4) ** $n;
886 126
        $X = $r * cos($theta);
887 126
        $Y = $r * sin($theta);
888 126
889 126
        $westing = $Y + $falseEasting->asMetres()->getValue();
890 126
        $southing = $X + $falseNorthing->asMetres()->getValue();
891 126
892 126
        return ProjectedPoint::create($to, new Metre(-$westing), new Metre(-$southing), new Metre($westing), new Metre($southing), $this->epoch);
893
    }
894 126
895 126
    /**
896
     * Krovak Modified
897 126
     * Incorporates a polynomial transformation which is defined to be exact and for practical purposes is considered
898
     * to be a map projection.
899
     */
900
    public function krovakModified(
901
        Projected $to,
902
        Angle $latitudeOfProjectionCentre,
903
        Angle $longitudeOfOrigin,
904
        Angle $coLatitudeOfConeAxis,
905 54
        Angle $latitudeOfPseudoStandardParallel,
906
        Scale $scaleFactorOnPseudoStandardParallel,
907
        Length $falseEasting,
908
        Length $falseNorthing,
909
        Length $ordinate1OfEvaluationPoint,
910
        Length $ordinate2OfEvaluationPoint,
911
        Coefficient $C1,
912
        Coefficient $C2,
913
        Coefficient $C3,
914
        Coefficient $C4,
915
        Coefficient $C5,
916
        Coefficient $C6,
917
        Coefficient $C7,
918
        Coefficient $C8,
919
        Coefficient $C9,
920
        Coefficient $C10
921
    ): ProjectedPoint {
922
        $asKrovak = $this->krovak($to, $latitudeOfProjectionCentre, $longitudeOfOrigin, $coLatitudeOfConeAxis, $latitudeOfPseudoStandardParallel, $scaleFactorOnPseudoStandardParallel, new Metre(0), new Metre(0));
923
924
        $westing = $asKrovak->getWesting()->asMetres()->getValue();
925
        $southing = $asKrovak->getSouthing()->asMetres()->getValue();
926
        $c1 = $C1->asUnity()->getValue();
927 54
        $c2 = $C2->asUnity()->getValue();
928
        $c3 = $C3->asUnity()->getValue();
929 54
        $c4 = $C4->asUnity()->getValue();
930 54
        $c5 = $C5->asUnity()->getValue();
931 54
        $c6 = $C6->asUnity()->getValue();
932 54
        $c7 = $C7->asUnity()->getValue();
933 54
        $c8 = $C8->asUnity()->getValue();
934 54
        $c9 = $C9->asUnity()->getValue();
935 54
        $c10 = $C10->asUnity()->getValue();
936 54
937 54
        $Xr = $southing - $ordinate1OfEvaluationPoint->asMetres()->getValue();
938 54
        $Yr = $westing - $ordinate2OfEvaluationPoint->asMetres()->getValue();
939 54
940 54
        $dX = $c1 + $c3 * $Xr - $c4 * $Yr - 2 * $c6 * $Xr * $Yr + $c5 * ($Xr ** 2 - $Yr ** 2) + $c7 * $Xr * ($Xr ** 2 - 3 * $Yr ** 2) - $c8 * $Yr * (3 * $Xr ** 2 - $Yr ** 2) + 4 * $c9 * $Xr * $Yr * ($Xr ** 2 - $Yr ** 2) + $c10 * ($Xr ** 4 + $Yr ** 4 - 6 * $Xr ** 2 * $Yr ** 2);
941
        $dY = $c2 + $c3 * $Yr + $c4 * $Xr + 2 * $c5 * $Xr * $Yr + $c6 * ($Xr ** 2 - $Yr ** 2) + $c8 * $Xr * ($Xr ** 2 - 3 * $Yr ** 2) + $c7 * $Yr * (3 * $Xr ** 2 - $Yr ** 2) - 4 * $c10 * $Xr * $Yr * ($Xr ** 2 - $Yr ** 2) + $c9 * ($Xr ** 4 + $Yr ** 4 - 6 * $Xr ** 2 * $Yr ** 2);
942 54
943 54
        $westing += $falseEasting->asMetres()->getValue() - $dY;
944
        $southing += $falseNorthing->asMetres()->getValue() - $dX;
945 54
946 54
        return ProjectedPoint::create($to, new Metre(-$westing), new Metre(-$southing), new Metre($westing), new Metre($southing), $this->epoch);
947
    }
948 54
949 54
    /**
950
     * Lambert Azimuthal Equal Area
951 54
     * This is the ellipsoidal form of the projection.
952
     */
953
    public function lambertAzimuthalEqualArea(
954
        Projected $to,
955
        Angle $latitudeOfNaturalOrigin,
956
        Angle $longitudeOfNaturalOrigin,
957
        Length $falseEasting,
958 135
        Length $falseNorthing
959
    ): ProjectedPoint {
960
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
961
        $latitude = $this->latitude->asRadians()->getValue();
962
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
963
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
964
        $e = $ellipsoid->getEccentricity();
965 135
        $e2 = $ellipsoid->getEccentricitySquared();
966 135
967 135
        $q = (1 - $e2) * ((sin($latitude) / (1 - $e2 * sin($latitude) ** 2)) - ((1 / (2 * $e)) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude)))));
968 135
        $qO = (1 - $e2) * ((sin($latitudeOrigin) / (1 - $e2 * sin($latitudeOrigin) ** 2)) - ((1 / (2 * $e)) * log((1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin)))));
969 135
        $qP = (1 - $e2) * ((1 / (1 - $e2)) - ((1 / (2 * $e)) * log((1 - $e) / (1 + $e))));
970 135
        $beta = self::asin($q / $qP);
971
        $betaO = self::asin($qO / $qP);
972 135
        $Rq = $a * sqrt($qP / 2);
973 135
        $B = $Rq * sqrt(2 / (1 + sin($betaO) * sin($beta) + (cos($betaO) * cos($beta) * cos($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue()))));
974 135
        $D = $a * (cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2)) / ($Rq * cos($betaO));
975 135
976 135
        $easting = $falseEasting->asMetres()->getValue() + (($B * $D) * (cos($beta) * sin($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue())));
977 135
        $northing = $falseNorthing->asMetres()->getValue() + ($B / $D) * ((cos($betaO) * sin($beta)) - (sin($betaO) * cos($beta) * cos($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue())));
978 135
979 135
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
980
    }
981 135
982 135
    /**
983
     * Lambert Azimuthal Equal Area (Spherical)
984 135
     * This is the spherical form of the projection.  See coordinate operation method Lambert Azimuthal Equal Area
985
     * (code 9820) for ellipsoidal form.  Differences of several tens of metres result from comparison of the two
986
     * methods.
987
     */
988
    public function lambertAzimuthalEqualAreaSpherical(
989
        Projected $to,
990
        Angle $latitudeOfNaturalOrigin,
991
        Angle $longitudeOfNaturalOrigin,
992
        Length $falseEasting,
993 18
        Length $falseNorthing
994
    ): ProjectedPoint {
995
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
996
        $latitude = $this->latitude->asRadians()->getValue();
997
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
998
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
999
1000 18
        $k = sqrt(2 / (1 + sin($latitudeOrigin) * sin($latitude) + cos($latitudeOrigin) * cos($latitude) * cos($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue())));
1001 18
1002 18
        $easting = $falseEasting->asMetres()->getValue() + ($a * $k * cos($latitude) * sin($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue()));
1003 18
        $northing = $falseNorthing->asMetres()->getValue() + ($a * $k * (cos($latitudeOrigin) * sin($latitude) - sin($latitudeOrigin) * cos($latitude) * cos($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue())));
1004
1005 18
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1006
    }
1007 18
1008 18
    /**
1009
     * Lambert Conic Conformal (1SP).
1010 18
     */
1011
    public function lambertConicConformal1SP(
1012
        Projected $to,
1013
        Angle $latitudeOfNaturalOrigin,
1014
        Angle $longitudeOfNaturalOrigin,
1015
        Scale $scaleFactorAtNaturalOrigin,
1016 1953
        Length $falseEasting,
1017
        Length $falseNorthing
1018
    ): ProjectedPoint {
1019
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1020
        $latitude = $this->latitude->asRadians()->getValue();
1021
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1022
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1023
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1024 1953
        $e = $ellipsoid->getEccentricity();
1025 1953
        $e2 = $ellipsoid->getEccentricitySquared();
1026 1953
1027 1953
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
1028 1953
        $tO = tan(M_PI / 4 - $latitudeOrigin / 2) / ((1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin))) ** ($e / 2);
1029 1953
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1030 1953
        $n = sin($latitudeOrigin);
1031
        $F = $mO / ($n * $tO ** $n);
1032 1953
        $rO = $a * $F * $tO ** $n * $kO;
1033 1953
        $r = $a * $F * $t ** $n * $kO;
1034 1953
        $theta = $n * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
1035 1953
1036 1953
        $easting = $falseEasting->asMetres()->getValue() + $r * sin($theta);
1037 1953
        $northing = $falseNorthing->asMetres()->getValue() + $rO - $r * cos($theta);
1038 1953
1039 1953
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1040
    }
1041 1953
1042 1953
    /**
1043
     * Lambert Conic Conformal (1SP) Variant B.
1044 1953
     */
1045
    public function lambertConicConformal1SPVariantB(
1046
        Projected $to,
1047
        Angle $latitudeOfNaturalOrigin,
1048
        Scale $scaleFactorAtNaturalOrigin,
1049
        Angle $latitudeOfFalseOrigin,
1050 9
        Angle $longitudeOfFalseOrigin,
1051
        Length $eastingAtFalseOrigin,
1052
        Length $northingAtFalseOrigin
1053
    ): ProjectedPoint {
1054
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1055
        $latitude = $this->latitude->asRadians()->getValue();
1056
        $latitudeNaturalOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1057
        $latitudeFalseOrigin = $latitudeOfFalseOrigin->asRadians()->getValue();
1058
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1059 9
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1060 9
        $e = $ellipsoid->getEccentricity();
1061 9
        $e2 = $ellipsoid->getEccentricitySquared();
1062 9
1063 9
        $mO = cos($latitudeNaturalOrigin) / sqrt(1 - $e2 * sin($latitudeNaturalOrigin) ** 2);
1064 9
        $tO = tan(M_PI / 4 - $latitudeNaturalOrigin / 2) / ((1 - $e * sin($latitudeNaturalOrigin)) / (1 + $e * sin($latitudeNaturalOrigin))) ** ($e / 2);
1065 9
        $tF = tan(M_PI / 4 - $latitudeFalseOrigin / 2) / ((1 - $e * sin($latitudeFalseOrigin)) / (1 + $e * sin($latitudeFalseOrigin))) ** ($e / 2);
1066 9
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1067
        $n = sin($latitudeNaturalOrigin);
1068 9
        $F = $mO / ($n * $tO ** $n);
1069 9
        $rF = $a * $F * $tF ** $n * $kO;
1070 9
        $r = $a * $F * $t ** $n * $kO;
1071 9
        $theta = $n * $this->normaliseLongitude($this->longitude->subtract($longitudeOfFalseOrigin))->asRadians()->getValue();
1072 9
1073 9
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $r * sin($theta);
1074 9
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rF - $r * cos($theta);
1075 9
1076 9
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1077
    }
1078 9
1079 9
    /**
1080
     * Lambert Conic Conformal (2SP Belgium)
1081 9
     * In 2000 this modification was replaced through use of the regular Lambert Conic Conformal (2SP) method [9802]
1082
     * with appropriately modified parameter values.
1083
     */
1084
    public function lambertConicConformal2SPBelgium(
1085
        Projected $to,
1086
        Angle $latitudeOfFalseOrigin,
1087
        Angle $longitudeOfFalseOrigin,
1088
        Angle $latitudeOf1stStandardParallel,
1089 18
        Angle $latitudeOf2ndStandardParallel,
1090
        Length $eastingAtFalseOrigin,
1091
        Length $northingAtFalseOrigin
1092
    ): ProjectedPoint {
1093
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1094
        $latitude = $this->latitude->asRadians()->getValue();
1095
        $phiF = $latitudeOfFalseOrigin->asRadians()->getValue();
1096
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
1097
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
1098 18
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1099 18
        $e = $ellipsoid->getEccentricity();
1100 18
        $e2 = $ellipsoid->getEccentricitySquared();
1101 18
1102 18
        $m1 = cos($phi1) / sqrt(1 - $e2 * sin($phi1) ** 2);
1103 18
        $m2 = cos($phi2) / sqrt(1 - $e2 * sin($phi2) ** 2);
1104 18
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1105 18
        $t1 = tan(M_PI / 4 - $phi1 / 2) / ((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))) ** ($e / 2);
1106
        $t2 = tan(M_PI / 4 - $phi2 / 2) / ((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))) ** ($e / 2);
1107 18
        $tF = tan(M_PI / 4 - $phiF / 2) / ((1 - $e * sin($phiF)) / (1 + $e * sin($phiF))) ** ($e / 2);
1108 18
        $n = (log($m1) - log($m2)) / (log($t1) - log($t2));
1109 18
        $F = $m1 / ($n * $t1 ** $n);
1110 18
        $r = $a * $F * $t ** $n;
1111 18
        $rF = $a * $F * $tF ** $n;
1112 18
        if (is_nan($rF)) {
1113 18
            $rF = 0;
1114 18
        }
1115 18
        $theta = ($n * $this->normaliseLongitude($this->longitude->subtract($longitudeOfFalseOrigin))->asRadians()->getValue()) - (new ArcSecond(29.2985))->asRadians()->getValue();
1116 18
1117 18
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $r * sin($theta);
1118 9
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rF - $r * cos($theta);
1119
1120 18
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1121
    }
1122 18
1123 18
    /**
1124
     * Lambert Conic Conformal (2SP Michigan).
1125 18
     */
1126
    public function lambertConicConformal2SPMichigan(
1127
        Projected $to,
1128
        Angle $latitudeOfFalseOrigin,
1129
        Angle $longitudeOfFalseOrigin,
1130
        Angle $latitudeOf1stStandardParallel,
1131 36
        Angle $latitudeOf2ndStandardParallel,
1132
        Length $eastingAtFalseOrigin,
1133
        Length $northingAtFalseOrigin,
1134
        Scale $ellipsoidScalingFactor
1135
    ): ProjectedPoint {
1136
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1137
        $latitude = $this->latitude->asRadians()->getValue();
1138
        $phiF = $latitudeOfFalseOrigin->asRadians()->getValue();
1139
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
1140
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
1141 36
        $K = $ellipsoidScalingFactor->asUnity()->getValue();
1142 36
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1143 36
        $e = $ellipsoid->getEccentricity();
1144 36
        $e2 = $ellipsoid->getEccentricitySquared();
1145 36
1146 36
        $m1 = cos($phi1) / sqrt(1 - $e2 * sin($phi1) ** 2);
1147 36
        $m2 = cos($phi2) / sqrt(1 - $e2 * sin($phi2) ** 2);
1148 36
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1149 36
        $t1 = tan(M_PI / 4 - $phi1 / 2) / ((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))) ** ($e / 2);
1150
        $t2 = tan(M_PI / 4 - $phi2 / 2) / ((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))) ** ($e / 2);
1151 36
        $tF = tan(M_PI / 4 - $phiF / 2) / ((1 - $e * sin($phiF)) / (1 + $e * sin($phiF))) ** ($e / 2);
1152 36
        $n = (log($m1) - log($m2)) / (log($t1) - log($t2));
1153 36
        $F = $m1 / ($n * $t1 ** $n);
1154 36
        $r = $a * $K * $F * $t ** $n;
1155 36
        $rF = $a * $K * $F * $tF ** $n;
1156 36
        $theta = $n * $this->normaliseLongitude($this->longitude->subtract($longitudeOfFalseOrigin))->asRadians()->getValue();
1157 36
1158 36
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $r * sin($theta);
1159 36
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rF - $r * cos($theta);
1160 36
1161 36
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1162
    }
1163 36
1164 36
    /**
1165
     * Lambert Conic Conformal (2SP).
1166 36
     */
1167
    public function lambertConicConformal2SP(
1168
        Projected $to,
1169
        Angle $latitudeOfFalseOrigin,
1170
        Angle $longitudeOfFalseOrigin,
1171
        Angle $latitudeOf1stStandardParallel,
1172 8244
        Angle $latitudeOf2ndStandardParallel,
1173
        Length $eastingAtFalseOrigin,
1174
        Length $northingAtFalseOrigin
1175
    ): ProjectedPoint {
1176
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1177
        $latitude = $this->latitude->asRadians()->getValue();
1178
        $phiF = $latitudeOfFalseOrigin->asRadians()->getValue();
1179
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
1180
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
1181 8244
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1182 8244
        $e = $ellipsoid->getEccentricity();
1183 8244
        $e2 = $ellipsoid->getEccentricitySquared();
1184 8244
1185 8244
        $m1 = cos($phi1) / sqrt(1 - $e2 * sin($phi1) ** 2);
1186 8244
        $m2 = cos($phi2) / sqrt(1 - $e2 * sin($phi2) ** 2);
1187 8244
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1188 8244
        $t1 = tan(M_PI / 4 - $phi1 / 2) / ((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))) ** ($e / 2);
1189
        $t2 = tan(M_PI / 4 - $phi2 / 2) / ((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))) ** ($e / 2);
1190 8244
        $tF = tan(M_PI / 4 - $phiF / 2) / ((1 - $e * sin($phiF)) / (1 + $e * sin($phiF))) ** ($e / 2);
1191 8244
        $n = (log($m1) - log($m2)) / (log($t1) - log($t2));
1192 8244
        $F = $m1 / ($n * $t1 ** $n);
1193 8244
        $r = $a * $F * $t ** $n;
1194 8244
        $rF = $a * $F * $tF ** $n;
1195 8244
        $theta = $n * $this->normaliseLongitude($this->longitude->subtract($longitudeOfFalseOrigin))->asRadians()->getValue();
1196 8244
1197 8244
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $r * sin($theta);
1198 8244
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rF - $r * cos($theta);
1199 8244
1200 8244
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1201
    }
1202 8244
1203 8244
    /**
1204
     * Lambert Conic Conformal (West Orientated).
1205 8244
     */
1206
    public function lambertConicConformalWestOrientated(
1207
        Projected $to,
1208
        Angle $latitudeOfNaturalOrigin,
1209
        Angle $longitudeOfNaturalOrigin,
1210
        Scale $scaleFactorAtNaturalOrigin,
1211 135
        Length $falseEasting,
1212
        Length $falseNorthing
1213
    ): ProjectedPoint {
1214
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1215
        $latitude = $this->latitude->asRadians()->getValue();
1216
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1217
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1218
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1219 135
        $e = $ellipsoid->getEccentricity();
1220 135
        $e2 = $ellipsoid->getEccentricitySquared();
1221 135
1222 135
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
1223 135
        $tO = tan(M_PI / 4 - $latitudeOrigin / 2) / ((1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin))) ** ($e / 2);
1224 135
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1225 135
        $n = sin($latitudeOrigin);
1226
        $F = $mO / ($n * $tO ** $n);
1227 135
        $rO = $a * $F * $tO ** $n ** $kO;
1228 135
        $r = $a * $F * $t ** $n ** $kO;
1229 135
        $theta = $n * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
1230 135
1231 135
        $westing = $falseEasting->asMetres()->getValue() - $r * sin($theta);
1232 135
        $northing = $falseNorthing->asMetres()->getValue() + $rO - $r * cos($theta);
1233 135
1234 135
        return ProjectedPoint::create($to, new Metre(-$westing), new Metre($northing), new Metre($westing), new Metre(-$northing), $this->epoch);
1235
    }
1236 135
1237 135
    /**
1238
     * Lambert Conic Near-Conformal
1239 135
     * The Lambert Near-Conformal projection is derived from the Lambert Conformal Conic projection by truncating the
1240
     * series expansion of the projection formulae.
1241
     */
1242
    public function lambertConicNearConformal(
1243
        Projected $to,
1244
        Angle $latitudeOfNaturalOrigin,
1245
        Angle $longitudeOfNaturalOrigin,
1246
        Scale $scaleFactorAtNaturalOrigin,
1247 18
        Length $falseEasting,
1248
        Length $falseNorthing
1249
    ): ProjectedPoint {
1250
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1251
        $latitude = $this->latitude->asRadians()->getValue();
1252
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1253
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1254
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1255 18
        $e2 = $ellipsoid->getEccentricitySquared();
1256 18
        $f = $ellipsoid->getFlattening();
1257 18
1258 18
        $n = $f / (2 - $f);
1259 18
        $rhoO = $a * (1 - $e2) / (1 - $e2 * sin($latitudeOrigin) ** 2) ** (3 / 2);
1260 18
        $nuO = $a / sqrt(1 - $e2 * (sin($latitudeOrigin) ** 2));
1261 18
        $A = 1 / (6 * $rhoO * $nuO);
1262
        $APrime = $a * (1 - $n + 5 * ($n ** 2 - $n ** 3) / 4 + 81 * ($n ** 4 - $n ** 5) / 64);
1263 18
        $BPrime = 3 * $a * ($n - $n ** 2 + 7 * ($n ** 3 - $n ** 4) / 8 + 55 * $n ** 5 / 64) / 2;
1264 18
        $CPrime = 15 * $a * ($n ** 2 - $n ** 3 + 3 * ($n ** 4 - $n ** 5) / 4) / 16;
1265 18
        $DPrime = 35 * $a * ($n ** 3 - $n ** 4 + 11 * $n ** 5 / 16) / 48;
1266 18
        $EPrime = 315 * $a * ($n ** 4 - $n ** 5) / 512;
1267 18
        $rO = $kO * $nuO / tan($latitudeOrigin);
1268 18
        $sO = $APrime * $latitudeOrigin - $BPrime * sin(2 * $latitudeOrigin) + $CPrime * sin(4 * $latitudeOrigin) - $DPrime * sin(6 * $latitudeOrigin) + $EPrime * sin(8 * $latitudeOrigin);
1269 18
        $s = $APrime * $latitude - $BPrime * sin(2 * $latitude) + $CPrime * sin(4 * $latitude) - $DPrime * sin(6 * $latitude) + $EPrime * sin(8 * $latitude);
1270 18
        $m = $s - $sO;
1271 18
        $M = $kO * ($m + $A * $m ** 3);
1272 18
        $r = $rO - $M;
1273 18
        $theta = $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue() * sin($latitudeOrigin);
1274 18
1275 18
        $easting = $falseEasting->asMetres()->getValue() + $r * sin($theta);
1276 18
        $northing = $falseNorthing->asMetres()->getValue() + $M + $r * sin($theta) * tan($theta / 2);
1277 18
1278 18
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1279
    }
1280 18
1281 18
    /**
1282
     * Lambert Cylindrical Equal Area
1283 18
     * This is the ellipsoidal form of the projection.
1284
     */
1285
    public function lambertCylindricalEqualArea(
1286
        Projected $to,
1287
        Angle $latitudeOf1stStandardParallel,
1288
        Angle $longitudeOfNaturalOrigin,
1289
        Length $falseEasting,
1290 18
        Length $falseNorthing
1291
    ): ProjectedPoint {
1292
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1293
        $latitude = $this->latitude->asRadians()->getValue();
1294
        $latitudeFirstParallel = $latitudeOf1stStandardParallel->asRadians()->getValue();
1295
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1296
        $e = $ellipsoid->getEccentricity();
1297 18
        $e2 = $ellipsoid->getEccentricitySquared();
1298 18
1299 18
        $k = cos($latitudeFirstParallel) / sqrt(1 - $e2 * sin($latitudeFirstParallel) ** 2);
1300 18
        $q = (1 - $e2) * ((sin($latitude) / (1 - $e2 * sin($latitude) ** 2)) - (1 / (2 * $e)) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))));
1301 18
1302 18
        $x = $a * $k * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
1303
        $y = $a * $q / (2 * $k);
1304 18
1305 18
        $easting = $falseEasting->asMetres()->getValue() + $x;
1306
        $northing = $falseNorthing->asMetres()->getValue() + $y;
1307 18
1308 18
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1309
    }
1310 18
1311 18
    /**
1312
     * Modified Azimuthal Equidistant
1313 18
     * Modified form of Oblique Azimuthal Equidistant projection method developed for Polynesian islands. For the
1314
     * distances over which these projections are used (under 800km) this modification introduces no significant error.
1315
     */
1316
    public function modifiedAzimuthalEquidistant(
1317
        Projected $to,
1318
        Angle $latitudeOfNaturalOrigin,
1319
        Angle $longitudeOfNaturalOrigin,
1320
        Length $falseEasting,
1321 18
        Length $falseNorthing
1322
    ): ProjectedPoint {
1323
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1324
        $latitude = $this->latitude->asRadians()->getValue();
1325
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1326
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1327
        $e = $ellipsoid->getEccentricity();
1328 18
        $e2 = $ellipsoid->getEccentricitySquared();
1329 18
1330 18
        $nuO = $a / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
1331 18
        $nu = $a / sqrt(1 - $e2 * sin($latitude) ** 2);
1332 18
        $psi = atan((1 - $e2) * tan($latitude) + ($e2 * $nuO * sin($latitudeOrigin)) / ($nu * cos($latitude)));
1333 18
        $alpha = atan2(sin($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue()), (cos($latitudeOrigin) * tan($psi) - sin($latitudeOrigin) * cos($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue())));
1334
        $G = $e * sin($latitudeOrigin) / sqrt(1 - $e2);
1335 18
        $H = $e * cos($latitudeOrigin) * cos($alpha) / sqrt(1 - $e2);
1336 18
1337 18
        if (sin($alpha) === 0.0) {
1338 18
            $s = self::asin(cos($latitudeOrigin) * sin($psi) - sin($latitudeOrigin) * cos($alpha)) * cos($alpha) / abs(cos($alpha));
1339 18
        } else {
1340 18
            $s = self::asin(sin($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue()) * cos($psi) / sin($alpha));
1341
        }
1342 18
1343
        $c = $nuO * $s * ((1 - $s ** 2 * $H ** 2 * (1 - $H ** 2) / 6) + (($s ** 3 / 8) * $G * $H * (1 - 2 * $H ** 2)) + ($s ** 4 / 120) * ($H ** 2 * (4 - 7 * $H ** 2) - 3 * $G ** 2 * (1 - 7 * $H ** 2)) - (($s ** 5 / 48) * $G * $H));
1344
1345 18
        $easting = $falseEasting->asMetres()->getValue() + $c * sin($alpha);
1346
        $northing = $falseNorthing->asMetres()->getValue() + $c * cos($alpha);
1347
1348 18
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1349
    }
1350 18
1351 18
    /**
1352
     * Oblique Stereographic
1353 18
     * This is not the same as the projection method of the same name in USGS Professional Paper no. 1395, "Map
1354
     * Projections - A Working Manual" by John P. Snyder.
1355
     */
1356
    public function obliqueStereographic(
1357
        Projected $to,
1358
        Angle $latitudeOfNaturalOrigin,
1359
        Angle $longitudeOfNaturalOrigin,
1360
        Scale $scaleFactorAtNaturalOrigin,
1361 144
        Length $falseEasting,
1362
        Length $falseNorthing
1363
    ): ProjectedPoint {
1364
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1365
        $latitude = $this->latitude->asRadians()->getValue();
1366
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1367
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1368
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1369 144
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1370 144
        $e = $ellipsoid->getEccentricity();
1371 144
        $e2 = $ellipsoid->getEccentricitySquared();
1372 144
1373 144
        $rhoOrigin = $a * (1 - $e2) / (1 - $e2 * sin($latitudeOrigin) ** 2) ** (3 / 2);
1374 144
        $nuOrigin = $a / sqrt(1 - $e2 * (sin($latitudeOrigin) ** 2));
1375 144
        $R = sqrt($rhoOrigin * $nuOrigin);
1376 144
1377
        $n = sqrt(1 + ($e2 * cos($latitudeOrigin) ** 4 / (1 - $e2)));
1378 144
        $S1 = (1 + sin($latitudeOrigin)) / (1 - sin($latitudeOrigin));
1379 144
        $S2 = (1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin));
1380 144
        $w1 = ($S1 * ($S2 ** $e)) ** $n;
1381
        $c = (($n + sin($latitudeOrigin)) * (1 - ($w1 - 1) / ($w1 + 1))) / (($n - sin($latitudeOrigin)) * (1 + ($w1 - 1) / ($w1 + 1)));
1382 144
        $w2 = $c * $w1;
1383 144
        $chiOrigin = self::asin(($w2 - 1) / ($w2 + 1));
1384 144
1385 144
        $lambda = $n * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue() + $longitudeOrigin;
1386 144
1387 144
        $Sa = (1 + sin($latitude)) / (1 - sin($latitude));
1388 144
        $Sb = (1 - $e * sin($latitude)) / (1 + $e * sin($latitude));
1389
        $w = $c * ($Sa * ($Sb ** $e)) ** $n;
1390 144
        $chi = self::asin(($w - 1) / ($w + 1));
1391
1392 144
        $B = (1 + sin($chi) * sin($chiOrigin) + cos($chi) * cos($chiOrigin) * cos($lambda - $longitudeOrigin));
1393 144
1394 144
        $easting = $falseEasting->asMetres()->getValue() + 2 * $R * $kO * cos($chi) * sin($lambda - $longitudeOrigin) / $B;
1395 144
        $northing = $falseNorthing->asMetres()->getValue() + 2 * $R * $kO * (sin($chi) * cos($chiOrigin) - cos($chi) * sin($chiOrigin) * cos($lambda - $longitudeOrigin)) / $B;
1396
1397 144
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1398
    }
1399 144
1400 144
    /**
1401
     * Polar Stereographic (variant A)
1402 144
     * Latitude of natural origin must be either 90 degrees or -90 degrees (or equivalent in alternative angle unit).
1403
     */
1404
    public function polarStereographicVariantA(
1405
        Projected $to,
1406
        Angle $latitudeOfNaturalOrigin,
1407
        Angle $longitudeOfNaturalOrigin,
1408
        Scale $scaleFactorAtNaturalOrigin,
1409 99
        Length $falseEasting,
1410
        Length $falseNorthing
1411
    ): ProjectedPoint {
1412
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1413
        $latitude = $this->latitude->asRadians()->getValue();
1414
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1415
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1416
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1417 99
        $e = $ellipsoid->getEccentricity();
1418 99
1419 99
        if ($latitudeOrigin < 0) {
1420 99
            $t = tan(M_PI / 4 + $latitude / 2) / (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1421 99
        } else {
1422 99
            $t = tan(M_PI / 4 - $latitude / 2) * (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1423
        }
1424 99
        $rho = 2 * $a * $kO * $t / sqrt((1 + $e) ** (1 + $e) * (1 - $e) ** (1 - $e));
1425 27
1426
        $theta = $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
1427 72
        $dE = $rho * sin($theta);
1428
        $dN = $rho * cos($theta);
1429 99
1430
        $easting = $falseEasting->asMetres()->getValue() + $dE;
1431 99
        if ($latitudeOrigin < 0) {
1432 99
            $northing = $falseNorthing->asMetres()->getValue() + $dN;
1433 99
        } else {
1434
            $northing = $falseNorthing->asMetres()->getValue() - $dN;
1435 99
        }
1436 99
1437 27
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1438
    }
1439 72
1440
    /**
1441
     * Polar Stereographic (variant B).
1442 99
     */
1443
    public function polarStereographicVariantB(
1444
        Projected $to,
1445
        Angle $latitudeOfStandardParallel,
1446
        Angle $longitudeOfOrigin,
1447
        Length $falseEasting,
1448 243
        Length $falseNorthing
1449
    ): ProjectedPoint {
1450
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1451
        $latitude = $this->latitude->asRadians()->getValue();
1452
        $firstStandardParallel = $latitudeOfStandardParallel->asRadians()->getValue();
1453
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1454
        $e = $ellipsoid->getEccentricity();
1455 243
        $e2 = $ellipsoid->getEccentricitySquared();
1456 243
1457 243
        if ($firstStandardParallel < 0) {
1458 243
            $tF = tan(M_PI / 4 + $firstStandardParallel / 2) / (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1459 243
            $t = tan(M_PI / 4 + $latitude / 2) / (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1460 243
        } else {
1461
            $tF = tan(M_PI / 4 - $firstStandardParallel / 2) * (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1462 243
            $t = tan(M_PI / 4 - $latitude / 2) * (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1463 216
        }
1464 216
        $mF = cos($firstStandardParallel) / sqrt(1 - $e2 * sin($firstStandardParallel) ** 2);
1465
        $kO = $mF * sqrt((1 + $e) ** (1 + $e) * (1 - $e) ** (1 - $e)) / (2 * $tF);
1466 27
1467 27
        $rho = 2 * $a * $kO * $t / sqrt((1 + $e) ** (1 + $e) * (1 - $e) ** (1 - $e));
1468
1469 243
        $theta = $this->normaliseLongitude($this->longitude->subtract($longitudeOfOrigin))->asRadians()->getValue();
1470 243
        $dE = $rho * sin($theta);
1471
        $dN = $rho * cos($theta);
1472 243
1473
        $easting = $falseEasting->asMetres()->getValue() + $dE;
1474 243
        if ($firstStandardParallel < 0) {
1475 243
            $northing = $falseNorthing->asMetres()->getValue() + $dN;
1476 243
        } else {
1477
            $northing = $falseNorthing->asMetres()->getValue() - $dN;
1478 243
        }
1479 243
1480 216
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1481
    }
1482 27
1483
    /**
1484
     * Polar Stereographic (variant C).
1485 243
     */
1486
    public function polarStereographicVariantC(
1487
        Projected $to,
1488
        Angle $latitudeOfStandardParallel,
1489
        Angle $longitudeOfOrigin,
1490
        Length $eastingAtFalseOrigin,
1491 27
        Length $northingAtFalseOrigin
1492
    ): ProjectedPoint {
1493
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1494
        $latitude = $this->latitude->asRadians()->getValue();
1495
        $firstStandardParallel = $latitudeOfStandardParallel->asRadians()->getValue();
1496
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1497
        $e = $ellipsoid->getEccentricity();
1498 27
        $e2 = $ellipsoid->getEccentricitySquared();
1499 27
1500 27
        if ($firstStandardParallel < 0) {
1501 27
            $tF = tan(M_PI / 4 + $firstStandardParallel / 2) / (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1502 27
            $t = tan(M_PI / 4 + $latitude / 2) / (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1503 27
        } else {
1504
            $tF = tan(M_PI / 4 - $firstStandardParallel / 2) * (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1505 27
            $t = tan(M_PI / 4 - $latitude / 2) * (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1506 27
        }
1507 27
        $mF = cos($firstStandardParallel) / sqrt(1 - $e2 * sin($firstStandardParallel) ** 2);
1508
1509
        $rhoF = $a * $mF;
1510
        $rho = $rhoF * $t / $tF;
1511
1512 27
        $theta = $this->normaliseLongitude($this->longitude->subtract($longitudeOfOrigin))->asRadians()->getValue();
1513
        $dE = $rho * sin($theta);
1514 27
        $dN = $rho * cos($theta);
1515 27
1516
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $dE;
1517 27
        if ($firstStandardParallel < 0) {
1518 27
            $northing = $northingAtFalseOrigin->asMetres()->getValue() - $rhoF + $dN;
1519 27
        } else {
1520
            $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rhoF - $dN;
1521 27
        }
1522 27
1523 27
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1524
    }
1525
1526
    /**
1527
     * Popular Visualisation Pseudo Mercator
1528 27
     * Applies spherical formulas to the ellipsoid. As such does not have the properties of a true Mercator projection.
1529
     */
1530
    public function popularVisualisationPseudoMercator(
1531
        Projected $to,
1532
        Angle $latitudeOfNaturalOrigin,
0 ignored issues
show
Unused Code introduced by
The parameter $latitudeOfNaturalOrigin is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1532
        /** @scrutinizer ignore-unused */ Angle $latitudeOfNaturalOrigin,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1533
        Angle $longitudeOfNaturalOrigin,
1534
        Length $falseEasting,
1535 18
        Length $falseNorthing
1536
    ): ProjectedPoint {
1537
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1538
        $latitude = $this->latitude->asRadians()->getValue();
1539
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1540
1541
        $easting = $falseEasting->asMetres()->getValue() + $a * ($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue());
1542 18
        $northing = $falseNorthing->asMetres()->getValue() + $a * log(tan(M_PI / 4 + $latitude / 2));
1543 18
1544 18
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1545
    }
1546 18
1547 18
    /**
1548
     * Mercator (variant A)
1549 18
     * Note that in these formulas the parameter latitude of natural origin (latO) is not used. However for this
1550
     * Mercator (variant A) method the EPSG dataset includes this parameter, which must have a value of zero, for
1551
     * completeness in CRS labelling.
1552
     */
1553
    public function mercatorVariantA(
1554
        Projected $to,
1555
        Angle $latitudeOfNaturalOrigin,
0 ignored issues
show
Unused Code introduced by
The parameter $latitudeOfNaturalOrigin is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1555
        /** @scrutinizer ignore-unused */ Angle $latitudeOfNaturalOrigin,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1556
        Angle $longitudeOfNaturalOrigin,
1557
        Scale $scaleFactorAtNaturalOrigin,
1558 90
        Length $falseEasting,
1559
        Length $falseNorthing
1560
    ): ProjectedPoint {
1561
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1562
        $latitude = $this->latitude->asRadians()->getValue();
1563
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1564
1565
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1566 90
        $e = $ellipsoid->getEccentricity();
1567 90
1568 90
        $easting = $falseEasting->asMetres()->getValue() + $a * $kO * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
1569
        $northing = $falseNorthing->asMetres()->getValue() + $a * $kO * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1570 90
1571 90
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1572
    }
1573 90
1574 90
    /**
1575
     * Mercator (variant B)
1576 90
     * Used for most nautical charts.
1577
     */
1578
    public function mercatorVariantB(
1579
        Projected $to,
1580
        Angle $latitudeOf1stStandardParallel,
1581
        Angle $longitudeOfNaturalOrigin,
1582
        Length $falseEasting,
1583 36
        Length $falseNorthing
1584
    ): ProjectedPoint {
1585
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1586
        $latitude = $this->latitude->asRadians()->getValue();
1587
        $firstStandardParallel = $latitudeOf1stStandardParallel->asRadians()->getValue();
1588
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1589
        $e = $ellipsoid->getEccentricity();
1590 36
        $e2 = $ellipsoid->getEccentricitySquared();
1591 36
1592 36
        $kO = cos($firstStandardParallel) / sqrt(1 - $e2 * sin($firstStandardParallel) ** 2);
1593 36
1594 36
        $easting = $falseEasting->asMetres()->getValue() + $a * $kO * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
1595 36
        $northing = $falseNorthing->asMetres()->getValue() + $a * $kO * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1596
1597 36
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1598
    }
1599 36
1600 36
    /**
1601
     * Longitude rotation
1602 36
     * This transformation allows calculation of the longitude of a point in the target system by adding the parameter
1603
     * value to the longitude value of the point in the source system.
1604
     */
1605
    public function longitudeRotation(
1606
        Geographic2D|Geographic3D $to,
1607
        Angle $longitudeOffset
1608
    ): self {
1609
        $newLongitude = $this->longitude->add($longitudeOffset);
1610 442
1611
        return static::create($to, $this->latitude, $newLongitude, $this->height, $this->epoch);
1612
    }
1613
1614 442
    /**
1615
     * Hotine Oblique Mercator (variant A).
1616 442
     */
1617
    public function obliqueMercatorHotineVariantA(
1618
        Projected $to,
1619
        Angle $latitudeOfProjectionCentre,
1620
        Angle $longitudeOfProjectionCentre,
1621
        Angle $azimuthOfInitialLine,
1622 207
        Angle $angleFromRectifiedToSkewGrid,
1623
        Scale $scaleFactorOnInitialLine,
1624
        Length $falseEasting,
1625
        Length $falseNorthing
1626
    ): ProjectedPoint {
1627
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1628
        $latitude = $this->latitude->asRadians()->getValue();
1629
        $longitude = $this->longitude->asRadians()->getValue();
1630
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1631
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1632 207
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1633 207
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1634 207
        $gammaC = $angleFromRectifiedToSkewGrid->asRadians()->getValue();
1635 207
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1636 207
        $e = $ellipsoid->getEccentricity();
1637 207
        $e2 = $ellipsoid->getEccentricitySquared();
1638 207
1639 207
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1640 207
        $A = $a * $B * $kC * sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2);
1641 207
        $tO = tan(M_PI / 4 - $latC / 2) / ((1 - $e * sin($latC)) / (1 + $e * sin($latC))) ** ($e / 2);
1642 207
        $D = $B * sqrt((1 - $e2)) / (cos($latC) * sqrt(1 - $e2 * sin($latC) ** 2));
1643
        $DD = max(1, $D ** 2);
1644 207
        $F = $D + sqrt($DD - 1) * static::sign($latC);
1645 207
        $H = $F * ($tO) ** $B;
1646 207
        $G = ($F - 1 / $F) / 2;
1647 207
        $gammaO = self::asin(sin($alphaC) / $D);
1648 207
        $lonO = $lonC - (self::asin($G * tan($gammaO))) / $B;
1649 207
1650 207
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1651 207
        $Q = $H / $t ** $B;
1652 207
        $S = ($Q - 1 / $Q) / 2;
1653 207
        $T = ($Q + 1 / $Q) / 2;
1654
        $V = sin($B * ($longitude - $lonO));
1655 207
        $U = (-$V * cos($gammaO) + $S * sin($gammaO)) / $T;
1656 207
        $v = $A * log((1 - $U) / (1 + $U)) / (2 * $B);
1657 207
        $u = $A * atan2(($S * cos($gammaO) + $V * sin($gammaO)), cos($B * ($longitude - $lonO))) / $B;
1658 207
1659 207
        $easting = $v * cos($gammaC) + $u * sin($gammaC) + $falseEasting->asMetres()->getValue();
1660 207
        $northing = $u * cos($gammaC) - $v * sin($gammaC) + $falseNorthing->asMetres()->getValue();
1661 207
1662 207
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1663
    }
1664 207
1665 207
    /**
1666
     * Hotine Oblique Mercator (variant B).
1667 207
     */
1668
    public function obliqueMercatorHotineVariantB(
1669
        Projected $to,
1670
        Angle $latitudeOfProjectionCentre,
1671
        Angle $longitudeOfProjectionCentre,
1672
        Angle $azimuthOfInitialLine,
1673 108
        Angle $angleFromRectifiedToSkewGrid,
1674
        Scale $scaleFactorOnInitialLine,
1675
        Length $eastingAtProjectionCentre,
1676
        Length $northingAtProjectionCentre
1677
    ): ProjectedPoint {
1678
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1679
        $latitude = $this->latitude->asRadians()->getValue();
1680
        $longitude = $this->longitude->asRadians()->getValue();
1681
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1682
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1683 108
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1684 108
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1685 108
        $gammaC = $angleFromRectifiedToSkewGrid->asRadians()->getValue();
1686 108
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1687 108
        $e = $ellipsoid->getEccentricity();
1688 108
        $e2 = $ellipsoid->getEccentricitySquared();
1689 108
1690 108
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1691 108
        $A = $a * $B * $kC * sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2);
1692 108
        $tO = tan(M_PI / 4 - $latC / 2) / ((1 - $e * sin($latC)) / (1 + $e * sin($latC))) ** ($e / 2);
1693 108
        $D = $B * sqrt((1 - $e2)) / (cos($latC) * sqrt(1 - $e2 * sin($latC) ** 2));
1694
        $DD = max(1, $D ** 2);
1695 108
        $F = $D + sqrt($DD - 1) * static::sign($latC);
1696 108
        $H = $F * ($tO) ** $B;
1697 108
        $G = ($F - 1 / $F) / 2;
1698 108
        $gammaO = self::asin(sin($alphaC) / $D);
1699 108
        $lonO = $lonC - (self::asin($G * tan($gammaO))) / $B;
1700 108
        $vC = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $vC is dead and can be removed.
Loading history...
1701 108
        if ($alphaC === M_PI / 2) {
1702 108
            $uC = $A * ($lonC - $lonO);
1703 108
        } else {
1704 108
            $uC = ($A / $B) * atan2(sqrt($DD - 1), cos($alphaC)) * static::sign($latC);
1705 108
        }
1706 108
1707 45
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1708
        $Q = $H / $t ** $B;
1709 63
        $S = ($Q - 1 / $Q) / 2;
1710
        $T = ($Q + 1 / $Q) / 2;
1711
        $V = sin($B * ($longitude - $lonO));
1712 108
        $U = (-$V * cos($gammaO) + $S * sin($gammaO)) / $T;
1713 108
        $v = $A * log((1 - $U) / (1 + $U)) / (2 * $B);
1714 108
1715 108
        if ($alphaC === M_PI / 2) {
1716 108
            if ($longitude === $lonC) {
1717 108
                $u = 0;
1718 108
            } else {
1719
                $u = ($A * atan(($S * cos($gammaO) + $V * sin($gammaO)) / cos($B * ($longitude - $lonO))) / $B) - (abs($uC) * static::sign($latC) * static::sign($lonC - $longitude));
1720 108
            }
1721 45
        } else {
1722
            $u = ($A * atan2(($S * cos($gammaO) + $V * sin($gammaO)), cos($B * ($longitude - $lonO))) / $B) - (abs($uC) * static::sign($latC));
1723
        }
1724 45
1725
        $easting = $v * cos($gammaC) + $u * sin($gammaC) + $eastingAtProjectionCentre->asMetres()->getValue();
1726
        $northing = $u * cos($gammaC) - $v * sin($gammaC) + $northingAtProjectionCentre->asMetres()->getValue();
1727 63
1728
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1729
    }
1730 108
1731 108
    /**
1732
     * Laborde Oblique Mercator.
1733 108
     */
1734
    public function obliqueMercatorLaborde(
1735
        Projected $to,
1736
        Angle $latitudeOfProjectionCentre,
1737
        Angle $longitudeOfProjectionCentre,
1738
        Angle $azimuthOfInitialLine,
1739 27
        Scale $scaleFactorOnInitialLine,
1740
        Length $falseEasting,
1741
        Length $falseNorthing
1742
    ): ProjectedPoint {
1743
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1744
        $latitude = $this->latitude->asRadians()->getValue();
1745
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1746
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1747
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1748 27
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1749 27
        $e = $ellipsoid->getEccentricity();
1750 27
        $e2 = $ellipsoid->getEccentricitySquared();
1751 27
1752 27
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1753 27
        $latS = self::asin(sin($latC) / $B);
1754 27
        $R = $a * $kC * (sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2));
1755 27
        $C = log(tan(M_PI / 4 + $latS / 2)) - $B * log(tan(M_PI / 4 + $latC / 2) * ((1 - $e * sin($latC)) / (1 + $e * sin($latC))) ** ($e / 2));
1756
1757 27
        $L = $B * $this->normaliseLongitude($this->longitude->subtract($longitudeOfProjectionCentre))->asRadians()->getValue();
1758 27
        $q = $C + $B * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1759 27
        $P = 2 * atan(M_E ** $q) - M_PI / 2;
1760 27
        $U = cos($P) * cos($L) * cos($latS) + sin($P) * sin($latS);
1761
        $V = cos($P) * cos($L) * sin($latS) - sin($P) * cos($latS);
1762 27
        $W = cos($P) * sin($L);
1763 27
        $d = hypot($U, $V);
1764 27
        if ($d === 0.0) {
1765 27
            $LPrime = 0;
1766 27
            $PPrime = static::sign($W) * M_PI / 2;
1767 27
        } else {
1768 27
            $LPrime = 2 * atan($V / ($U + $d));
1769 27
            $PPrime = atan($W / $d);
1770
        }
1771
        $H = new ComplexNumber(-$LPrime, log(tan(M_PI / 4 + $PPrime / 2)));
1772
        $G = (new ComplexNumber(1 - cos(2 * $alphaC), sin(2 * $alphaC)))->divide(new ComplexNumber(12, 0));
1773 27
1774 27
        $easting = $falseEasting->asMetres()->getValue() + $R * $H->pow(3)->multiply($G)->add($H)->getImaginary();
1775
        $northing = $falseNorthing->asMetres()->getValue() + $R * $H->pow(3)->multiply($G)->add($H)->getReal();
1776 27
1777 27
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1778
    }
1779 27
1780 27
    /**
1781
     * Transverse Mercator.
1782 27
     */
1783
    public function transverseMercator(
1784
        Projected $to,
1785
        Angle $latitudeOfNaturalOrigin,
1786
        Angle $longitudeOfNaturalOrigin,
1787
        Scale $scaleFactorAtNaturalOrigin,
1788 32041
        Length $falseEasting,
1789
        Length $falseNorthing
1790
    ): ProjectedPoint {
1791
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1792
        $latitude = $this->latitude->asRadians()->getValue();
1793
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1794
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1795
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1796 32041
        $e = $ellipsoid->getEccentricity();
1797 32041
        $f = $ellipsoid->getFlattening();
1798 32041
1799 32041
        $n = $f / (2 - $f);
1800 32041
        $B = ($a / (1 + $n)) * (1 + $n ** 2 / 4 + $n ** 4 / 64);
1801 32041
1802 32041
        $h1 = $n / 2 - (2 / 3) * $n ** 2 + (5 / 16) * $n ** 3 + (41 / 180) * $n ** 4;
1803
        $h2 = (13 / 48) * $n ** 2 - (3 / 5) * $n ** 3 + (557 / 1440) * $n ** 4;
1804 32041
        $h3 = (61 / 240) * $n ** 3 - (103 / 140) * $n ** 4;
1805 32041
        $h4 = (49561 / 161280) * $n ** 4;
1806
1807 32041
        if ($latitudeOrigin === 0.0) {
0 ignored issues
show
introduced by
The condition $latitudeOrigin === 0.0 is always false.
Loading history...
1808 32041
            $mO = 0;
1809 32041
        } elseif ($latitudeOrigin === M_PI / 2) {
1810 32041
            $mO = $B * M_PI / 2;
1811
        } elseif ($latitudeOrigin === -M_PI / 2) {
1812 32041
            $mO = $B * -M_PI / 2;
1813 21906
        } else {
1814 10135
            $qO = asinh(tan($latitudeOrigin)) - ($e * atanh($e * sin($latitudeOrigin)));
1815
            $betaO = atan(sinh($qO));
1816 10135
            $xiO0 = self::asin(sin($betaO));
1817 333
            $xiO1 = $h1 * sin(2 * $xiO0);
1818
            $xiO2 = $h2 * sin(4 * $xiO0);
1819 9802
            $xiO3 = $h3 * sin(6 * $xiO0);
1820 9802
            $xiO4 = $h4 * sin(8 * $xiO0);
1821 9802
            $xiO = $xiO0 + $xiO1 + $xiO2 + $xiO3 + $xiO4;
1822 9802
            $mO = $B * $xiO;
1823 9802
        }
1824 9802
1825 9802
        $Q = asinh(tan($latitude)) - ($e * atanh($e * sin($latitude)));
1826 9802
        $beta = atan(sinh($Q));
1827 9802
        $eta0 = atanh(cos($beta) * sin($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue()));
1828
        $xi0 = self::asin(sin($beta) * cosh($eta0));
1829
        $xi1 = $h1 * sin(2 * $xi0) * cosh(2 * $eta0);
1830 32041
        $eta1 = $h1 * cos(2 * $xi0) * sinh(2 * $eta0);
1831 32041
        $xi2 = $h2 * sin(4 * $xi0) * cosh(4 * $eta0);
1832 32041
        $eta2 = $h2 * cos(4 * $xi0) * sinh(4 * $eta0);
1833 32041
        $xi3 = $h3 * sin(6 * $xi0) * cosh(6 * $eta0);
1834 32041
        $eta3 = $h3 * cos(6 * $xi0) * sinh(6 * $eta0);
1835 32041
        $xi4 = $h4 * sin(8 * $xi0) * cosh(8 * $eta0);
1836 32041
        $eta4 = $h4 * cos(8 * $xi0) * sinh(8 * $eta0);
1837 32041
        $xi = $xi0 + $xi1 + $xi2 + $xi3 + $xi4;
1838 32041
        $eta = $eta0 + $eta1 + $eta2 + $eta3 + $eta4;
1839 32041
1840 32041
        $easting = $falseEasting->asMetres()->getValue() + $kO * $B * $eta;
1841 32041
        $northing = $falseNorthing->asMetres()->getValue() + $kO * ($B * $xi - $mO);
1842 32041
1843 32041
        $height = count($to->getCoordinateSystem()->getAxes()) === 3 ? $this->height : null;
1844
1845 32041
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch, $height);
1846 32041
    }
1847
1848 32041
    /**
1849
     * Transverse Mercator Zoned Grid System
1850
     * If locations fall outwith the fixed zones the general Transverse Mercator method (code 9807) must be used for
1851
     * each zone.
1852
     */
1853
    public function transverseMercatorZonedGrid(
1854
        Projected $to,
1855
        Angle $latitudeOfNaturalOrigin,
1856 54
        Angle $initialLongitude,
1857
        Angle $zoneWidth,
1858
        Scale $scaleFactorAtNaturalOrigin,
1859
        Length $falseEasting,
1860
        Length $falseNorthing
1861
    ): ProjectedPoint {
1862
        $W = $zoneWidth->asDegrees()->getValue();
1863
        $Z = (int) ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / $W) % (int) (360 / $W) + 1;
1864
1865 54
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * $W - $W / 2));
1866 54
        $falseEasting = $falseEasting->add(new Metre($Z * 1000000));
1867
1868 54
        return $this->transverseMercator($to, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
1869 54
    }
1870
1871 54
    /**
1872
     * New Zealand Map Grid.
1873
     */
1874
    public function newZealandMapGrid(
1875
        Projected $to,
1876
        Angle $latitudeOfNaturalOrigin,
1877 36
        Angle $longitudeOfNaturalOrigin,
1878
        Length $falseEasting,
1879
        Length $falseNorthing
1880
    ): ProjectedPoint {
1881
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1882
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1883
1884 36
        $deltaLatitudeToOrigin = Angle::convert($this->latitude->subtract($latitudeOfNaturalOrigin), Angle::EPSG_ARC_SECOND)->getValue();
1885 36
        $deltaLongitudeToOrigin = $this->longitude->subtract($longitudeOfNaturalOrigin)->asRadians();
1886
1887 36
        $deltaPsi = 0;
1888 36
        $deltaPsi += 0.6399175073 * ($deltaLatitudeToOrigin * 0.00001) ** 1;
1889
        $deltaPsi += -0.1358797613 * ($deltaLatitudeToOrigin * 0.00001) ** 2;
1890 36
        $deltaPsi += 0.063294409 * ($deltaLatitudeToOrigin * 0.00001) ** 3;
1891 36
        $deltaPsi += -0.02526853 * ($deltaLatitudeToOrigin * 0.00001) ** 4;
1892 36
        $deltaPsi += 0.0117879 * ($deltaLatitudeToOrigin * 0.00001) ** 5;
1893 36
        $deltaPsi += -0.0055161 * ($deltaLatitudeToOrigin * 0.00001) ** 6;
1894 36
        $deltaPsi += 0.0026906 * ($deltaLatitudeToOrigin * 0.00001) ** 7;
1895 36
        $deltaPsi += -0.001333 * ($deltaLatitudeToOrigin * 0.00001) ** 8;
1896 36
        $deltaPsi += 0.00067 * ($deltaLatitudeToOrigin * 0.00001) ** 9;
1897 36
        $deltaPsi += -0.00034 * ($deltaLatitudeToOrigin * 0.00001) ** 10;
1898 36
1899 36
        $zeta = new ComplexNumber($deltaPsi, $deltaLongitudeToOrigin->getValue());
1900 36
1901
        $B1 = new ComplexNumber(0.7557853228, 0.0);
1902 36
        $B2 = new ComplexNumber(0.249204646, 0.003371507);
1903
        $B3 = new ComplexNumber(-0.001541739, 0.041058560);
1904 36
        $B4 = new ComplexNumber(-0.10162907, 0.01727609);
1905 36
        $B5 = new ComplexNumber(-0.26623489, -0.36249218);
1906 36
        $B6 = new ComplexNumber(-0.6870983, -1.1651967);
1907 36
        $z = new ComplexNumber(0, 0);
1908 36
        $z = $z->add($B1->multiply($zeta->pow(1)));
1909 36
        $z = $z->add($B2->multiply($zeta->pow(2)));
1910 36
        $z = $z->add($B3->multiply($zeta->pow(3)));
1911 36
        $z = $z->add($B4->multiply($zeta->pow(4)));
1912 36
        $z = $z->add($B5->multiply($zeta->pow(5)));
1913 36
        $z = $z->add($B6->multiply($zeta->pow(6)));
1914 36
1915 36
        $easting = $falseEasting->asMetres()->getValue() + $z->getImaginary() * $a;
1916 36
        $northing = $falseNorthing->asMetres()->getValue() + $z->getReal() * $a;
1917
1918 36
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1919 36
    }
1920
1921 36
    /**
1922
     * Madrid to ED50 polynomial.
1923
     */
1924
    public function madridToED50Polynomial(
1925
        Geographic2D $to,
1926
        Scale $A0,
1927 45
        Scale $A1,
1928
        Scale $A2,
1929
        Scale $A3,
1930
        Angle $B00,
1931
        Scale $B0,
1932
        Scale $B1,
1933
        Scale $B2,
1934
        Scale $B3
1935
    ): self {
1936
        $dLatitude = new ArcSecond($A0->add($A1->multiply($this->latitude->getValue()))->add($A2->multiply($this->longitude->getValue()))->add($A3->multiply($this->height ? $this->height->getValue() : 0))->getValue());
1937
        $dLongitude = $B00->add(new ArcSecond($B0->add($B1->multiply($this->latitude->getValue()))->add($B2->multiply($this->longitude->getValue()))->add($B3->multiply($this->height ? $this->height->getValue() : 0))->getValue()));
1938
1939 45
        return self::create($to, $this->latitude->add($dLatitude), $this->longitude->add($dLongitude), null, $this->epoch);
1940 45
    }
1941
1942 45
    /**
1943
     * Geographic3D to 2D conversion.
1944
     */
1945
    public function threeDToTwoD(
1946
        Geographic2D|Geographic3D $to
1947
    ): self {
1948 1724
        if ($to instanceof Geographic2D) {
1949
            return static::create($to, $this->latitude, $this->longitude, null, $this->epoch);
1950
        }
1951 1724
1952 1724
        return static::create($to, $this->latitude, $this->longitude, new Metre(0), $this->epoch);
1953
    }
1954
1955 1696
    /**
1956
     * Geographic2D offsets.
1957
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
1958
     * coordinate values of the point in the source system.
1959
     */
1960
    public function geographic2DOffsets(
1961
        Geographic2D|Geographic3D $to,
1962
        Angle $latitudeOffset,
1963 54
        Angle $longitudeOffset
1964
    ): self {
1965
        $toLatitude = $this->latitude->add($latitudeOffset);
1966
        $toLongitude = $this->longitude->add($longitudeOffset);
1967
1968 54
        return static::create($to, $toLatitude, $toLongitude, null, $this->epoch);
1969 54
    }
1970
1971 54
    /*
1972
     * Geographic2D with Height Offsets.
1973
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
1974
     * coordinate values of the point in the source system.
1975
     */
1976
    public function geographic2DWithHeightOffsets(
1977
        Compound $to,
1978
        Angle $latitudeOffset,
1979 918
        Angle $longitudeOffset,
1980
        Length $geoidUndulation
1981
    ): CompoundPoint {
1982
        $toLatitude = $this->latitude->add($latitudeOffset);
1983
        $toLongitude = $this->longitude->add($longitudeOffset);
1984
        $toHeight = $this->height->add($geoidUndulation);
0 ignored issues
show
Bug introduced by
The method add() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1984
        /** @scrutinizer ignore-call */ 
1985
        $toHeight = $this->height->add($geoidUndulation);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1985 918
1986 918
        $horizontal = static::create($to->getHorizontal(), $toLatitude, $toLongitude, null, $this->epoch);
0 ignored issues
show
Bug introduced by
It seems like $to->getHorizontal() can also be of type PHPCoord\CoordinateReferenceSystem\Geocentric; however, parameter $crs of PHPCoord\GeographicPoint::create() does only seem to accept PHPCoord\CoordinateRefer...enceSystem\Geographic3D, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1986
        $horizontal = static::create(/** @scrutinizer ignore-type */ $to->getHorizontal(), $toLatitude, $toLongitude, null, $this->epoch);
Loading history...
1987 918
        $vertical = VerticalPoint::create($to->getVertical(), $toHeight, $this->epoch);
0 ignored issues
show
Bug introduced by
It seems like $toHeight can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1987
        $vertical = VerticalPoint::create($to->getVertical(), /** @scrutinizer ignore-type */ $toHeight, $this->epoch);
Loading history...
1988
1989 918
        return CompoundPoint::create($to, $horizontal, $vertical, $this->epoch);
1990 918
    }
1991
1992 918
    /**
1993
     * General polynomial.
1994
     * @param Coefficient[] $powerCoefficients
1995
     */
1996
    public function generalPolynomial(
1997
        Geographic2D|Geographic3D $to,
1998
        Angle $ordinate1OfEvaluationPointInSourceCRS,
1999 36
        Angle $ordinate2OfEvaluationPointInSourceCRS,
2000
        Angle $ordinate1OfEvaluationPointInTargetCRS,
2001
        Angle $ordinate2OfEvaluationPointInTargetCRS,
2002
        Scale $scalingFactorForSourceCRSCoordDifferences,
2003
        Scale $scalingFactorForTargetCRSCoordDifferences,
2004
        Scale $A0,
2005
        Scale $B0,
2006
        array $powerCoefficients
2007
    ): self {
2008
        $xs = $this->latitude->getValue();
2009
        $ys = $this->longitude->getValue();
2010
2011 36
        $t = $this->generalPolynomialUnitless(
2012 36
            $xs,
2013
            $ys,
2014 36
            $ordinate1OfEvaluationPointInSourceCRS,
2015 36
            $ordinate2OfEvaluationPointInSourceCRS,
2016
            $ordinate1OfEvaluationPointInTargetCRS,
2017
            $ordinate2OfEvaluationPointInTargetCRS,
2018
            $scalingFactorForSourceCRSCoordDifferences,
2019
            $scalingFactorForTargetCRSCoordDifferences,
2020
            $A0,
2021
            $B0,
2022
            $powerCoefficients
2023
        );
2024
2025
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2026
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2027
2028 36
        return static::create(
2029 36
            $to,
2030
            Angle::makeUnit($t['xt'], $xtUnit),
2031 36
            Angle::makeUnit($t['yt'], $ytUnit),
2032 36
            $this->height,
2033 36
            $this->epoch
2034 36
        );
2035
    }
2036 36
2037
    /**
2038
     * Reversible polynomial.
2039
     * @param Coefficient[] $powerCoefficients
2040
     */
2041
    public function reversiblePolynomial(
2042
        Geographic2D|Geographic3D $to,
2043
        Angle $ordinate1OfEvaluationPoint,
2044 72
        Angle $ordinate2OfEvaluationPoint,
2045
        Scale $scalingFactorForCoordDifferences,
2046
        Scale $A0,
2047
        Scale $B0,
2048
        $powerCoefficients
2049
    ): self {
2050
        $xs = $this->latitude->getValue();
2051
        $ys = $this->longitude->getValue();
2052
2053 72
        $t = $this->reversiblePolynomialUnitless(
2054 72
            $xs,
2055
            $ys,
2056 72
            $ordinate1OfEvaluationPoint,
2057 72
            $ordinate2OfEvaluationPoint,
2058
            $scalingFactorForCoordDifferences,
2059
            $A0,
2060
            $B0,
2061
            $powerCoefficients
2062
        );
2063
2064
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2065
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2066
2067 72
        return static::create(
2068 72
            $to,
2069
            Angle::makeUnit($t['xt'], $xtUnit),
2070 72
            Angle::makeUnit($t['yt'], $ytUnit),
2071 72
            $this->height,
2072 72
            $this->epoch
2073 72
        );
2074
    }
2075 72
2076
    /**
2077
     * Axis Order Reversal.
2078
     */
2079
    public function axisReversal(
2080
        Geographic2D|Geographic3D $to
2081
    ): self {
2082 144
        // axes are read in from the CRS, this is a book-keeping adjustment only
2083
        return static::create($to, $this->latitude, $this->longitude, $this->height, $this->epoch);
2084
    }
2085
2086 144
    /**
2087
     * Ordnance Survey National Transformation
2088
     * Geodetic transformation between ETRS89 (or WGS 84) and OSGB36 / National Grid.  Uses ETRS89 / National Grid as
2089
     * an intermediate coordinate system for bi-linear interpolation of gridded grid coordinate differences.
2090
     */
2091
    public function OSTN15(
2092
        Projected $to,
0 ignored issues
show
Unused Code introduced by
The parameter $to is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

2092
        /** @scrutinizer ignore-unused */ Projected $to,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2093
        OSTNOSGM15Grid $eastingAndNorthingDifferenceFile
2094 3
    ): ProjectedPoint {
2095
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2096
        $etrs89NationalGrid = new Projected(
2097
            'ETRS89 / National Grid',
2098 3
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2099 3
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2100 3
            $osgb36NationalGrid->getBoundingArea()
2101 3
        );
2102 3
2103 3
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2104
2105
        return $eastingAndNorthingDifferenceFile->applyForwardHorizontalAdjustment($projected);
2106 3
    }
2107
2108 3
    /**
2109
     * Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB).
2110
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2111
     * coordinate differences.
2112
     */
2113
    public function geographic3DTo2DPlusGravityHeightOSGM15(
2114
        Compound $to,
2115
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
2116 8
    ): CompoundPoint {
2117
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2118
        $etrs89NationalGrid = new Projected(
2119
            'ETRS89 / National Grid',
2120 8
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2121 8
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2122 8
            $osgb36NationalGrid->getBoundingArea()
2123 8
        );
2124 8
2125 8
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2126
2127
        $horizontalPoint = self::create(
2128 8
            $to->getHorizontal(),
0 ignored issues
show
Bug introduced by
It seems like $to->getHorizontal() can also be of type PHPCoord\CoordinateReferenceSystem\Geocentric; however, parameter $crs of PHPCoord\GeographicPoint::create() does only seem to accept PHPCoord\CoordinateRefer...enceSystem\Geographic3D, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2128
            /** @scrutinizer ignore-type */ $to->getHorizontal(),
Loading history...
2129
            $this->latitude,
2130 8
            $this->longitude,
2131 8
            null,
2132 8
            $this->getCoordinateEpoch()
2133 8
        );
2134 8
2135 8
        $verticalPoint = VerticalPoint::create(
2136
            $to->getVertical(),
2137
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...Adjustment($projected)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2137
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
Loading history...
2138 8
            $this->getCoordinateEpoch()
2139 8
        );
2140 8
2141 8
        return CompoundPoint::create(
2142
            $to,
2143
            $horizontalPoint,
2144 8
            $verticalPoint,
2145 8
            $this->getCoordinateEpoch()
2146 8
        );
2147 8
    }
2148 8
2149
    /**
2150
     * Geographic3D to GravityRelatedHeight (OSGM-GB).
2151
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2152
     * coordinate differences.
2153
     */
2154
    public function geographic3DToGravityHeightOSGM15(
2155
        Vertical $to,
2156
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
2157 8
    ): VerticalPoint {
2158
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2159
        $etrs89NationalGrid = new Projected(
2160
            'ETRS89 / National Grid',
2161 8
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2162 8
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2163 8
            $osgb36NationalGrid->getBoundingArea()
2164 8
        );
2165 8
2166 8
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2167
2168
        return VerticalPoint::create(
2169 8
            $to,
2170
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...Adjustment($projected)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2170
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
Loading history...
2171 8
            $this->getCoordinateEpoch()
2172 8
        );
2173 8
    }
2174 8
2175
    /**
2176
     * Geog3D to Geog2D+GravityRelatedHeight.
2177
     */
2178
    public function geographic3DTo2DPlusGravityHeightFromGrid(
2179
        Compound $to,
2180
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2181 10
    ): CompoundPoint {
2182
        $horizontalPoint = self::create(
2183
            $to->getHorizontal(),
0 ignored issues
show
Bug introduced by
It seems like $to->getHorizontal() can also be of type PHPCoord\CoordinateReferenceSystem\Geocentric; however, parameter $crs of PHPCoord\GeographicPoint::create() does only seem to accept PHPCoord\CoordinateRefer...enceSystem\Geographic3D, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2183
            /** @scrutinizer ignore-type */ $to->getHorizontal(),
Loading history...
2184
            $this->latitude,
2185 10
            $this->longitude,
2186 10
            null,
2187 10
            $this->getCoordinateEpoch()
2188 10
        );
2189 10
2190 10
        $verticalPoint = VerticalPoint::create(
2191
            $to->getVertical(),
2192
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...eightAdjustment($this)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2192
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
Loading history...
2193 10
            $this->getCoordinateEpoch()
2194 10
        );
2195 10
2196 10
        return CompoundPoint::create(
2197
            $to,
2198
            $horizontalPoint,
2199 10
            $verticalPoint,
2200 10
            $this->getCoordinateEpoch()
2201 10
        );
2202 10
    }
2203 10
2204
    /**
2205
     * Geographic3D to GravityRelatedHeight.
2206
     */
2207
    public function geographic3DToGravityHeightFromGrid(
2208
        Vertical $to,
2209
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2210 11
    ): VerticalPoint {
2211
        return VerticalPoint::create(
2212
            $to,
2213
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...eightAdjustment($this)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2213
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
Loading history...
2214 11
            $this->getCoordinateEpoch()
2215 11
        );
2216 11
    }
2217 11
2218
    /**
2219
     * NADCON5.
2220
     * @internal just a wrapper
2221
     */
2222
    public function offsetsFromGridNADCON5(
2223
        Geographic2D|Geographic3D $to,
2224
        NADCON5Grid $latitudeDifferenceFile,
2225
        NADCON5Grid $longitudeDifferenceFile,
2226 36
        ?NADCON5Grid $ellipsoidalHeightDifferenceFile,
2227
        bool $inReverse
2228
    ): self {
2229
        $aggregation = new NADCON5Grids($longitudeDifferenceFile, $latitudeDifferenceFile, $ellipsoidalHeightDifferenceFile);
2230
2231
        return $this->offsetsFromGrid($to, $aggregation, $inReverse);
2232
    }
2233
2234
    /**
2235
     * Geographic offsets from grid.
2236
     */
2237
    public function offsetsFromGrid(
2238 36
        Geographic2D|Geographic3D $to,
2239 32
        GeographicGrid $offsetsFile,
2240 32
        bool $inReverse
2241 32
    ): self {
2242
        if (!$inReverse) {
2243 32
            return $offsetsFile->applyForwardAdjustment($this, $to);
2244
        }
2245
2246 32
        return $offsetsFile->applyReverseAdjustment($this, $to);
2247
    }
2248
2249 32
    public function asGeographicValue(): GeographicValue
2250 32
    {
2251 32
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2252 32
    }
2253 32
2254 32
    public function asUTMPoint(): UTMPoint
2255
    {
2256 32
        $hemisphere = $this->getLatitude()->asDegrees()->getValue() >= 0 ? UTMPoint::HEMISPHERE_NORTH : UTMPoint::HEMISPHERE_SOUTH;
2257
2258
        $initialLongitude = new Degree(-180);
2259
        $zone = (int) ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / 6) % (360 / 6) + 1;
2260
2261
        if ($hemisphere === UTMPoint::HEMISPHERE_NORTH) {
2262
            $derivingConversion = 'urn:ogc:def:coordinateOperation:EPSG::' . ($zone + 16000);
2263
        } else {
2264 91
            $derivingConversion = 'urn:ogc:def:coordinateOperation:EPSG::' . ($zone + 16100);
2265
        }
2266
2267
        $srid = 'urn:ogc:def:crs,' . str_replace('urn:ogc:def:', '', $this->crs->getSRID()) . ',' . str_replace('urn:ogc:def:', '', Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M) . ',' . str_replace('urn:ogc:def:', '', $derivingConversion);
2268
2269 91
        $projectedCRS = new Projected(
2270 89
            $srid,
2271
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2272
            $this->crs->getDatum(),
2273 88
            BoundingArea::createWorld() // this is a dummy CRS for the transform only, details don't matter (UTMPoint creates own)
2274
        );
2275
2276
        $asProjected = $this->performOperation($derivingConversion, $projectedCRS, false);
2277
2278
        return new UTMPoint($this->crs, $asProjected->getEasting(), $asProjected->getNorthing(), $zone, $hemisphere, $this->epoch);
0 ignored issues
show
Bug introduced by
It seems like $this->crs can also be of type PHPCoord\CoordinateReferenceSystem\Geographic3D; however, parameter $crs of PHPCoord\UTMPoint::__construct() does only seem to accept PHPCoord\CoordinateReferenceSystem\Geographic2D, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2278
        return new UTMPoint(/** @scrutinizer ignore-type */ $this->crs, $asProjected->getEasting(), $asProjected->getNorthing(), $zone, $hemisphere, $this->epoch);
Loading history...
Bug introduced by
The method getEasting() does not exist on PHPCoord\Point. It seems like you code against a sub-type of PHPCoord\Point such as PHPCoord\ProjectedPoint. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2278
        return new UTMPoint($this->crs, $asProjected->/** @scrutinizer ignore-call */ getEasting(), $asProjected->getNorthing(), $zone, $hemisphere, $this->epoch);
Loading history...
Bug introduced by
The method getNorthing() does not exist on PHPCoord\Point. It seems like you code against a sub-type of PHPCoord\Point such as PHPCoord\ProjectedPoint. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2278
        return new UTMPoint($this->crs, $asProjected->getEasting(), $asProjected->/** @scrutinizer ignore-call */ getNorthing(), $zone, $hemisphere, $this->epoch);
Loading history...
2279 5
    }
2280
}
2281