GeographicPoint::coordinateFrameRotation()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1

Importance

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

1563
        /** @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...
1564
        Angle $longitudeOfNaturalOrigin,
1565
        Length $falseEasting,
1566
        Length $falseNorthing
1567 9
    ): ProjectedPoint {
1568 9
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1569 9
        $latitude = $this->latitude->asRadians()->getValue();
1570
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1571 9
1572 9
        $easting = $falseEasting->asMetres()->getValue() + $a * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
1573
        $northing = $falseNorthing->asMetres()->getValue() + $a * log(tan(M_PI / 4 + $latitude / 2));
1574 9
1575
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1576
    }
1577
1578
    /**
1579
     * Mercator (variant A)
1580
     * Note that in these formulas the parameter latitude of natural origin (latO) is not used. However for this
1581
     * Mercator (variant A) method the EPSG dataset includes this parameter, which must have a value of zero, for
1582
     * completeness in CRS labelling.
1583 18
     */
1584
    public function mercatorVariantA(
1585
        Projected $to,
1586
        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

1586
        /** @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...
1587
        Angle $longitudeOfNaturalOrigin,
1588
        Scale $scaleFactorAtNaturalOrigin,
1589
        Length $falseEasting,
1590
        Length $falseNorthing
1591 18
    ): ProjectedPoint {
1592 18
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1593 18
        $latitude = $this->latitude->asRadians()->getValue();
1594
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1595 18
1596 18
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1597
        $e = $ellipsoid->getEccentricity();
1598 18
1599 18
        $easting = $falseEasting->asMetres()->getValue() + $a * $kO * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
1600
        $northing = $falseNorthing->asMetres()->getValue() + $a * $kO * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1601 18
1602
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1603
    }
1604
1605
    /**
1606
     * Mercator (variant B)
1607
     * Used for most nautical charts.
1608 9
     */
1609
    public function mercatorVariantB(
1610
        Projected $to,
1611
        Angle $latitudeOf1stStandardParallel,
1612
        Angle $longitudeOfNaturalOrigin,
1613
        Length $falseEasting,
1614
        Length $falseNorthing
1615 9
    ): ProjectedPoint {
1616 9
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1617 9
        $latitude = $this->latitude->asRadians()->getValue();
1618 9
        $firstStandardParallel = $latitudeOf1stStandardParallel->asRadians()->getValue();
1619 9
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1620 9
        $e = $ellipsoid->getEccentricity();
1621
        $e2 = $ellipsoid->getEccentricitySquared();
1622 9
1623
        $kO = cos($firstStandardParallel) / sqrt(1 - $e2 * sin($firstStandardParallel) ** 2);
1624 9
1625 9
        $easting = $falseEasting->asMetres()->getValue() + $a * $kO * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
1626
        $northing = $falseNorthing->asMetres()->getValue() + $a * $kO * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1627 9
1628
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1629
    }
1630
1631
    /**
1632
     * Longitude rotation
1633
     * This transformation allows calculation of the longitude of a point in the target system by adding the parameter
1634
     * value to the longitude value of the point in the source system.
1635 27
     */
1636
    public function longitudeRotation(
1637
        Geographic2D|Geographic3D $to,
1638
        Angle $longitudeOffset
1639 27
    ): self {
1640
        $newLongitude = $this->longitude->add($longitudeOffset);
1641 27
1642
        return static::create($to, $this->latitude, $newLongitude, $this->height, $this->epoch);
1643
    }
1644
1645
    /**
1646
     * Hotine Oblique Mercator (variant A).
1647 9
     */
1648
    public function obliqueMercatorHotineVariantA(
1649
        Projected $to,
1650
        Angle $latitudeOfProjectionCentre,
1651
        Angle $longitudeOfProjectionCentre,
1652
        Angle $azimuthAtProjectionCentre,
1653
        Angle $angleFromRectifiedToSkewGrid,
1654
        Scale $scaleFactorAtProjectionCentre,
1655
        Length $falseEasting,
1656
        Length $falseNorthing
1657 9
    ): ProjectedPoint {
1658 9
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1659 9
        $latitude = $this->latitude->asRadians()->getValue();
1660 9
        $longitude = $this->longitude->asRadians()->getValue();
1661 9
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1662 9
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1663 9
        $alphaC = $azimuthAtProjectionCentre->asRadians()->getValue();
1664 9
        $kC = $scaleFactorAtProjectionCentre->asUnity()->getValue();
1665 9
        $gammaC = $angleFromRectifiedToSkewGrid->asRadians()->getValue();
1666 9
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1667 9
        $e = $ellipsoid->getEccentricity();
1668
        $e2 = $ellipsoid->getEccentricitySquared();
1669 9
1670 9
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1671 9
        $A = $a * $B * $kC * sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2);
1672 9
        $tO = tan(M_PI / 4 - $latC / 2) / ((1 - $e * sin($latC)) / (1 + $e * sin($latC))) ** ($e / 2);
1673 9
        $D = $B * sqrt(1 - $e2) / (cos($latC) * sqrt(1 - $e2 * sin($latC) ** 2));
1674 9
        $DD = max(1, $D ** 2);
1675 9
        $F = $D + sqrt($DD - 1) * static::sign($latC);
1676 9
        $H = $F * $tO ** $B;
1677 9
        $G = ($F - 1 / $F) / 2;
1678 9
        $gammaO = self::asin(sin($alphaC) / $D);
1679
        $lonO = $lonC - self::asin($G * tan($gammaO)) / $B;
1680 9
1681 9
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1682 9
        $Q = $H / $t ** $B;
1683 9
        $S = ($Q - 1 / $Q) / 2;
1684 9
        $T = ($Q + 1 / $Q) / 2;
1685 9
        $V = sin($B * ($longitude - $lonO));
1686 9
        $U = (-$V * cos($gammaO) + $S * sin($gammaO)) / $T;
1687 9
        $v = $A * log((1 - $U) / (1 + $U)) / (2 * $B);
1688
        $u = $A * atan2($S * cos($gammaO) + $V * sin($gammaO), cos($B * ($longitude - $lonO))) / $B;
1689 9
1690 9
        $easting = $v * cos($gammaC) + $u * sin($gammaC) + $falseEasting->asMetres()->getValue();
1691
        $northing = $u * cos($gammaC) - $v * sin($gammaC) + $falseNorthing->asMetres()->getValue();
1692 9
1693
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1694
    }
1695
1696
    /**
1697
     * Hotine Oblique Mercator (variant B).
1698 9
     */
1699
    public function obliqueMercatorHotineVariantB(
1700
        Projected $to,
1701
        Angle $latitudeOfProjectionCentre,
1702
        Angle $longitudeOfProjectionCentre,
1703
        Angle $azimuthAtProjectionCentre,
1704
        Angle $angleFromRectifiedToSkewGrid,
1705
        Scale $scaleFactorAtProjectionCentre,
1706
        Length $eastingAtProjectionCentre,
1707
        Length $northingAtProjectionCentre
1708 9
    ): ProjectedPoint {
1709 9
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1710 9
        $latitude = $this->latitude->asRadians()->getValue();
1711 9
        $longitude = $this->longitude->asRadians()->getValue();
1712 9
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1713 9
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1714 9
        $alphaC = $azimuthAtProjectionCentre->asRadians()->getValue();
1715 9
        $kC = $scaleFactorAtProjectionCentre->asUnity()->getValue();
1716 9
        $gammaC = $angleFromRectifiedToSkewGrid->asRadians()->getValue();
1717 9
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1718 9
        $e = $ellipsoid->getEccentricity();
1719
        $e2 = $ellipsoid->getEccentricitySquared();
1720 9
1721 9
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1722 9
        $A = $a * $B * $kC * sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2);
1723 9
        $tO = tan(M_PI / 4 - $latC / 2) / ((1 - $e * sin($latC)) / (1 + $e * sin($latC))) ** ($e / 2);
1724 9
        $D = $B * sqrt(1 - $e2) / (cos($latC) * sqrt(1 - $e2 * sin($latC) ** 2));
1725 9
        $F = $D + sqrt(max($D ** 2, 1) - 1) * static::sign($latC);
1726 9
        $H = $F * $tO ** $B;
1727 9
        $G = ($F - 1 / $F) / 2;
1728 9
        $gammaO = self::asin(sin($alphaC) / $D);
1729 9
        $lonO = $lonC - self::asin($G * tan($gammaO)) / $B;
1730 9
        $vC = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $vC is dead and can be removed.
Loading history...
1731
        if ($alphaC === M_PI / 2) {
1732
            $uC = $A * ($lonC - $lonO);
1733 9
        } else {
1734
            $uC = ($A / $B) * atan2(sqrt(max($D ** 2, 1) - 1), cos($alphaC)) * static::sign($latC);
1735
        }
1736 9
1737 9
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1738 9
        $Q = $H / $t ** $B;
1739 9
        $S = ($Q - 1 / $Q) / 2;
1740 9
        $T = ($Q + 1 / $Q) / 2;
1741 9
        $V = sin($B * ($longitude - $lonO));
1742 9
        $U = (-$V * cos($gammaO) + $S * sin($gammaO)) / $T;
1743 9
        $v = $A * log((1 - $U) / (1 + $U)) / (2 * $B);
1744
        $u = ($A * atan2($S * cos($gammaO) + $V * sin($gammaO), cos($B * ($longitude - $lonO))) / $B) - (abs($uC) * static::sign($latC));
1745 9
1746 9
        $easting = $v * cos($gammaC) + $u * sin($gammaC) + $eastingAtProjectionCentre->asMetres()->getValue();
1747
        $northing = $u * cos($gammaC) - $v * sin($gammaC) + $northingAtProjectionCentre->asMetres()->getValue();
1748 9
1749
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1750
    }
1751
1752
    /**
1753
     * Laborde Oblique Mercator.
1754 9
     */
1755
    public function obliqueMercatorLaborde(
1756
        Projected $to,
1757
        Angle $latitudeOfProjectionCentre,
1758
        Angle $longitudeOfProjectionCentre,
1759
        Angle $azimuthAtProjectionCentre,
1760
        Scale $scaleFactorAtProjectionCentre,
1761
        Length $falseEasting,
1762
        Length $falseNorthing
1763 9
    ): ProjectedPoint {
1764 9
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1765 9
        $latitude = $this->latitude->asRadians()->getValue();
1766 9
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1767 9
        $alphaC = $azimuthAtProjectionCentre->asRadians()->getValue();
1768 9
        $kC = $scaleFactorAtProjectionCentre->asUnity()->getValue();
1769 9
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1770 9
        $e = $ellipsoid->getEccentricity();
1771
        $e2 = $ellipsoid->getEccentricitySquared();
1772 9
1773 9
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1774 9
        $latS = self::asin(sin($latC) / $B);
1775 9
        $R = $a * $kC * (sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2));
1776
        $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));
1777 9
1778 9
        $L = $B * $this->normaliseLongitude($this->longitude->subtract($longitudeOfProjectionCentre))->asRadians()->getValue();
1779 9
        $q = $C + $B * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1780 9
        $P = 2 * atan(M_E ** $q) - M_PI / 2;
1781 9
        $U = cos($P) * cos($L) * cos($latS) + sin($P) * sin($latS);
1782 9
        $V = cos($P) * cos($L) * sin($latS) - sin($P) * cos($latS);
1783 9
        $W = cos($P) * sin($L);
1784 9
        $d = hypot($U, $V);
1785
        if ($d === 0.0) {
1786
            $LPrime = 0;
1787
            $PPrime = static::sign($W) * M_PI / 2;
1788 9
        } else {
1789 9
            $LPrime = 2 * atan($V / ($U + $d));
1790
            $PPrime = atan($W / $d);
1791 9
        }
1792 9
        $H = new ComplexNumber(-$LPrime, log(tan(M_PI / 4 + $PPrime / 2)));
1793
        $G = (new ComplexNumber(1 - cos(2 * $alphaC), sin(2 * $alphaC)))->divide(new ComplexNumber(12, 0));
1794 9
1795 9
        $easting = $falseEasting->asMetres()->getValue() + $R * $H->pow(3)->multiply($G)->add($H)->getImaginary();
1796
        $northing = $falseNorthing->asMetres()->getValue() + $R * $H->pow(3)->multiply($G)->add($H)->getReal();
1797 9
1798
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1799
    }
1800
1801
    /**
1802
     * Transverse Mercator.
1803 144
     */
1804
    public function transverseMercator(
1805
        Projected $to,
1806
        Angle $latitudeOfNaturalOrigin,
1807
        Angle $longitudeOfNaturalOrigin,
1808
        Scale $scaleFactorAtNaturalOrigin,
1809
        Length $falseEasting,
1810
        Length $falseNorthing
1811 144
    ): ProjectedPoint {
1812 144
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1813 144
        $latitude = $this->latitude->asRadians()->getValue();
1814 144
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1815 144
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1816 144
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1817 144
        $e = $ellipsoid->getEccentricity();
1818
        $f = $ellipsoid->getFlattening();
1819 144
1820 144
        $n = $f / (2 - $f);
1821
        $B = ($a / (1 + $n)) * (1 + $n ** 2 / 4 + $n ** 4 / 64 + $n ** 6 / 256 + (25 / 16384) * $n ** 8);
1822 144
1823 144
        $h1 = $n / 2 - (2 / 3) * $n ** 2 + (5 / 16) * $n ** 3 + (41 / 180) * $n ** 4 - (127 / 288) * $n ** 5 + (7891 / 37800) * $n ** 6 + (72161 / 387072) * $n ** 7 - (18975107 / 50803200) * $n ** 8;
1824 144
        $h2 = (13 / 48) * $n ** 2 - (3 / 5) * $n ** 3 + (557 / 1440) * $n ** 4 + (281 / 630) * $n ** 5 - (1983433 / 1935360) * $n ** 6 + (13769 / 28800) * $n ** 7 + (148003883 / 174182400) * $n ** 8;
1825 144
        $h3 = (61 / 240) * $n ** 3 - (103 / 140) * $n ** 4 + (15061 / 26880) * $n ** 5 + (167603 / 181440) * $n ** 6 - (67102379 / 29030400) * $n ** 7 + (79682431 / 79833600) * $n ** 8;
1826 144
        $h4 = (49561 / 161280) * $n ** 4 - (179 / 168) * $n ** 5 + (6601661 / 7257600) * $n ** 6 + (97445 / 49896) * $n ** 7 - (40176129013 / 7664025600) * $n ** 8;
1827 144
        $h5 = (34729 / 80640) * $n ** 5 - (3418889 / 1995840) * $n ** 6 + (14644087 / 9123840) * $n ** 7 + (2605413599 / 622702080) * $n ** 8;
1828 144
        $h6 = (212378941 / 319334400) * $n ** 6 - (30705481 / 10378368) * $n ** 7 + (175214326799 / 58118860800) * $n ** 8;
1829 144
        $h7 = (1522256789 / 1383782400) * $n ** 7 - (16759934899 / 3113510400) * $n ** 8;
1830
        $h8 = (1424729850961 / 743921418240) * $n ** 8;
1831 144
1832 81
        if ($latitudeOrigin === 0.0) {
0 ignored issues
show
introduced by
The condition $latitudeOrigin === 0.0 is always false.
Loading history...
1833 63
            $mO = 0;
1834
        } elseif ($latitudeOrigin === M_PI / 2) {
1835 63
            $mO = $B * M_PI / 2;
1836
        } elseif ($latitudeOrigin === -M_PI / 2) {
1837
            $mO = $B * -M_PI / 2;
1838 63
        } else {
1839 63
            $qO = asinh(tan($latitudeOrigin)) - ($e * atanh($e * sin($latitudeOrigin)));
1840 63
            $betaO = atan(sinh($qO));
1841 63
            $xiO0 = self::asin(sin($betaO));
1842 63
            $xiO1 = $h1 * sin(2 * $xiO0);
1843 63
            $xiO2 = $h2 * sin(4 * $xiO0);
1844 63
            $xiO3 = $h3 * sin(6 * $xiO0);
1845 63
            $xiO4 = $h4 * sin(8 * $xiO0);
1846 63
            $xiO5 = $h5 * sin(10 * $xiO0);
1847 63
            $xiO6 = $h6 * sin(12 * $xiO0);
1848 63
            $xiO7 = $h7 * sin(14 * $xiO0);
1849 63
            $xiO8 = $h8 * sin(16 * $xiO0);
1850 63
            $xiO = $xiO0 + $xiO1 + $xiO2 + $xiO3 + $xiO4 + $xiO5 + $xiO6 + $xiO7 + $xiO8;
1851
            $mO = $B * $xiO;
1852
        }
1853 144
1854 144
        $Q = asinh(tan($latitude)) - ($e * atanh($e * sin($latitude)));
1855 144
        $beta = atan(sinh($Q));
1856 144
        $eta0 = atanh(cos($beta) * sin($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue()));
1857 144
        $xi0 = self::asin(sin($beta) * cosh($eta0));
1858 144
        $xi1 = $h1 * sin(2 * $xi0) * cosh(2 * $eta0);
1859 144
        $eta1 = $h1 * cos(2 * $xi0) * sinh(2 * $eta0);
1860 144
        $xi2 = $h2 * sin(4 * $xi0) * cosh(4 * $eta0);
1861 144
        $eta2 = $h2 * cos(4 * $xi0) * sinh(4 * $eta0);
1862 144
        $xi3 = $h3 * sin(6 * $xi0) * cosh(6 * $eta0);
1863 144
        $eta3 = $h3 * cos(6 * $xi0) * sinh(6 * $eta0);
1864 144
        $xi4 = $h4 * sin(8 * $xi0) * cosh(8 * $eta0);
1865 144
        $eta4 = $h4 * cos(8 * $xi0) * sinh(8 * $eta0);
1866 144
        $xi5 = $h5 * sin(10 * $xi0) * cosh(10 * $eta0);
1867 144
        $eta5 = $h5 * cos(10 * $xi0) * sinh(10 * $eta0);
1868 144
        $xi6 = $h6 * sin(12 * $xi0) * cosh(12 * $eta0);
1869 144
        $eta6 = $h6 * cos(12 * $xi0) * sinh(12 * $eta0);
1870 144
        $xi7 = $h7 * sin(14 * $xi0) * cosh(14 * $eta0);
1871 144
        $eta7 = $h7 * cos(14 * $xi0) * sinh(14 * $eta0);
1872 144
        $xi8 = $h8 * sin(16 * $xi0) * cosh(16 * $eta0);
1873 144
        $eta8 = $h8 * cos(16 * $xi0) * sinh(16 * $eta0);
1874 144
        $xi = $xi0 + $xi1 + $xi2 + $xi3 + $xi4 + $xi5 + $xi6 + $xi7 + $xi8;
1875
        $eta = $eta0 + $eta1 + $eta2 + $eta3 + $eta4 + $eta5 + $eta6 + $eta7 + $eta8;
1876 144
1877 144
        $easting = $falseEasting->asMetres()->getValue() + $kO * $B * $eta;
1878
        $northing = $falseNorthing->asMetres()->getValue() + $kO * ($B * $xi - $mO);
1879 144
1880
        $height = count($to->getCoordinateSystem()->getAxes()) === 3 ? $this->height : null;
1881 144
1882
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch, $height);
1883
    }
1884
1885
    /**
1886
     * Transverse Mercator Zoned Grid System
1887
     * If locations fall outwith the fixed zones the general Transverse Mercator method (code 9807) must be used for
1888
     * each zone.
1889 36
     */
1890
    public function transverseMercatorZonedGrid(
1891
        Projected $to,
1892
        Angle $latitudeOfNaturalOrigin,
1893
        Angle $initialLongitude,
1894
        Angle $zoneWidth,
1895
        Scale $scaleFactorAtNaturalOrigin,
1896
        Length $falseEasting,
1897
        Length $falseNorthing
1898 36
    ): ProjectedPoint {
1899 36
        $W = $zoneWidth->asDegrees()->getValue();
1900
        $Z = (int) ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / $W) % (int) (360 / $W) + 1;
1901 36
1902 36
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * $W - $W / 2));
1903
        $falseEasting = $falseEasting->add(new Metre($Z * 1000000));
1904 36
1905
        return $this->transverseMercator($to, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
1906
    }
1907
1908
    /**
1909
     * New Zealand Map Grid.
1910 27
     */
1911
    public function newZealandMapGrid(
1912
        Projected $to,
1913
        Angle $latitudeOfNaturalOrigin,
1914
        Angle $longitudeOfNaturalOrigin,
1915
        Length $falseEasting,
1916
        Length $falseNorthing
1917 27
    ): ProjectedPoint {
1918 27
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1919
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1920 27
1921 27
        $deltaLatitudeToOrigin = Angle::convert($this->latitude->subtract($latitudeOfNaturalOrigin), Angle::EPSG_ARC_SECOND)->getValue();
1922
        $deltaLongitudeToOrigin = $this->longitude->subtract($longitudeOfNaturalOrigin)->asRadians();
1923 27
1924 27
        $deltaPsi = 0;
1925 27
        $deltaPsi += 0.6399175073 * ($deltaLatitudeToOrigin * 0.00001) ** 1;
1926 27
        $deltaPsi += -0.1358797613 * ($deltaLatitudeToOrigin * 0.00001) ** 2;
1927 27
        $deltaPsi += 0.063294409 * ($deltaLatitudeToOrigin * 0.00001) ** 3;
1928 27
        $deltaPsi += -0.02526853 * ($deltaLatitudeToOrigin * 0.00001) ** 4;
1929 27
        $deltaPsi += 0.0117879 * ($deltaLatitudeToOrigin * 0.00001) ** 5;
1930 27
        $deltaPsi += -0.0055161 * ($deltaLatitudeToOrigin * 0.00001) ** 6;
1931 27
        $deltaPsi += 0.0026906 * ($deltaLatitudeToOrigin * 0.00001) ** 7;
1932 27
        $deltaPsi += -0.001333 * ($deltaLatitudeToOrigin * 0.00001) ** 8;
1933 27
        $deltaPsi += 0.00067 * ($deltaLatitudeToOrigin * 0.00001) ** 9;
1934
        $deltaPsi += -0.00034 * ($deltaLatitudeToOrigin * 0.00001) ** 10;
1935 27
1936
        $zeta = new ComplexNumber($deltaPsi, $deltaLongitudeToOrigin->getValue());
1937 27
1938 27
        $B1 = new ComplexNumber(0.7557853228, 0.0);
1939 27
        $B2 = new ComplexNumber(0.249204646, 0.003371507);
1940 27
        $B3 = new ComplexNumber(-0.001541739, 0.041058560);
1941 27
        $B4 = new ComplexNumber(-0.10162907, 0.01727609);
1942 27
        $B5 = new ComplexNumber(-0.26623489, -0.36249218);
1943 27
        $B6 = new ComplexNumber(-0.6870983, -1.1651967);
1944 27
        $z = new ComplexNumber(0, 0);
1945 27
        $z = $z->add($B1->multiply($zeta->pow(1)));
1946 27
        $z = $z->add($B2->multiply($zeta->pow(2)));
1947 27
        $z = $z->add($B3->multiply($zeta->pow(3)));
1948 27
        $z = $z->add($B4->multiply($zeta->pow(4)));
1949 27
        $z = $z->add($B5->multiply($zeta->pow(5)));
1950
        $z = $z->add($B6->multiply($zeta->pow(6)));
1951 27
1952 27
        $easting = $falseEasting->asMetres()->getValue() + $z->getImaginary() * $a;
1953
        $northing = $falseNorthing->asMetres()->getValue() + $z->getReal() * $a;
1954 27
1955
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
1956
    }
1957
1958
    /**
1959
     * Madrid to ED50 polynomial.
1960 9
     */
1961
    public function madridToED50Polynomial(
1962
        Geographic2D $to,
1963
        Scale $A0,
1964
        Scale $A1,
1965
        Scale $A2,
1966
        Scale $A3,
1967
        Angle $B00,
1968
        Scale $B0,
1969
        Scale $B1,
1970
        Scale $B2,
1971
        Scale $B3
1972 9
    ): self {
1973 9
        $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());
1974
        $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()));
1975 9
1976
        return self::create($to, $this->latitude->add($dLatitude), $this->longitude->add($dLongitude), null, $this->epoch);
1977
    }
1978
1979
    /**
1980
     * Geographic3D to 2D conversion.
1981 47
     */
1982
    public function threeDToTwoD(
1983
        Geographic2D|Geographic3D $to
1984 47
    ): self {
1985 37
        if ($to instanceof Geographic2D) {
1986
            return static::create($to, $this->latitude, $this->longitude, null, $this->epoch);
1987
        }
1988 10
1989
        return static::create($to, $this->latitude, $this->longitude, new Metre(0), $this->epoch);
1990
    }
1991
1992
    /**
1993
     * Geographic2D offsets.
1994
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
1995
     * coordinate values of the point in the source system.
1996 9
     */
1997
    public function geographic2DOffsets(
1998
        Geographic2D|Geographic3D $to,
1999
        Angle $latitudeOffset,
2000
        Angle $longitudeOffset
2001 9
    ): self {
2002 9
        $toLatitude = $this->latitude->add($latitudeOffset);
2003
        $toLongitude = $this->longitude->add($longitudeOffset);
2004 9
2005
        return static::create($to, $toLatitude, $toLongitude, null, $this->epoch);
2006
    }
2007
2008
    /*
2009
     * Geographic2D with Height Offsets.
2010
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
2011
     * coordinate values of the point in the source system.
2012
     */
2013
    public function geographic2DWithHeightOffsets(
2014
        Compound $to,
2015
        Angle $latitudeOffset,
2016
        Angle $longitudeOffset,
2017
        Length $geoidUndulation
2018
    ): CompoundPoint {
2019
        assert($this->height instanceof Length);
2020
        $toLatitude = $this->latitude->add($latitudeOffset);
2021
        $toLongitude = $this->longitude->add($longitudeOffset);
2022
        $toHeight = $this->height->add($geoidUndulation);
2023
2024
        assert($to->getHorizontal() instanceof Geographic2D);
2025
        $horizontal = static::create($to->getHorizontal(), $toLatitude, $toLongitude, null, $this->epoch);
2026
        $vertical = VerticalPoint::create($to->getVertical(), $toHeight, $this->epoch);
2027
2028
        return CompoundPoint::create($to, $horizontal, $vertical, $this->epoch);
2029
    }
2030
2031
    /**
2032
     * General polynomial.
2033
     * @param Coefficient[] $powerCoefficients
2034 45
     */
2035
    public function generalPolynomial(
2036
        Geographic2D|Geographic3D $to,
2037
        Angle $ordinate1OfEvaluationPointInSourceCRS,
2038
        Angle $ordinate2OfEvaluationPointInSourceCRS,
2039
        Angle $ordinate1OfEvaluationPointInTargetCRS,
2040
        Angle $ordinate2OfEvaluationPointInTargetCRS,
2041
        Scale $scalingFactorForSourceCRSCoordDifferences,
2042
        Scale $scalingFactorForTargetCRSCoordDifferences,
2043
        Scale $A0,
2044
        Scale $B0,
2045
        array $powerCoefficients,
2046
        bool $inReverse
2047 45
    ): self {
2048 45
        $xs = $this->latitude->getValue();
2049
        $ys = $this->longitude->getValue();
2050 45
2051 45
        $t = $this->generalPolynomialUnitless(
2052 45
            $xs,
2053 45
            $ys,
2054 45
            $ordinate1OfEvaluationPointInSourceCRS,
2055 45
            $ordinate2OfEvaluationPointInSourceCRS,
2056 45
            $ordinate1OfEvaluationPointInTargetCRS,
2057 45
            $ordinate2OfEvaluationPointInTargetCRS,
2058 45
            $scalingFactorForSourceCRSCoordDifferences,
2059 45
            $scalingFactorForTargetCRSCoordDifferences,
2060 45
            $A0,
2061 45
            $B0,
2062 45
            $powerCoefficients,
2063 45
            $inReverse
2064
        );
2065 45
2066 45
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2067
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2068 45
2069 45
        return static::create(
2070 45
            $to,
2071 45
            Angle::makeUnit($t['xt'], $xtUnit),
2072 45
            Angle::makeUnit($t['yt'], $ytUnit),
2073 45
            $this->height,
2074 45
            $this->epoch
2075
        );
2076
    }
2077
2078
    /**
2079
     * Reversible polynomial.
2080
     * @param Coefficient[] $powerCoefficients
2081 36
     */
2082
    public function reversiblePolynomial(
2083
        Geographic2D|Geographic3D $to,
2084
        Angle $ordinate1OfEvaluationPoint,
2085
        Angle $ordinate2OfEvaluationPoint,
2086
        Scale $scalingFactorForCoordDifferences,
2087
        Scale $A0,
2088
        Scale $B0,
2089
        $powerCoefficients
2090 36
    ): self {
2091 36
        $xs = $this->latitude->getValue();
2092
        $ys = $this->longitude->getValue();
2093 36
2094 36
        $t = $this->reversiblePolynomialUnitless(
2095 36
            $xs,
2096 36
            $ys,
2097 36
            $ordinate1OfEvaluationPoint,
2098 36
            $ordinate2OfEvaluationPoint,
2099 36
            $scalingFactorForCoordDifferences,
2100 36
            $A0,
2101 36
            $B0,
2102 36
            $powerCoefficients
2103
        );
2104 36
2105 36
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2106
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2107 36
2108 36
        return static::create(
2109 36
            $to,
2110 36
            Angle::makeUnit($t['xt'], $xtUnit),
2111 36
            Angle::makeUnit($t['yt'], $ytUnit),
2112 36
            $this->height,
2113 36
            $this->epoch
2114
        );
2115
    }
2116
2117
    /**
2118
     * Axis Order Reversal.
2119
     */
2120
    public function axisReversal(
2121
        Geographic2D|Geographic3D $to
2122
    ): self {
2123
        // axes are read in from the CRS, this is a book-keeping adjustment only
2124
        return static::create($to, $this->latitude, $this->longitude, $this->height, $this->epoch);
2125
    }
2126
2127
    /**
2128
     * Ordnance Survey National Transformation
2129
     * Geodetic transformation between ETRS89 (or WGS 84) and OSGB36 / National Grid.  Uses ETRS89 / National Grid as
2130
     * an intermediate coordinate system for bi-linear interpolation of gridded grid coordinate differences.
2131
     */
2132
    public function OSTN15(
2133
        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

2133
        /** @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...
2134
        OSTNOSGM15Grid $eastingAndNorthingDifferenceFile
2135
    ): ProjectedPoint {
2136
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2137
        $etrs89NationalGrid = new Projected(
2138
            'ETRS89 / National Grid',
2139
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2140
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2141
            $osgb36NationalGrid->getBoundingArea()
2142
        );
2143
2144
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2145
2146
        return $eastingAndNorthingDifferenceFile->applyForwardHorizontalAdjustment($projected);
2147
    }
2148
2149
    /**
2150
     * Geog3D to Geog2D+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 geographic3DTo2DPlusGravityHeightOSGM15(
2155
        Compound $to,
2156
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
2157
    ): CompoundPoint {
2158
        assert($this->height instanceof Length);
2159
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2160
        $etrs89NationalGrid = new Projected(
2161
            'ETRS89 / National Grid',
2162
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2163
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2164
            $osgb36NationalGrid->getBoundingArea()
2165
        );
2166
2167
        /** @var ProjectedPoint $projected */
2168
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2169
2170
        assert($to->getHorizontal() instanceof Geographic2D);
2171
        $horizontalPoint = self::create(
2172
            $to->getHorizontal(),
2173
            $this->latitude,
2174
            $this->longitude,
2175
            null,
2176
            $this->getCoordinateEpoch()
2177
        );
2178
2179
        $verticalPoint = VerticalPoint::create(
2180
            $to->getVertical(),
2181
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
2182
            $this->getCoordinateEpoch()
2183
        );
2184
2185
        return CompoundPoint::create(
2186
            $to,
2187
            $horizontalPoint,
2188
            $verticalPoint,
2189
            $this->getCoordinateEpoch()
2190
        );
2191
    }
2192
2193
    /**
2194
     * Geographic3D to GravityRelatedHeight (OSGM-GB).
2195
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2196
     * coordinate differences.
2197
     */
2198
    public function geographic3DToGravityHeightOSGM15(
2199
        Vertical $to,
2200
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
2201
    ): VerticalPoint {
2202
        assert($this->height instanceof Length);
2203
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2204
        $etrs89NationalGrid = new Projected(
2205
            'ETRS89 / National Grid',
2206
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2207
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2208
            $osgb36NationalGrid->getBoundingArea()
2209
        );
2210
2211
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2212
2213
        return VerticalPoint::create(
2214
            $to,
2215
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
2216
            $this->getCoordinateEpoch()
2217
        );
2218
    }
2219
2220
    /**
2221
     * Geog3D to Geog2D+GravityRelatedHeight.
2222 6
     */
2223
    public function geographic3DTo2DPlusGravityHeightFromGrid(
2224
        Compound $to,
2225
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2226 6
    ): CompoundPoint {
2227 6
        assert($this->height instanceof Length);
2228 6
        assert($to->getHorizontal() instanceof Geographic);
2229 6
        $horizontalPoint = self::create(
2230 6
            $to->getHorizontal(),
2231 6
            $this->latitude,
2232 6
            $this->longitude,
2233 6
            null,
2234 6
            $this->getCoordinateEpoch()
2235
        );
2236 6
2237 6
        $verticalPoint = VerticalPoint::create(
2238 6
            $to->getVertical(),
2239 6
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
2240 6
            $this->getCoordinateEpoch()
2241
        );
2242 6
2243 6
        return CompoundPoint::create(
2244 6
            $to,
2245 6
            $horizontalPoint,
2246 6
            $verticalPoint,
2247 6
            $this->getCoordinateEpoch()
2248
        );
2249
    }
2250
2251
    /**
2252
     * Geographic3D to GravityRelatedHeight.
2253 4
     */
2254
    public function geographic3DToGravityHeightFromGrid(
2255
        Vertical $to,
2256
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2257 4
    ): VerticalPoint {
2258
        assert($this->height instanceof Length);
2259 4
2260 4
        return VerticalPoint::create(
2261 4
            $to,
2262 4
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
2263 4
            $this->getCoordinateEpoch()
2264
        );
2265
    }
2266
2267
    /**
2268
     * NADCON5.
2269
     * @internal just a wrapper
2270 8
     */
2271
    public function offsetsFromGridNADCON5(
2272
        Geographic2D|Geographic3D $to,
2273
        NADCON5Grid $latitudeDifferenceFile,
2274
        NADCON5Grid $longitudeDifferenceFile,
2275
        ?NADCON5Grid $ellipsoidalHeightDifferenceFile,
2276
        bool $inReverse
2277 8
    ): self {
2278
        $aggregation = new NADCON5Grids($longitudeDifferenceFile, $latitudeDifferenceFile, $ellipsoidalHeightDifferenceFile);
2279 8
2280
        return $this->offsetsFromGrid($to, $aggregation, $inReverse);
2281
    }
2282
2283
    /**
2284
     * Geographic offsets from grid.
2285 15
     */
2286
    public function offsetsFromGrid(
2287
        Geographic2D|Geographic3D $to,
2288
        GeographicGrid $offsetsFile,
2289
        bool $inReverse
2290 15
    ): self {
2291 9
        if (!$inReverse) {
2292
            return $offsetsFile->applyForwardAdjustment($this, $to);
2293
        }
2294 7
2295
        return $offsetsFile->applyReverseAdjustment($this, $to);
2296
    }
2297 9
2298
    public function localOrthographic(
2299
        Projected $to,
2300
        Angle $latitudeOfProjectionCentre,
2301
        Angle $longitudeOfProjectionCentre,
2302
        Angle $azimuthAtProjectionCentre,
2303
        Scale $scaleFactorAtProjectionCentre,
2304
        Length $eastingAtProjectionCentre,
2305
        Length $northingAtProjectionCentre
2306 9
    ): ProjectedPoint {
2307 9
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
2308 9
        $latitude = $this->latitude->asRadians()->getValue();
2309 9
        $longitude = $this->longitude->asRadians()->getValue();
2310 9
        $latitudeCentre = $latitudeOfProjectionCentre->asRadians()->getValue();
2311 9
        $longitudeCentre = $longitudeOfProjectionCentre->asRadians()->getValue();
2312 9
        $azimuthCentre = $azimuthAtProjectionCentre->asRadians()->getValue();
2313
        $scaleFactorCentre = $scaleFactorAtProjectionCentre->asUnity()->getValue();
2314 9
2315 9
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
2316 9
        $e2 = $ellipsoid->getEccentricitySquared();
2317 9
        $v = $a / sqrt(1 - $e2 * sin($latitude) ** 2);
2318
        $vc = $a / sqrt(1 - $e2 * sin($latitudeCentre) ** 2);
2319 9
2320 9
        $xp = $v * cos($latitude) * sin($longitude - $longitudeCentre);
2321
        $yp = -sin($latitudeCentre) * ($v * cos($latitude) * cos($longitude - $longitudeCentre) - $vc * cos($latitudeCentre)) + cos($latitudeCentre) * ($v * (1 - $e2) * sin($latitude) - $vc * (1 - $e2) * sin($latitudeCentre));
2322 9
2323 9
        $easting = $eastingAtProjectionCentre->asMetres()->getValue() + $scaleFactorCentre * (cos($azimuthCentre) * $xp - sin($azimuthCentre) * $yp);
2324
        $northing = $northingAtProjectionCentre->asMetres()->getValue() + $scaleFactorCentre * (sin($azimuthCentre) * $xp + cos($azimuthCentre) * $yp);
2325 9
2326
        return ProjectedPoint::create($to, new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $this->epoch);
2327
    }
2328 481
2329
    public function asGeographicValue(): GeographicValue
2330 481
    {
2331
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2332
    }
2333 18
2334
    public function asUTMPoint(): UTMPoint
2335 18
    {
2336
        $hemisphere = $this->getLatitude()->asDegrees()->getValue() >= 0 ? UTMPoint::HEMISPHERE_NORTH : UTMPoint::HEMISPHERE_SOUTH;
2337 18
2338 18
        $initialLongitude = new Degree(-180);
2339
        $zone = (int) ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / 6) % (360 / 6) + 1;
2340 18
2341 9
        if ($hemisphere === UTMPoint::HEMISPHERE_NORTH) {
2342
            $derivingConversion = 'urn:ogc:def:coordinateOperation:EPSG::' . ($zone + 16000);
2343 9
        } else {
2344
            $derivingConversion = 'urn:ogc:def:coordinateOperation:EPSG::' . ($zone + 16100);
2345
        }
2346 18
2347
        $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);
2348 18
2349 18
        $projectedCRS = new Projected(
2350 18
            $srid,
2351 18
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2352 18
            $this->crs->getDatum(),
2353 18
            BoundingArea::createWorld() // this is a dummy CRS for the transform only, details don't matter (UTMPoint creates own)
2354
        );
2355
2356 18
        /** @var ProjectedPoint $asProjected */
2357
        $asProjected = $this->performOperation($derivingConversion, $projectedCRS, false);
2358 18
2359
        return new UTMPoint($this->crs, $asProjected->getEasting(), $asProjected->getNorthing(), $zone, $hemisphere, $this->epoch);
2360
    }
2361
}
2362