Passed
Push — master ( 3c9843...962ccc )
by Doug
02:47
created

lambertConicConformal2SPMichigan()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 37
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 24
c 0
b 0
f 0
dl 0
loc 37
ccs 25
cts 25
cp 1
rs 9.536
cc 1
nc 1
nop 8
crap 1

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use function abs;
12
use function asinh;
13
use function atan;
14
use function atan2;
15
use function atanh;
16
use function cos;
17
use function cosh;
18
use DateTime;
19
use DateTimeImmutable;
20
use DateTimeInterface;
21
use function get_class;
22
use function implode;
23
use function is_nan;
24
use function log;
25
use const M_E;
26
use const M_PI;
27
use function max;
28
use PHPCoord\CoordinateOperation\AutoConversion;
29
use PHPCoord\CoordinateOperation\ComplexNumber;
30
use PHPCoord\CoordinateOperation\ConvertiblePoint;
31
use PHPCoord\CoordinateOperation\GeocentricValue;
32
use PHPCoord\CoordinateOperation\GeographicValue;
33
use PHPCoord\CoordinateReferenceSystem\Compound;
34
use PHPCoord\CoordinateReferenceSystem\Geocentric;
35
use PHPCoord\CoordinateReferenceSystem\Geographic;
36
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
37
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
38
use PHPCoord\CoordinateReferenceSystem\Projected;
39
use PHPCoord\CoordinateSystem\Axis;
40
use PHPCoord\CoordinateSystem\Cartesian;
41
use PHPCoord\Datum\Ellipsoid;
42
use PHPCoord\Exception\InvalidCoordinateReferenceSystemException;
43
use PHPCoord\Exception\UnknownAxisException;
44
use PHPCoord\Geometry\GeographicPolygon;
45
use PHPCoord\UnitOfMeasure\Angle\Angle;
46
use PHPCoord\UnitOfMeasure\Angle\ArcSecond;
47
use PHPCoord\UnitOfMeasure\Angle\Degree;
48
use PHPCoord\UnitOfMeasure\Angle\Radian;
49
use PHPCoord\UnitOfMeasure\Length\Length;
50
use PHPCoord\UnitOfMeasure\Length\Metre;
51
use PHPCoord\UnitOfMeasure\Scale\Coefficient;
52
use PHPCoord\UnitOfMeasure\Scale\Scale;
53
use PHPCoord\UnitOfMeasure\Scale\Unity;
54
use function sin;
55
use function sinh;
56
use function sprintf;
57
use function sqrt;
58
use function tan;
59
use TypeError;
60
61
/**
62
 * Coordinate representing a point on an ellipsoid.
63
 */
64
class GeographicPoint extends Point implements ConvertiblePoint
65
{
66
    use AutoConversion;
67
68
    /**
69
     * Latitude.
70
     */
71
    protected Angle $latitude;
72
73
    /**
74
     * Longitude.
75
     */
76
    protected Angle $longitude;
77
78
    /**
79
     * Height above ellipsoid (N.B. *not* height above ground, sea-level or anything else tangible).
80
     */
81
    protected ?Length $height;
82
83
    /**
84
     * Coordinate reference system.
85
     */
86
    protected Geographic $crs;
87
88
    /**
89
     * Coordinate epoch (date for which the specified coordinates represented this point).
90
     */
91
    protected ?DateTimeImmutable $epoch;
92
93 160
    protected function __construct(Angle $latitude, Angle $longitude, ?Length $height, Geographic $crs, ?DateTimeInterface $epoch = null)
94
    {
95 160
        if (!$crs instanceof Geographic2D && !$crs instanceof Geographic3D) {
96
            throw new TypeError(sprintf("A geographic point must be associated with a geographic CRS, but a '%s' CRS was given", get_class($crs)));
97
        }
98
99 160
        if ($crs instanceof Geographic2D && $height !== null) {
100 1
            throw new InvalidCoordinateReferenceSystemException('A 2D geographic point must not include a height');
101
        }
102
103 159
        if ($crs instanceof Geographic3D && $height === null) {
104 1
            throw new InvalidCoordinateReferenceSystemException('A 3D geographic point must include a height, none given');
105
        }
106
107 158
        $this->crs = $crs;
108
109 158
        $latitude = $this->normaliseLatitude($latitude);
110 158
        $longitude = $this->normaliseLongitude($longitude);
111
112 158
        $this->latitude = Angle::convert($latitude, $this->getAxisByName(Axis::GEODETIC_LATITUDE)->getUnitOfMeasureId());
113 158
        $this->longitude = Angle::convert($longitude, $this->getAxisByName(Axis::GEODETIC_LONGITUDE)->getUnitOfMeasureId());
114
115 158
        if ($height) {
116 20
            $this->height = Length::convert($height, $this->getAxisByName(Axis::ELLIPSOIDAL_HEIGHT)->getUnitOfMeasureId());
117
        } else {
118 146
            $this->height = null;
119
        }
120
121 158
        if ($epoch instanceof DateTime) {
122 1
            $epoch = DateTimeImmutable::createFromMutable($epoch);
123
        }
124 158
        $this->epoch = $epoch;
125 158
    }
126
127
    /**
128
     * @param Angle   $latitude  refer to CRS for preferred unit of measure, but any angle unit accepted
129
     * @param Angle   $longitude refer to CRS for preferred unit of measure, but any angle unit accepted
130
     * @param ?Length $height    refer to CRS for preferred unit of measure, but any length unit accepted
131
     */
132 160
    public static function create(Angle $latitude, Angle $longitude, ?Length $height = null, Geographic $crs, ?DateTimeInterface $epoch = null): self
133
    {
134 160
        return new static($latitude, $longitude, $height, $crs, $epoch);
135
    }
136
137 82
    public function getLatitude(): Angle
138
    {
139 82
        return $this->latitude;
140
    }
141
142 80
    public function getLongitude(): Angle
143
    {
144 80
        return $this->longitude;
145
    }
146
147 37
    public function getHeight(): ?Length
148
    {
149 37
        return $this->height;
150
    }
151
152 158
    public function getCRS(): Geographic
153
    {
154 158
        return $this->crs;
155
    }
156
157 18
    public function getCoordinateEpoch(): ?DateTimeImmutable
158
    {
159 18
        return $this->epoch;
160
    }
161
162 158
    protected function normaliseLatitude(Angle $latitude): Angle
163
    {
164 158
        if ($latitude->asDegrees()->getValue() > 90) {
165
            return new Degree(90);
166
        }
167 158
        if ($latitude->asDegrees()->getValue() < -90) {
168
            return new Degree(-90);
169
        }
170
171 158
        return $latitude;
172
    }
173
174 158
    protected function normaliseLongitude(Angle $longitude): Angle
175
    {
176 158
        while ($longitude->asDegrees()->getValue() > 180) {
177
            $longitude = $longitude->subtract(new Degree(360));
178
        }
179 158
        while ($longitude->asDegrees()->getValue() <= -180) {
180
            $longitude = $longitude->add(new Degree(360));
181
        }
182
183 158
        return $longitude;
184
    }
185
186
    /**
187
     * Calculate surface distance between two points.
188
     */
189 17
    public function calculateDistance(Point $to): Length
190
    {
191
        try {
192 17
            if ($to instanceof ConvertiblePoint) {
193 16
                $to = $to->convert($this->crs);
194
            }
195
        } finally {
196 17
            if ($to->getCRS()->getSRID() !== $this->crs->getSRID()) {
197 1
                throw new InvalidCoordinateReferenceSystemException('Can only calculate distances between two points in the same CRS');
198
            }
199
200
            /* @var GeographicPoint $to */
201 16
            return static::vincenty($this->asGeographicValue(), $to->asGeographicValue(), $this->getCRS()->getDatum()->getEllipsoid());
202
        }
203
    }
204
205 4
    public function __toString(): string
206
    {
207 4
        $values = [];
208 4
        foreach ($this->getCRS()->getCoordinateSystem()->getAxes() as $axis) {
209 4
            if ($axis->getName() === Axis::GEODETIC_LATITUDE) {
210 4
                $values[] = $this->latitude;
211 4
            } elseif ($axis->getName() === Axis::GEODETIC_LONGITUDE) {
212 4
                $values[] = $this->longitude;
213 1
            } elseif ($axis->getName() === Axis::ELLIPSOIDAL_HEIGHT) {
214 1
                $values[] = $this->height;
215
            } else {
216
                throw new UnknownAxisException(); // @codeCoverageIgnore
217
            }
218
        }
219
220 4
        return '(' . implode(', ', $values) . ')';
221
    }
222
223
    /**
224
     * Geographic/geocentric conversions
225
     * In applications it is often concatenated with the 3- 7- or 10-parameter transformations 9603, 9606, 9607 or
226
     * 9636 to form a geographic to geographic transformation.
227
     */
228 1
    public function geographicGeocentric(
229
        Geocentric $to
230
    ): GeocentricPoint {
231 1
        $geographicValue = new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
232 1
        $asGeocentric = $geographicValue->asGeocentricValue();
233
234 1
        return GeocentricPoint::create($asGeocentric->getX(), $asGeocentric->getY(), $asGeocentric->getZ(), $to, $this->epoch);
235
    }
236
237
    /**
238
     * Coordinate Frame rotation (geog2D/geog3D domain)
239
     * Note the analogy with the Position Vector tfm (codes 9606/1037) but beware of the differences!  The Position Vector
240
     * convention is used by IAG and recommended by ISO 19111. See methods 1032/1038/9607 for similar tfms operating
241
     * between other CRS types.
242
     */
243 6
    public function coordinateFrameRotation(
244
        Geographic $to,
245
        Length $xAxisTranslation,
246
        Length $yAxisTranslation,
247
        Length $zAxisTranslation,
248
        Angle $xAxisRotation,
249
        Angle $yAxisRotation,
250
        Angle $zAxisRotation,
251
        Scale $scaleDifference
252
    ): self {
253 6
        return $this->coordinateFrameMolodenskyBadekas(
254 6
            $to,
255
            $xAxisTranslation,
256
            $yAxisTranslation,
257
            $zAxisTranslation,
258
            $xAxisRotation,
259
            $yAxisRotation,
260
            $zAxisRotation,
261
            $scaleDifference,
262 6
            new Metre(0),
263 6
            new Metre(0),
264 6
            new Metre(0)
265
        );
266
    }
267
268
    /**
269
     * Molodensky-Badekas (CF geog2D/geog3D domain)
270
     * See method codes 1034 and 1039/9636 for this operation in other coordinate domains and method code 1062/1063 for the
271
     * opposite rotation convention in geographic 2D domain.
272
     */
273 8
    public function coordinateFrameMolodenskyBadekas(
274
        Geographic $to,
275
        Length $xAxisTranslation,
276
        Length $yAxisTranslation,
277
        Length $zAxisTranslation,
278
        Angle $xAxisRotation,
279
        Angle $yAxisRotation,
280
        Angle $zAxisRotation,
281
        Scale $scaleDifference,
282
        Length $ordinate1OfEvaluationPoint,
283
        Length $ordinate2OfEvaluationPoint,
284
        Length $ordinate3OfEvaluationPoint
285
    ): self {
286 8
        $geographicValue = new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
287 8
        $asGeocentric = $geographicValue->asGeocentricValue();
288
289 8
        $xs = $asGeocentric->getX()->asMetres()->getValue();
290 8
        $ys = $asGeocentric->getY()->asMetres()->getValue();
291 8
        $zs = $asGeocentric->getZ()->asMetres()->getValue();
292 8
        $tx = $xAxisTranslation->asMetres()->getValue();
293 8
        $ty = $yAxisTranslation->asMetres()->getValue();
294 8
        $tz = $zAxisTranslation->asMetres()->getValue();
295 8
        $rx = $xAxisRotation->asRadians()->getValue();
296 8
        $ry = $yAxisRotation->asRadians()->getValue();
297 8
        $rz = $zAxisRotation->asRadians()->getValue();
298 8
        $M = 1 + $scaleDifference->asUnity()->getValue();
299 8
        $xp = $ordinate1OfEvaluationPoint->asMetres()->getValue();
300 8
        $yp = $ordinate2OfEvaluationPoint->asMetres()->getValue();
301 8
        $zp = $ordinate3OfEvaluationPoint->asMetres()->getValue();
302
303 8
        $xt = $M * ((($xs - $xp) * 1) + (($ys - $yp) * $rz) + (($zs - $zp) * -$ry)) + $tx + $xp;
304 8
        $yt = $M * ((($xs - $xp) * -$rz) + (($ys - $yp) * 1) + (($zs - $zp) * $rx)) + $ty + $yp;
305 8
        $zt = $M * ((($xs - $xp) * $ry) + (($ys - $yp) * -$rx) + (($zs - $zp) * 1)) + $tz + $zp;
306 8
        $newGeocentric = new GeocentricValue(new Metre($xt), new Metre($yt), new Metre($zt), $to->getDatum());
307 8
        $newGeographic = $newGeocentric->asGeographicValue();
308
309 8
        return static::create($newGeographic->getLatitude(), $newGeographic->getLongitude(), $to instanceof Geographic3D ? $newGeographic->getHeight() : null, $to, $this->epoch);
310
    }
311
312
    /**
313
     * Position Vector transformation (geog2D/geog3D domain)
314
     * Note the analogy with the Coordinate Frame rotation (code 9607/1038) but beware of the differences!  The Position
315
     * Vector convention is used by IAG and recommended by ISO 19111. See methods 1033/1037/9606 for similar tfms
316
     * operating between other CRS types.
317
     */
318 18
    public function positionVectorTransformation(
319
        Geographic $to,
320
        Length $xAxisTranslation,
321
        Length $yAxisTranslation,
322
        Length $zAxisTranslation,
323
        Angle $xAxisRotation,
324
        Angle $yAxisRotation,
325
        Angle $zAxisRotation,
326
        Scale $scaleDifference
327
    ): self {
328 18
        return $this->positionVectorMolodenskyBadekas(
329 18
            $to,
330
            $xAxisTranslation,
331
            $yAxisTranslation,
332
            $zAxisTranslation,
333
            $xAxisRotation,
334
            $yAxisRotation,
335
            $zAxisRotation,
336
            $scaleDifference,
337 18
            new Metre(0),
338 18
            new Metre(0),
339 18
            new Metre(0)
340
        );
341
    }
342
343
    /**
344
     * Molodensky-Badekas (PV geog2D/geog3D domain)
345
     * See method codes 1061 and 1062/1063 for this operation in other coordinate domains and method code 1039/9636 for opposite
346
     * rotation in geographic 2D/3D domain.
347
     */
348 20
    public function positionVectorMolodenskyBadekas(
349
        Geographic $to,
350
        Length $xAxisTranslation,
351
        Length $yAxisTranslation,
352
        Length $zAxisTranslation,
353
        Angle $xAxisRotation,
354
        Angle $yAxisRotation,
355
        Angle $zAxisRotation,
356
        Scale $scaleDifference,
357
        Length $ordinate1OfEvaluationPoint,
358
        Length $ordinate2OfEvaluationPoint,
359
        Length $ordinate3OfEvaluationPoint
360
    ): self {
361 20
        $geographicValue = new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
362 20
        $asGeocentric = $geographicValue->asGeocentricValue();
363
364 20
        $xs = $asGeocentric->getX()->asMetres()->getValue();
365 20
        $ys = $asGeocentric->getY()->asMetres()->getValue();
366 20
        $zs = $asGeocentric->getZ()->asMetres()->getValue();
367 20
        $tx = $xAxisTranslation->asMetres()->getValue();
368 20
        $ty = $yAxisTranslation->asMetres()->getValue();
369 20
        $tz = $zAxisTranslation->asMetres()->getValue();
370 20
        $rx = $xAxisRotation->asRadians()->getValue();
371 20
        $ry = $yAxisRotation->asRadians()->getValue();
372 20
        $rz = $zAxisRotation->asRadians()->getValue();
373 20
        $M = 1 + $scaleDifference->asUnity()->getValue();
374 20
        $xp = $ordinate1OfEvaluationPoint->asMetres()->getValue();
375 20
        $yp = $ordinate2OfEvaluationPoint->asMetres()->getValue();
376 20
        $zp = $ordinate3OfEvaluationPoint->asMetres()->getValue();
377
378 20
        $xt = $M * ((($xs - $xp) * 1) + (($ys - $yp) * -$rz) + (($zs - $zp) * $ry)) + $tx + $xp;
379 20
        $yt = $M * ((($xs - $xp) * $rz) + (($ys - $yp) * 1) + (($zs - $zp) * -$rx)) + $ty + $yp;
380 20
        $zt = $M * ((($xs - $xp) * -$ry) + (($ys - $yp) * $rx) + (($zs - $zp) * 1)) + $tz + $zp;
381 20
        $newGeocentric = new GeocentricValue(new Metre($xt), new Metre($yt), new Metre($zt), $to->getDatum());
382 20
        $newGeographic = $newGeocentric->asGeographicValue();
383
384 20
        return static::create($newGeographic->getLatitude(), $newGeographic->getLongitude(), $to instanceof Geographic3D ? $newGeographic->getHeight() : null, $to, $this->epoch);
385
    }
386
387
    /**
388
     * Geocentric translations
389
     * This method allows calculation of geocentric coords in the target system by adding the parameter values to the
390
     * corresponding coordinates of the point in the source system. See methods 1031 and 1035 for similar tfms
391
     * operating between other CRSs types.
392
     */
393 6
    public function geocentricTranslation(
394
        Geographic $to,
395
        Length $xAxisTranslation,
396
        Length $yAxisTranslation,
397
        Length $zAxisTranslation
398
    ): self {
399 6
        return $this->positionVectorTransformation(
400 6
            $to,
401
            $xAxisTranslation,
402
            $yAxisTranslation,
403
            $zAxisTranslation,
404 6
            new Radian(0),
405 6
            new Radian(0),
406 6
            new Radian(0),
407 6
            new Unity(0)
408
        );
409
    }
410
411
    /**
412
     * Abridged Molodensky
413
     * This transformation is a truncated Taylor series expansion of a transformation between two geographic coordinate
414
     * systems, modelled as a set of geocentric translations.
415
     */
416 2
    public function abridgedMolodensky(
417
        Geographic $to,
418
        Length $xAxisTranslation,
419
        Length $yAxisTranslation,
420
        Length $zAxisTranslation,
421
        Length $differenceInSemiMajorAxis,
422
        Scale $differenceInFlattening
423
    ): self {
424 2
        $latitude = $this->latitude->asRadians()->getValue();
425 2
        $longitude = $this->longitude->asRadians()->getValue();
426 2
        $fromHeight = $this->height ? $this->height->asMetres()->getValue() : 0;
427 2
        $tx = $xAxisTranslation->asMetres()->getValue();
428 2
        $ty = $yAxisTranslation->asMetres()->getValue();
429 2
        $tz = $zAxisTranslation->asMetres()->getValue();
430 2
        $da = $differenceInSemiMajorAxis->asMetres()->getValue();
431 2
        $df = $differenceInFlattening->asUnity()->getValue();
432
433 2
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
434 2
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
435
436 2
        $rho = $a * (1 - $e2) / (1 - $e2 * sin($latitude) ** 2) ** (3 / 2);
437 2
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
438
439 2
        $f = $this->crs->getDatum()->getEllipsoid()->getInverseFlattening();
440
441 2
        $dLatitude = ((-$tx * sin($latitude) * cos($longitude)) - ($ty * sin($latitude) * sin($longitude)) + ($tz * cos($latitude)) + ((($a * $df) + ($this->crs->getDatum()->getEllipsoid()->getInverseFlattening() * $da)) * sin(2 * $latitude))) / ($rho * sin((new ArcSecond(1))->asRadians()->getValue()));
442 2
        $dLongitude = (-$tx * sin($longitude) + $ty * cos($longitude)) / (($nu * cos($latitude)) * sin((new ArcSecond(1))->asRadians()->getValue()));
443 2
        $dHeight = ($tx * cos($latitude) * cos($longitude)) + ($ty * cos($latitude) * sin($longitude)) + ($tz * sin($latitude)) + (($a * $df + $f * $da) * (sin($latitude) ** 2)) - $da;
444
445 2
        $toLatitude = $latitude + (new ArcSecond($dLatitude))->asRadians()->getValue();
446 2
        $toLongitude = $longitude + (new ArcSecond($dLongitude))->asRadians()->getValue();
447 2
        $toHeight = $fromHeight + $dHeight;
448
449 2
        return static::create(new Radian($toLatitude), new Radian($toLongitude), $to instanceof Geographic3D ? new Metre($toHeight) : null, $to, $this->epoch);
450
    }
451
452
    /**
453
     * Molodensky
454
     * See Abridged Molodensky.
455
     */
456 2
    public function molodensky(
457
        Geographic $to,
458
        Length $xAxisTranslation,
459
        Length $yAxisTranslation,
460
        Length $zAxisTranslation,
461
        Length $differenceInSemiMajorAxis,
462
        Scale $differenceInFlattening
463
    ): self {
464 2
        $latitude = $this->latitude->asRadians()->getValue();
465 2
        $longitude = $this->longitude->asRadians()->getValue();
466 2
        $fromHeight = $this->height ? $this->height->asMetres()->getValue() : 0;
467 2
        $tx = $xAxisTranslation->asMetres()->getValue();
468 2
        $ty = $yAxisTranslation->asMetres()->getValue();
469 2
        $tz = $zAxisTranslation->asMetres()->getValue();
470 2
        $da = $differenceInSemiMajorAxis->asMetres()->getValue();
471 2
        $df = $differenceInFlattening->asUnity()->getValue();
472
473 2
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
474 2
        $b = $this->crs->getDatum()->getEllipsoid()->getSemiMinorAxis()->asMetres()->getValue();
475 2
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
476
477 2
        $rho = $a * (1 - $e2) / (1 - $e2 * sin($latitude) ** 2) ** (3 / 2);
478 2
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
479
480 2
        $f = $this->crs->getDatum()->getEllipsoid()->getInverseFlattening();
0 ignored issues
show
Unused Code introduced by
The assignment to $f is dead and can be removed.
Loading history...
481
482 2
        $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()));
483 2
        $dLongitude = (-$tx * sin($longitude) + $ty * cos($longitude)) / ((($nu + $fromHeight) * cos($latitude)) * sin((new ArcSecond(1))->asRadians()->getValue()));
484 2
        $dHeight = ($tx * cos($latitude) * cos($longitude)) + ($ty * cos($latitude) * sin($longitude)) + ($tz * sin($latitude)) - $da * $a / $nu + $df * $b / $a * $nu * sin($latitude) ** 2;
485
486 2
        $toLatitude = $latitude + (new ArcSecond($dLatitude))->asRadians()->getValue();
487 2
        $toLongitude = $longitude + (new ArcSecond($dLongitude))->asRadians()->getValue();
488 2
        $toHeight = $fromHeight + $dHeight;
489
490 2
        return static::create(new Radian($toLatitude), new Radian($toLongitude), $to instanceof Geographic3D ? new Metre($toHeight) : null, $to, $this->epoch);
491
    }
492
493
    /**
494
     * Albers Equal Area.
495
     */
496 2
    public function albersEqualArea(
497
        Projected $to,
498
        Angle $latitudeOfFalseOrigin,
499
        Angle $longitudeOfFalseOrigin,
500
        Angle $latitudeOf1stStandardParallel,
501
        Angle $latitudeOf2ndStandardParallel,
502
        Length $eastingAtFalseOrigin,
503
        Length $northingAtFalseOrigin
504
    ): ProjectedPoint {
505 2
        $latitude = $this->latitude->asRadians()->getValue();
506 2
        $longitude = $this->longitude->asRadians()->getValue();
507 2
        $phiOrigin = $latitudeOfFalseOrigin->asRadians()->getValue();
508 2
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
509 2
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
510 2
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
511 2
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
512 2
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
513
514 2
        $centralMeridianFirstParallel = cos($phi1) / sqrt(1 - ($e2 * sin($phi1) ** 2));
515 2
        $centralMeridianSecondParallel = cos($phi2) / sqrt(1 - ($e2 * sin($phi2) ** 2));
516
517 2
        $alpha = (1 - $e2) * (sin($latitude) / (1 - $e2 * sin($latitude) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))));
518 2
        $alphaOrigin = (1 - $e2) * (sin($phiOrigin) / (1 - $e2 * sin($phiOrigin) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($phiOrigin)) / (1 + $e * sin($phiOrigin))));
519 2
        $alphaFirstParallel = (1 - $e2) * (sin($phi1) / (1 - $e2 * sin($phi1) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))));
520 2
        $alphaSecondParallel = (1 - $e2) * (sin($phi2) / (1 - $e2 * sin($phi2) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))));
521
522 2
        $n = ($centralMeridianFirstParallel ** 2 - $centralMeridianSecondParallel ** 2) / ($alphaSecondParallel - $alphaFirstParallel);
523 2
        $C = $centralMeridianFirstParallel ** 2 + $n * $alphaFirstParallel;
524 2
        $theta = $n * ($longitude - $longitudeOfFalseOrigin->asRadians()->getValue());
525 2
        $rho = $a * sqrt($C - $n * $alpha) / $n;
526 2
        $rhoOrigin = ($a * sqrt($C - $n * $alphaOrigin)) / $n;
527
528 2
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + ($rho * sin($theta));
529 2
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rhoOrigin - ($rho * cos($theta));
530
531 2
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
532
    }
533
534
    /**
535
     * American Polyconic.
536
     */
537 1
    public function americanPolyconic(
538
        Projected $to,
539
        Angle $latitudeOfNaturalOrigin,
540
        Angle $longitudeOfNaturalOrigin,
541
        Length $falseEasting,
542
        Length $falseNorthing
543
    ): ProjectedPoint {
544 1
        $latitude = $this->latitude->asRadians()->getValue();
545 1
        $longitude = $this->longitude->asRadians()->getValue();
546 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
547 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
548 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
549 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
550 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
551 1
        $e4 = $e ** 4;
552 1
        $e6 = $e ** 6;
553
554 1
        $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));
555 1
        $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));
556
557 1
        if ($latitude === 0.0) {
0 ignored issues
show
introduced by
The condition $latitude === 0.0 is always false.
Loading history...
558
            $easting = $falseEasting->asMetres()->getValue() + $a * ($longitude - $longitudeOrigin);
559
            $northing = $falseNorthing->asMetres()->getValue() - $MO;
560
        } else {
561 1
            $L = ($longitude - $longitudeOrigin) * sin($latitude);
562 1
            $nu = $a / sqrt(1 - $e2 * sin($latitude) ** 2);
563
564 1
            $easting = $falseEasting->asMetres()->getValue() + $nu * 1 / tan($latitude) * sin($L);
565 1
            $northing = $falseNorthing->asMetres()->getValue() + $M - $MO + $nu * 1 / tan($latitude) * (1 - cos($L));
566
        }
567
568 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
569
    }
570
571
    /**
572
     * Bonne.
573
     */
574 1
    public function bonne(
575
        Projected $to,
576
        Angle $latitudeOfNaturalOrigin,
577
        Angle $longitudeOfNaturalOrigin,
578
        Length $falseEasting,
579
        Length $falseNorthing
580
    ): ProjectedPoint {
581 1
        $latitude = $this->latitude->asRadians()->getValue();
582 1
        $longitude = $this->longitude->asRadians()->getValue();
583 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
584 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
585 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
586 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
587 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
588 1
        $e4 = $e ** 4;
589 1
        $e6 = $e ** 6;
590
591 1
        $m = cos($latitude) / sqrt(1 - $e2 * sin($latitude) ** 2);
592 1
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
593
594 1
        $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));
595 1
        $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));
596
597 1
        $rho = $a * $mO / sin($latitudeOrigin) + $MO - $M;
598 1
        $tau = $a * $m * ($longitude - $longitudeOrigin) / $rho;
599
600 1
        $easting = $falseEasting->asMetres()->getValue() + ($rho * sin($tau));
601 1
        $northing = $falseNorthing->asMetres()->getValue() + (($a * $mO / sin($latitudeOrigin) - $rho * cos($tau)));
602
603 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
604
    }
605
606
    /**
607
     * Bonne South Orientated.
608
     */
609 1
    public function bonneSouthOrientated(
610
        Projected $to,
611
        Angle $latitudeOfNaturalOrigin,
612
        Angle $longitudeOfNaturalOrigin,
613
        Length $falseEasting,
614
        Length $falseNorthing
615
    ): ProjectedPoint {
616 1
        $latitude = $this->latitude->asRadians()->getValue();
617 1
        $longitude = $this->longitude->asRadians()->getValue();
618 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
619 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
620 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
621 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
622 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
623 1
        $e4 = $e ** 4;
624 1
        $e6 = $e ** 6;
625
626 1
        $m = cos($latitude) / sqrt(1 - $e2 * sin($latitude) ** 2);
627 1
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
628
629 1
        $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));
630 1
        $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));
631
632 1
        $rho = $a * $mO / sin($latitudeOrigin) + $MO - $M;
633 1
        $tau = $a * $m * ($longitude - $longitudeOrigin) / $rho;
634
635 1
        $westing = $falseEasting->asMetres()->getValue() - ($rho * sin($tau));
636 1
        $southing = $falseNorthing->asMetres()->getValue() - (($a * $mO / sin($latitudeOrigin) - $rho * cos($tau)));
637
638 1
        return ProjectedPoint::create(new Metre(-$westing), new Metre(-$southing), new Metre($westing), new Metre($southing), $to, $this->epoch);
639
    }
640
641
    /**
642
     * Cassini-Soldner.
643
     */
644 1
    public function cassiniSoldner(
645
        Projected $to,
646
        Angle $latitudeOfNaturalOrigin,
647
        Angle $longitudeOfNaturalOrigin,
648
        Length $falseEasting,
649
        Length $falseNorthing
650
    ): ProjectedPoint {
651 1
        $latitude = $this->latitude->asRadians()->getValue();
652 1
        $longitude = $this->longitude->asRadians()->getValue();
653 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
654 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
655 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
656 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
657 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
658 1
        $e4 = $e ** 4;
659 1
        $e6 = $e ** 6;
660
661 1
        $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));
662 1
        $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));
663
664 1
        $A = ($longitude - $longitudeOrigin) * cos($latitude);
665 1
        $T = tan($latitude) ** 2;
666 1
        $C = $e2 * cos($latitude) ** 2 / (1 - $e2);
667 1
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
668 1
        $X = $M - $MO + $nu * tan($latitude) * ($A ** 2 / 2 + (5 - $T + 6 * $C) * $A ** 4 / 24);
669
670 1
        $easting = $falseEasting->asMetres()->getValue() + $nu * ($A - $T * $A ** 3 / 6 - (8 - $T + 8 * $C) * $T * $A ** 5 / 120);
671 1
        $northing = $falseNorthing->asMetres()->getValue() + $X;
672
673 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
674
    }
675
676
    /**
677
     * Hyperbolic Cassini-Soldner.
678
     */
679 2
    public function hyperbolicCassiniSoldner(
680
        Projected $to,
681
        Angle $latitudeOfNaturalOrigin,
682
        Angle $longitudeOfNaturalOrigin,
683
        Length $falseEasting,
684
        Length $falseNorthing
685
    ): ProjectedPoint {
686 2
        $latitude = $this->latitude->asRadians()->getValue();
687 2
        $longitude = $this->longitude->asRadians()->getValue();
688 2
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
689 2
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
690 2
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
691 2
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
692 2
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
693 2
        $e4 = $e ** 4;
694 2
        $e6 = $e ** 6;
695
696 2
        $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));
697 2
        $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));
698
699 2
        $A = ($longitude - $longitudeOrigin) * cos($latitude);
700 2
        $T = tan($latitude) ** 2;
701 2
        $C = $e2 * cos($latitude) ** 2 / (1 - $e2);
702 2
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
703 2
        $rho = $a * (1 - $e2) / (1 - $e2 * sin($latitude) ** 2) ** (3 / 2);
704 2
        $X = $M - $MO + $nu * tan($latitude) * ($A ** 2 / 2 + (5 - $T + 6 * $C) * $A ** 4 / 24);
705
706 2
        $easting = $falseEasting->asMetres()->getValue() + $nu * ($A - $T * $A ** 3 / 6 - (8 - $T + 8 * $C) * $T * $A ** 5 / 120);
707 2
        $northing = $falseNorthing->asMetres()->getValue() + $X - ($X ** 3 / (6 * $rho * $nu));
708
709 2
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
710
    }
711
712
    /**
713
     * Colombia Urban.
714
     */
715 1
    public function columbiaUrban(
716
        Projected $to,
717
        Angle $latitudeOfNaturalOrigin,
718
        Angle $longitudeOfNaturalOrigin,
719
        Length $falseEasting,
720
        Length $falseNorthing,
721
        Length $projectionPlaneOriginHeight
722
    ): ProjectedPoint {
723 1
        $latitude = $this->latitude->asRadians()->getValue();
724 1
        $longitude = $this->longitude->asRadians()->getValue();
725 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
726 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
727 1
        $heightOrigin = $projectionPlaneOriginHeight->asMetres()->getValue();
728 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
729 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
730
731 1
        $rho = $a * (1 - $e2) / (1 - $e2 * sin($latitude) ** 2) ** (3 / 2);
0 ignored issues
show
Unused Code introduced by
The assignment to $rho is dead and can be removed.
Loading history...
732 1
        $rhoOrigin = $a * (1 - $e2) / (1 - $e2 * sin($latitudeOrigin) ** 2) ** (3 / 2);
733 1
        $rhoMid = $a * (1 - $e2) / (1 - $e2 * sin(($latitude + $latitudeOrigin) / 2) ** 2) ** (3 / 2);
734
735 1
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
736 1
        $nuOrigin = $a / sqrt(1 - $e2 * (sin($latitudeOrigin) ** 2));
737
738 1
        $A = 1 + $heightOrigin / $nuOrigin;
739 1
        $B = tan($latitudeOrigin) / (2 * $rhoOrigin * $nuOrigin);
740 1
        $G = 1 + $heightOrigin / $rhoMid;
741
742 1
        $easting = $falseEasting->asMetres()->getValue() + $A * $nu * cos($latitude) * ($longitude - $longitudeOrigin);
743 1
        $northing = $falseNorthing->asMetres()->getValue() + $G * $rhoOrigin * (($latitude - $latitudeOrigin) + ($B * ($longitude - $longitudeOrigin) ** 2 * $nu ** 2 * cos($latitude) ** 2));
744
745 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
746
    }
747
748
    /**
749
     * Equal Earth.
750
     */
751 1
    public function equalEarth(
752
        Projected $to,
753
        Angle $longitudeOfNaturalOrigin,
754
        Length $falseEasting,
755
        Length $falseNorthing
756
    ): ProjectedPoint {
757 1
        $latitude = $this->latitude->asRadians()->getValue();
758 1
        $longitude = $this->longitude->asRadians()->getValue();
759 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
760 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
761 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
762 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
763
764 1
        $q = (1 - $e2) * ((sin($latitude) / (1 - $e2 * sin($latitude) ** 2)) - (1 / (2 * $e) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude)))));
765 1
        $qP = (1 - $e2) * ((1 / (1 - $e2)) - (1 / (2 * $e) * log((1 - $e) / (1 + $e))));
766 1
        $beta = self::asin($q / $qP);
767 1
        $theta = self::asin(sin($beta) * sqrt(3) / 2);
768 1
        $Rq = $a * sqrt($qP / 2);
769
770 1
        $easting = $falseEasting->asMetres()->getValue() + ($Rq * 2 * ($longitude - $longitudeOrigin) * cos($theta)) / (sqrt(3) * (1.340264 - 0.243318 * $theta ** 2 + $theta ** 6 * (0.006251 + 0.034164 * $theta ** 2)));
771 1
        $northing = $falseNorthing->asMetres()->getValue() + $Rq * $theta * (1.340264 - 0.081106 * $theta ** 2 + $theta ** 6 * (0.000893 + 0.003796 * $theta ** 2));
772
773 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
774
    }
775
776
    /**
777
     * Equidistant Cylindrical
778
     * See method code 1029 for spherical development. See also Pseudo Plate Carree, method code 9825.
779
     */
780 1
    public function equidistantCylindrical(
781
        Projected $to,
782
        Angle $latitudeOf1stStandardParallel,
783
        Angle $longitudeOfNaturalOrigin,
784
        Length $falseEasting,
785
        Length $falseNorthing
786
    ): ProjectedPoint {
787 1
        $latitude = $this->latitude->asRadians()->getValue();
788 1
        $longitude = $this->longitude->asRadians()->getValue();
789 1
        $latitudeFirstParallel = $latitudeOf1stStandardParallel->asRadians()->getValue();
790 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
791 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
792 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
793 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
794 1
        $e4 = $e ** 4;
795 1
        $e6 = $e ** 6;
796 1
        $e8 = $e ** 8;
797 1
        $e10 = $e ** 10;
798 1
        $e12 = $e ** 12;
799 1
        $e14 = $e ** 14;
800
801 1
        $nu1 = $a / sqrt(1 - $e2 * sin($latitudeFirstParallel) ** 2);
802
803
        $M = $a * (
804 1
            (1 - 1 / 4 * $e2 - 3 / 64 * $e4 - 5 / 256 * $e6 - 175 / 16384 * $e8 - 441 / 65536 * $e10 - 4851 / 1048576 * $e12 - 14157 / 4194304 * $e14) * $latitude +
805 1
            (-3 / 8 * $e2 - 3 / 32 * $e4 - 45 / 1024 * $e6 - 105 / 4096 * $e8 - 2205 / 131072 * $e10 - 6237 / 524288 * $e12 - 297297 / 33554432 * $e14) * sin(2 * $latitude) +
806 1
            (15 / 256 * $e4 + 45 / 1024 * $e ** 6 + 525 / 16384 * $e ** 8 + 1575 / 65536 * $e10 + 155925 / 8388608 * $e12 + 495495 / 33554432 * $e14) * sin(4 * $latitude) +
807 1
            (-35 / 3072 * $e6 - 175 / 12288 * $e8 - 3675 / 262144 * $e10 - 13475 / 1048576 * $e12 - 385385 / 33554432 * $e14) * sin(6 * $latitude) +
808 1
            (315 / 131072 * $e8 + 2205 / 524288 * $e10 + 43659 / 8388608 * $e12 + 189189 / 33554432 * $e14) * sin(8 * $latitude) +
809 1
            (-693 / 1310720 * $e10 - 6537 / 5242880 * $e12 - 297297 / 167772160 * $e14) * sin(10 * $latitude) +
810 1
            (1001 / 8388608 * $e12 + 11011 / 33554432 * $e14) * sin(12 * $latitude) +
811 1
            (-6435 / 234881024 * $e ** 14) * sin(14 * $latitude)
812
        );
813
814 1
        $easting = $falseEasting->asMetres()->getValue() + $nu1 * cos($latitudeFirstParallel) * ($longitude - $longitudeOrigin);
815 1
        $northing = $falseNorthing->asMetres()->getValue() + $M;
816
817 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
818
    }
819
820
    /**
821
     * Guam Projection
822
     * Simplified form of Oblique Azimuthal Equidistant projection method.
823
     */
824 1
    public function guamProjection(
825
        Projected $to,
826
        Angle $latitudeOfNaturalOrigin,
827
        Angle $longitudeOfNaturalOrigin,
828
        Length $falseEasting,
829
        Length $falseNorthing
830
    ): ProjectedPoint {
831 1
        $latitude = $this->latitude->asRadians()->getValue();
832 1
        $longitude = $this->longitude->asRadians()->getValue();
833 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
834 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
835 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
836 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
837 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
838 1
        $e4 = $e ** 4;
839 1
        $e6 = $e ** 6;
840
841 1
        $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));
842 1
        $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));
843 1
        $x = ($a * ($longitude - $longitudeOrigin) * cos($latitude)) / sqrt(1 - $e2 * sin($latitude) ** 2);
844
845 1
        $easting = $falseEasting->asMetres()->getValue() + $x;
846 1
        $northing = $falseNorthing->asMetres()->getValue() + $M - $MO + ($x ** 2 * tan($latitude) * sqrt(1 - $e2 * sin($latitude) ** 2) / (2 * $a));
847
848 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
849
    }
850
851
    /**
852
     * Krovak.
853
     */
854 4
    public function krovak(
855
        Projected $to,
856
        Angle $latitudeOfProjectionCentre,
857
        Angle $longitudeOfOrigin,
858
        Angle $coLatitudeOfConeAxis,
859
        Angle $latitudeOfPseudoStandardParallel,
860
        Scale $scaleFactorOnPseudoStandardParallel,
861
        Length $falseEasting,
862
        Length $falseNorthing
863
    ): ProjectedPoint {
864 4
        $longitudeOffset = $to->getDatum()->getPrimeMeridian()->getGreenwichLongitude()->asRadians()->getValue() - $this->getCRS()->getDatum()->getPrimeMeridian()->getGreenwichLongitude()->asRadians()->getValue();
865 4
        $latitude = $this->latitude->asRadians()->getValue();
866 4
        $longitude = $this->longitude->asRadians()->getValue() - $longitudeOffset;
867 4
        $latitudeC = $latitudeOfProjectionCentre->asRadians()->getValue();
868 4
        $longitudeO = $longitudeOfOrigin->asRadians()->getValue();
869 4
        $alphaC = $coLatitudeOfConeAxis->asRadians()->getValue();
870 4
        $latitudeP = $latitudeOfPseudoStandardParallel->asRadians()->getValue();
871 4
        $kP = $scaleFactorOnPseudoStandardParallel->asUnity()->getValue();
872 4
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
873 4
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
874 4
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
875
876 4
        $A = $a * sqrt(1 - $e2) / (1 - $e2 * sin($latitudeC) ** 2);
877 4
        $B = sqrt(1 + $e2 * cos($latitudeC) ** 4 / (1 - $e2));
878 4
        $upsilonO = self::asin(sin($latitudeC) / $B);
879 4
        $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);
880 4
        $n = sin($latitudeP);
881 4
        $rO = $kP * $A / tan($latitudeP);
882
883 4
        $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);
884 4
        $V = $B * ($longitudeO - $longitude);
885 4
        $T = self::asin(cos($alphaC) * sin($U) + sin($alphaC) * cos($U) * cos($V));
886 4
        $D = atan2(cos($U) * sin($V) / cos($T), ((cos($alphaC) * sin($T) - sin($U)) / (sin($alphaC) * cos($T))));
887 4
        $theta = $n * $D;
888 4
        $r = $rO * tan(M_PI / 4 + $latitudeP / 2) ** $n / tan($T / 2 + M_PI / 4) ** $n;
889 4
        $X = $r * cos($theta);
890 4
        $Y = $r * sin($theta);
891
892 4
        $westing = $Y + $falseEasting->asMetres()->getValue();
893 4
        $southing = $X + $falseNorthing->asMetres()->getValue();
894
895 4
        return ProjectedPoint::create(new Metre(-$westing), new Metre(-$southing), new Metre($westing), new Metre($southing), $to, $this->epoch);
896
    }
897
898
    /**
899
     * Krovak Modified
900
     * Incorporates a polynomial transformation which is defined to be exact and for practical purposes is considered
901
     * to be a map projection.
902
     */
903 2
    public function krovakModified(
904
        Projected $to,
905
        Angle $latitudeOfProjectionCentre,
906
        Angle $longitudeOfOrigin,
907
        Angle $coLatitudeOfConeAxis,
908
        Angle $latitudeOfPseudoStandardParallel,
909
        Scale $scaleFactorOnPseudoStandardParallel,
910
        Length $falseEasting,
911
        Length $falseNorthing,
912
        Length $ordinate1OfEvaluationPoint,
913
        Length $ordinate2OfEvaluationPoint,
914
        Coefficient $C1,
915
        Coefficient $C2,
916
        Coefficient $C3,
917
        Coefficient $C4,
918
        Coefficient $C5,
919
        Coefficient $C6,
920
        Coefficient $C7,
921
        Coefficient $C8,
922
        Coefficient $C9,
923
        Coefficient $C10
924
    ): ProjectedPoint {
925 2
        $asKrovak = $this->krovak($to, $latitudeOfProjectionCentre, $longitudeOfOrigin, $coLatitudeOfConeAxis, $latitudeOfPseudoStandardParallel, $scaleFactorOnPseudoStandardParallel, new Metre(0), new Metre(0));
926
927 2
        $westing = $asKrovak->getWesting()->asMetres()->getValue();
928 2
        $southing = $asKrovak->getSouthing()->asMetres()->getValue();
929 2
        $c1 = $C1->asUnity()->getValue();
930 2
        $c2 = $C2->asUnity()->getValue();
931 2
        $c3 = $C3->asUnity()->getValue();
932 2
        $c4 = $C4->asUnity()->getValue();
933 2
        $c5 = $C5->asUnity()->getValue();
934 2
        $c6 = $C6->asUnity()->getValue();
935 2
        $c7 = $C7->asUnity()->getValue();
936 2
        $c8 = $C8->asUnity()->getValue();
937 2
        $c9 = $C9->asUnity()->getValue();
938 2
        $c10 = $C10->asUnity()->getValue();
939
940 2
        $Xr = $southing - $ordinate1OfEvaluationPoint->asMetres()->getValue();
941 2
        $Yr = $westing - $ordinate2OfEvaluationPoint->asMetres()->getValue();
942
943 2
        $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);
944 2
        $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);
945
946 2
        $westing += $falseEasting->asMetres()->getValue() - $dY;
947 2
        $southing += $falseNorthing->asMetres()->getValue() - $dX;
948
949 2
        return ProjectedPoint::create(new Metre(-$westing), new Metre(-$southing), new Metre($westing), new Metre($southing), $to, $this->epoch);
950
    }
951
952
    /**
953
     * Lambert Azimuthal Equal Area
954
     * This is the ellipsoidal form of the projection.
955
     */
956 1
    public function lambertAzimuthalEqualArea(
957
        Projected $to,
958
        Angle $latitudeOfNaturalOrigin,
959
        Angle $longitudeOfNaturalOrigin,
960
        Length $falseEasting,
961
        Length $falseNorthing
962
    ): ProjectedPoint {
963 1
        $latitude = $this->latitude->asRadians()->getValue();
964 1
        $longitude = $this->longitude->asRadians()->getValue();
965 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
966 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
967 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
968 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
969 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
970
971 1
        $q = (1 - $e2) * ((sin($latitude) / (1 - $e2 * sin($latitude) ** 2)) - ((1 / (2 * $e)) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude)))));
972 1
        $qO = (1 - $e2) * ((sin($latitudeOrigin) / (1 - $e2 * sin($latitudeOrigin) ** 2)) - ((1 / (2 * $e)) * log((1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin)))));
973 1
        $qP = (1 - $e2) * ((1 / (1 - $e2)) - ((1 / (2 * $e)) * log((1 - $e) / (1 + $e))));
974 1
        $beta = self::asin($q / $qP);
975 1
        $betaO = self::asin($qO / $qP);
976 1
        $Rq = $a * sqrt($qP / 2);
977 1
        $B = $Rq * sqrt(2 / (1 + sin($betaO) * sin($beta) + (cos($betaO) * cos($beta) * cos($longitude - $longitudeOrigin))));
978 1
        $D = $a * (cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2)) / ($Rq * cos($betaO));
979
980 1
        $easting = $falseEasting->asMetres()->getValue() + (($B * $D) * (cos($beta) * sin($longitude - $longitudeOrigin)));
981 1
        $northing = $falseNorthing->asMetres()->getValue() + ($B / $D) * ((cos($betaO) * sin($beta)) - (sin($betaO) * cos($beta) * cos($longitude - $longitudeOrigin)));
982
983 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
984
    }
985
986
    /**
987
     * Lambert Azimuthal Equal Area (Spherical)
988
     * This is the spherical form of the projection.  See coordinate operation method Lambert Azimuthal Equal Area
989
     * (code 9820) for ellipsoidal form.  Differences of several tens of metres result from comparison of the two
990
     * methods.
991
     */
992 1
    public function lambertAzimuthalEqualAreaSpherical(
993
        Projected $to,
994
        Angle $latitudeOfNaturalOrigin,
995
        Angle $longitudeOfNaturalOrigin,
996
        Length $falseEasting,
997
        Length $falseNorthing
998
    ): ProjectedPoint {
999 1
        $latitude = $this->latitude->asRadians()->getValue();
1000 1
        $longitude = $this->longitude->asRadians()->getValue();
1001 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1002 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1003 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1004
1005 1
        $k = sqrt(2 / (1 + sin($latitudeOrigin) * sin($latitude) + cos($latitudeOrigin) * cos($latitude) * cos($longitude - $longitudeOrigin)));
1006
1007 1
        $easting = $falseEasting->asMetres()->getValue() + ($a * $k * cos($latitude) * sin($longitude - $longitudeOrigin));
1008 1
        $northing = $falseNorthing->asMetres()->getValue() + ($a * $k * (cos($latitudeOrigin) * sin($latitude) - sin($latitudeOrigin) * cos($latitude) * cos($longitude - $longitudeOrigin)));
1009
1010 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1011
    }
1012
1013
    /**
1014
     * Lambert Conic Conformal (1SP).
1015
     */
1016 1
    public function lambertConicConformal1SP(
1017
        Projected $to,
1018
        Angle $latitudeOfNaturalOrigin,
1019
        Angle $longitudeOfNaturalOrigin,
1020
        Scale $scaleFactorAtNaturalOrigin,
1021
        Length $falseEasting,
1022
        Length $falseNorthing
1023
    ): ProjectedPoint {
1024 1
        $latitude = $this->latitude->asRadians()->getValue();
1025 1
        $longitude = $this->longitude->asRadians()->getValue();
1026 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1027 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1028 1
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1029 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1030 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1031 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1032
1033 1
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
1034 1
        $tO = tan(M_PI / 4 - $latitudeOrigin / 2) / ((1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin))) ** ($e / 2);
1035 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1036 1
        $n = sin($latitudeOrigin);
1037 1
        $F = $mO / ($n * $tO ** $n);
1038 1
        $rO = $a * $F * $tO ** $n * $kO;
1039 1
        $r = $a * $F * $t ** $n * $kO;
1040 1
        $theta = $n * ($longitude - $longitudeOrigin);
1041
1042 1
        $easting = $falseEasting->asMetres()->getValue() + $r * sin($theta);
1043 1
        $northing = $falseNorthing->asMetres()->getValue() + $rO - $r * cos($theta);
1044
1045 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $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
        $latitude = $this->latitude->asRadians()->getValue();
1061
        $longitude = $this->longitude->asRadians()->getValue();
1062
        $latitudeNaturalOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1063
        $latitudeFalseOrigin = $latitudeOfFalseOrigin->asRadians()->getValue();
1064
        $longitudeFalseOrigin = $longitudeOfFalseOrigin->asRadians()->getValue();
1065
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1066
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1067
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1068
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1069
1070
        $mO = cos($latitudeNaturalOrigin) / sqrt(1 - $e2 * sin($latitudeNaturalOrigin) ** 2);
1071
        $tO = tan(M_PI / 4 - $latitudeNaturalOrigin / 2) / ((1 - $e * sin($latitudeNaturalOrigin)) / (1 + $e * sin($latitudeNaturalOrigin))) ** ($e / 2);
1072
        $tF = tan(M_PI / 4 - $latitudeFalseOrigin / 2) / ((1 - $e * sin($latitudeFalseOrigin)) / (1 + $e * sin($latitudeFalseOrigin))) ** ($e / 2);
1073
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1074
        $n = sin($latitudeNaturalOrigin);
1075
        $F = $mO / ($n * $tO ** $n);
1076
        $rF = $a * $F * $tF ** $n * $kO;
1077
        $r = $a * $F * $t ** $n * $kO;
1078
        $theta = $n * ($longitude - $longitudeFalseOrigin);
1079
1080
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $r * sin($theta);
1081
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rF - $r * cos($theta);
1082
1083
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1084
    }
1085
1086
    /**
1087
     * Lambert Conic Conformal (2SP Belgium)
1088
     * In 2000 this modification was replaced through use of the regular Lambert Conic Conformal (2SP) method [9802]
1089
     * with appropriately modified parameter values.
1090
     */
1091 1
    public function lambertConicConformal2SPBelgium(
1092
        Projected $to,
1093
        Angle $latitudeOfFalseOrigin,
1094
        Angle $longitudeOfFalseOrigin,
1095
        Angle $latitudeOf1stStandardParallel,
1096
        Angle $latitudeOf2ndStandardParallel,
1097
        Length $eastingAtFalseOrigin,
1098
        Length $northingAtFalseOrigin
1099
    ): ProjectedPoint {
1100 1
        $latitude = $this->latitude->asRadians()->getValue();
1101 1
        $longitude = $this->longitude->asRadians()->getValue();
1102 1
        $lambdaF = $longitudeOfFalseOrigin->asRadians()->getValue();
1103 1
        $phiF = $latitudeOfFalseOrigin->asRadians()->getValue();
1104 1
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
1105 1
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
1106 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1107 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1108 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1109
1110 1
        $m1 = cos($phi1) / sqrt(1 - $e2 * sin($phi1) ** 2);
1111 1
        $m2 = cos($phi2) / sqrt(1 - $e2 * sin($phi2) ** 2);
1112 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1113 1
        $t1 = tan(M_PI / 4 - $phi1 / 2) / ((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))) ** ($e / 2);
1114 1
        $t2 = tan(M_PI / 4 - $phi2 / 2) / ((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))) ** ($e / 2);
1115 1
        $tF = tan(M_PI / 4 - $phiF / 2) / ((1 - $e * sin($phiF)) / (1 + $e * sin($phiF))) ** ($e / 2);
1116 1
        $n = (log($m1) - log($m2)) / (log($t1) - log($t2));
1117 1
        $F = $m1 / ($n * $t1 ** $n);
1118 1
        $r = $a * $F * $t ** $n;
1119 1
        $rF = $a * $F * $tF ** $n;
1120 1
        if (is_nan($rF)) {
1121 1
            $rF = 0;
1122
        }
1123 1
        $theta = ($n * ($longitude - $lambdaF)) - (new ArcSecond(29.2985))->asRadians()->getValue();
1124
1125 1
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $r * sin($theta);
1126 1
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rF - $r * cos($theta);
1127
1128 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1129
    }
1130
1131
    /**
1132
     * Lambert Conic Conformal (2SP Michigan).
1133
     */
1134 1
    public function lambertConicConformal2SPMichigan(
1135
        Projected $to,
1136
        Angle $latitudeOfFalseOrigin,
1137
        Angle $longitudeOfFalseOrigin,
1138
        Angle $latitudeOf1stStandardParallel,
1139
        Angle $latitudeOf2ndStandardParallel,
1140
        Length $eastingAtFalseOrigin,
1141
        Length $northingAtFalseOrigin,
1142
        Scale $ellipsoidScalingFactor
1143
    ): ProjectedPoint {
1144 1
        $latitude = $this->latitude->asRadians()->getValue();
1145 1
        $longitude = $this->longitude->asRadians()->getValue();
1146 1
        $lambdaF = $longitudeOfFalseOrigin->asRadians()->getValue();
1147 1
        $phiF = $latitudeOfFalseOrigin->asRadians()->getValue();
1148 1
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
1149 1
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
1150 1
        $K = $ellipsoidScalingFactor->asUnity()->getValue();
1151 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1152 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1153 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1154
1155 1
        $m1 = cos($phi1) / sqrt(1 - $e2 * sin($phi1) ** 2);
1156 1
        $m2 = cos($phi2) / sqrt(1 - $e2 * sin($phi2) ** 2);
1157 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1158 1
        $t1 = tan(M_PI / 4 - $phi1 / 2) / ((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))) ** ($e / 2);
1159 1
        $t2 = tan(M_PI / 4 - $phi2 / 2) / ((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))) ** ($e / 2);
1160 1
        $tF = tan(M_PI / 4 - $phiF / 2) / ((1 - $e * sin($phiF)) / (1 + $e * sin($phiF))) ** ($e / 2);
1161 1
        $n = (log($m1) - log($m2)) / (log($t1) - log($t2));
1162 1
        $F = $m1 / ($n * $t1 ** $n);
1163 1
        $r = $a * $K * $F * $t ** $n;
1164 1
        $rF = $a * $K * $F * $tF ** $n;
1165 1
        $theta = $n * ($longitude - $lambdaF);
1166
1167 1
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $r * sin($theta);
1168 1
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rF - $r * cos($theta);
1169
1170 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1171
    }
1172
1173
    /**
1174
     * Lambert Conic Conformal (2SP).
1175
     */
1176 1
    public function lambertConicConformal2SP(
1177
        Projected $to,
1178
        Angle $latitudeOfFalseOrigin,
1179
        Angle $longitudeOfFalseOrigin,
1180
        Angle $latitudeOf1stStandardParallel,
1181
        Angle $latitudeOf2ndStandardParallel,
1182
        Length $eastingAtFalseOrigin,
1183
        Length $northingAtFalseOrigin
1184
    ): ProjectedPoint {
1185 1
        $latitude = $this->latitude->asRadians()->getValue();
1186 1
        $longitude = $this->longitude->asRadians()->getValue();
1187 1
        $lambdaF = $longitudeOfFalseOrigin->asRadians()->getValue();
1188 1
        $phiF = $latitudeOfFalseOrigin->asRadians()->getValue();
1189 1
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
1190 1
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
1191 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1192 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1193 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1194
1195 1
        $m1 = cos($phi1) / sqrt(1 - $e2 * sin($phi1) ** 2);
1196 1
        $m2 = cos($phi2) / sqrt(1 - $e2 * sin($phi2) ** 2);
1197 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1198 1
        $t1 = tan(M_PI / 4 - $phi1 / 2) / ((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))) ** ($e / 2);
1199 1
        $t2 = tan(M_PI / 4 - $phi2 / 2) / ((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))) ** ($e / 2);
1200 1
        $tF = tan(M_PI / 4 - $phiF / 2) / ((1 - $e * sin($phiF)) / (1 + $e * sin($phiF))) ** ($e / 2);
1201 1
        $n = (log($m1) - log($m2)) / (log($t1) - log($t2));
1202 1
        $F = $m1 / ($n * $t1 ** $n);
1203 1
        $r = $a * $F * $t ** $n;
1204 1
        $rF = $a * $F * $tF ** $n;
1205 1
        $theta = $n * ($longitude - $lambdaF);
1206
1207 1
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $r * sin($theta);
1208 1
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rF - $r * cos($theta);
1209
1210 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1211
    }
1212
1213
    /**
1214
     * Lambert Conic Conformal (West Orientated).
1215
     */
1216
    public function lambertConicConformalWestOrientated(
1217
        Projected $to,
1218
        Angle $latitudeOfNaturalOrigin,
1219
        Angle $longitudeOfNaturalOrigin,
1220
        Scale $scaleFactorAtNaturalOrigin,
1221
        Length $falseEasting,
1222
        Length $falseNorthing
1223
    ): ProjectedPoint {
1224
        $latitude = $this->latitude->asRadians()->getValue();
1225
        $longitude = $this->longitude->asRadians()->getValue();
1226
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1227
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1228
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1229
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1230
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1231
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1232
1233
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
1234
        $tO = tan(M_PI / 4 - $latitudeOrigin / 2) / ((1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin))) ** ($e / 2);
1235
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1236
        $n = sin($latitudeOrigin);
1237
        $F = $mO / ($n * $tO ** $n);
1238
        $rO = $a * $F * $tO ** $n ** $kO;
1239
        $r = $a * $F * $t ** $n ** $kO;
1240
        $theta = $n * ($longitude - $longitudeOrigin);
1241
1242
        $westing = $falseEasting->asMetres()->getValue() - $r * sin($theta);
1243
        $northing = $falseNorthing->asMetres()->getValue() + $rO - $r * cos($theta);
1244
1245
        return ProjectedPoint::create(new Metre(-$westing), new Metre($northing), new Metre($westing), new Metre(-$northing), $to, $this->epoch);
1246
    }
1247
1248
    /**
1249
     * Lambert Conic Near-Conformal
1250
     * The Lambert Near-Conformal projection is derived from the Lambert Conformal Conic projection by truncating the
1251
     * series expansion of the projection formulae.
1252
     */
1253 1
    public function lambertConicNearConformal(
1254
        Projected $to,
1255
        Angle $latitudeOfNaturalOrigin,
1256
        Angle $longitudeOfNaturalOrigin,
1257
        Scale $scaleFactorAtNaturalOrigin,
1258
        Length $falseEasting,
1259
        Length $falseNorthing
1260
    ): ProjectedPoint {
1261 1
        $latitude = $this->latitude->asRadians()->getValue();
1262 1
        $longitude = $this->longitude->asRadians()->getValue();
1263 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1264 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1265 1
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1266 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1267 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1268 1
        $f = $this->crs->getDatum()->getEllipsoid()->getInverseFlattening();
1269
1270 1
        $n = $f / (2 - $f);
1271 1
        $rhoO = $a * (1 - $e2) / (1 - $e2 * sin($latitudeOrigin) ** 2) ** (3 / 2);
1272 1
        $nuO = $a / sqrt(1 - $e2 * (sin($latitudeOrigin) ** 2));
1273 1
        $A = 1 / (6 * $rhoO * $nuO);
1274 1
        $APrime = $a * (1 - $n + 5 * ($n ** 2 - $n ** 3) / 4 + 81 * ($n ** 4 - $n ** 5) / 64);
1275 1
        $BPrime = 3 * $a * ($n - $n ** 2 + 7 * ($n ** 3 - $n ** 4) / 8 + 55 * $n ** 5 / 64) / 2;
1276 1
        $CPrime = 15 * $a * ($n ** 2 - $n ** 3 + 3 * ($n ** 4 - $n ** 5) / 4) / 16;
1277 1
        $DPrime = 35 * $a * ($n ** 3 - $n ** 4 + 11 * $n ** 5 / 16) / 48;
1278 1
        $EPrime = 315 * $a * ($n ** 4 - $n ** 5) / 512;
1279 1
        $rO = $kO * $nuO / tan($latitudeOrigin);
1280 1
        $sO = $APrime * $latitudeOrigin - $BPrime * sin(2 * $latitudeOrigin) + $CPrime * sin(4 * $latitudeOrigin) - $DPrime * sin(6 * $latitudeOrigin) + $EPrime * sin(8 * $latitudeOrigin);
1281 1
        $s = $APrime * $latitude - $BPrime * sin(2 * $latitude) + $CPrime * sin(4 * $latitude) - $DPrime * sin(6 * $latitude) + $EPrime * sin(8 * $latitude);
1282 1
        $m = $s - $sO;
1283 1
        $M = $kO * ($m + $A * $m ** 3);
1284 1
        $r = $rO - $M;
1285 1
        $theta = ($longitude - $longitudeOrigin) * sin($latitudeOrigin);
1286
1287 1
        $easting = $falseEasting->asMetres()->getValue() + $r * sin($theta);
1288 1
        $northing = $falseNorthing->asMetres()->getValue() + $M + $r * sin($theta) * tan($theta / 2);
1289
1290 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1291
    }
1292
1293
    /**
1294
     * Lambert Cylindrical Equal Area
1295
     * This is the ellipsoidal form of the projection.
1296
     */
1297 1
    public function lambertCylindricalEqualArea(
1298
        Projected $to,
1299
        Angle $latitudeOf1stStandardParallel,
1300
        Angle $longitudeOfNaturalOrigin,
1301
        Length $falseEasting,
1302
        Length $falseNorthing
1303
    ): ProjectedPoint {
1304 1
        $latitude = $this->latitude->asRadians()->getValue();
1305 1
        $longitude = $this->longitude->asRadians()->getValue();
1306 1
        $latitudeFirstParallel = $latitudeOf1stStandardParallel->asRadians()->getValue();
1307 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1308 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1309 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1310 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1311
1312 1
        $k = cos($latitudeFirstParallel) / sqrt(1 - $e2 * sin($latitudeFirstParallel) ** 2);
1313 1
        $q = (1 - $e2) * ((sin($latitude) / (1 - $e2 * sin($latitude) ** 2)) - (1 / (2 * $e)) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))));
1314
1315 1
        $x = $a * $k * ($longitude - $longitudeOrigin);
1316 1
        $y = $a * $q / (2 * $k);
1317
1318 1
        $easting = $falseEasting->asMetres()->getValue() + $x;
1319 1
        $northing = $falseNorthing->asMetres()->getValue() + $y;
1320
1321 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1322
    }
1323
1324
    /**
1325
     * Modified Azimuthal Equidistant
1326
     * Modified form of Oblique Azimuthal Equidistant projection method developed for Polynesian islands. For the
1327
     * distances over which these projections are used (under 800km) this modification introduces no significant error.
1328
     */
1329 1
    public function modifiedAzimuthalEquidistant(
1330
        Projected $to,
1331
        Angle $latitudeOfNaturalOrigin,
1332
        Angle $longitudeOfNaturalOrigin,
1333
        Length $falseEasting,
1334
        Length $falseNorthing
1335
    ): ProjectedPoint {
1336 1
        $latitude = $this->latitude->asRadians()->getValue();
1337 1
        $longitude = $this->longitude->asRadians()->getValue();
1338 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1339 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1340 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1341 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1342 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1343
1344 1
        $nuO = $a / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
1345 1
        $nu = $a / sqrt(1 - $e2 * sin($latitude) ** 2);
1346 1
        $psi = atan((1 - $e2) * tan($latitude) + ($e2 * $nuO * sin($latitudeOrigin)) / ($nu * cos($latitude)));
1347 1
        $alpha = atan2(sin($longitude - $longitudeOrigin), (cos($latitudeOrigin) * tan($psi) - sin($latitudeOrigin) * cos($longitude - $longitudeOrigin)));
1348 1
        $G = $e * sin($latitudeOrigin) / sqrt(1 - $e2);
1349 1
        $H = $e * cos($latitudeOrigin) * cos($alpha) / sqrt(1 - $e2);
1350
1351 1
        if (sin($alpha) === 0.0) {
1352
            $s = self::asin(cos($latitudeOrigin) * sin($psi) - sin($latitudeOrigin) * cos($alpha)) * cos($alpha) / abs(cos($alpha));
1353
        } else {
1354 1
            $s = self::asin(sin($longitude - $longitudeOrigin) * cos($psi) / sin($alpha));
1355
        }
1356
1357 1
        $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));
1358
1359 1
        $easting = $falseEasting->asMetres()->getValue() + $c * sin($alpha);
1360 1
        $northing = $falseNorthing->asMetres()->getValue() + $c * cos($alpha);
1361
1362 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1363
    }
1364
1365
    /**
1366
     * Oblique Stereographic
1367
     * This is not the same as the projection method of the same name in USGS Professional Paper no. 1395, "Map
1368
     * Projections - A Working Manual" by John P. Snyder.
1369
     */
1370 1
    public function obliqueStereographic(
1371
        Projected $to,
1372
        Angle $latitudeOfNaturalOrigin,
1373
        Angle $longitudeOfNaturalOrigin,
1374
        Scale $scaleFactorAtNaturalOrigin,
1375
        Length $falseEasting,
1376
        Length $falseNorthing
1377
    ): ProjectedPoint {
1378 1
        $latitude = $this->latitude->asRadians()->getValue();
1379 1
        $longitude = $this->longitude->asRadians()->getValue();
1380 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1381 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1382 1
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1383 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1384 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1385 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1386
1387 1
        $rhoOrigin = $a * (1 - $e2) / (1 - $e2 * sin($latitudeOrigin) ** 2) ** (3 / 2);
1388 1
        $nuOrigin = $a / sqrt(1 - $e2 * (sin($latitudeOrigin) ** 2));
1389 1
        $R = sqrt($rhoOrigin * $nuOrigin);
1390
1391 1
        $n = sqrt(1 + ($e2 * cos($latitudeOrigin) ** 4 / (1 - $e2)));
1392 1
        $S1 = (1 + sin($latitudeOrigin)) / (1 - sin($latitudeOrigin));
1393 1
        $S2 = (1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin));
1394 1
        $w1 = ($S1 * ($S2 ** $e)) ** $n;
1395 1
        $c = (($n + sin($latitudeOrigin)) * (1 - ($w1 - 1) / ($w1 + 1))) / (($n - sin($latitudeOrigin)) * (1 + ($w1 - 1) / ($w1 + 1)));
1396 1
        $w2 = $c * $w1;
1397 1
        $chiOrigin = self::asin(($w2 - 1) / ($w2 + 1));
1398
1399 1
        $lambda = $n * ($longitude - $longitudeOrigin) + $longitudeOrigin;
1400
1401 1
        $Sa = (1 + sin($latitude)) / (1 - sin($latitude));
1402 1
        $Sb = (1 - $e * sin($latitude)) / (1 + $e * sin($latitude));
1403 1
        $w = $c * ($Sa * ($Sb ** $e)) ** $n;
1404 1
        $chi = self::asin(($w - 1) / ($w + 1));
1405
1406 1
        $B = (1 + sin($chi) * sin($chiOrigin) + cos($chi) * cos($chiOrigin) * cos($lambda - $longitudeOrigin));
1407
1408 1
        $easting = $falseEasting->asMetres()->getValue() + 2 * $R * $kO * cos($chi) * sin($lambda - $longitudeOrigin) / $B;
1409 1
        $northing = $falseNorthing->asMetres()->getValue() + 2 * $R * $kO * (sin($chi) * cos($chiOrigin) - cos($chi) * sin($chiOrigin) * cos($lambda - $longitudeOrigin)) / $B;
1410
1411 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1412
    }
1413
1414
    /**
1415
     * Polar Stereographic (variant A)
1416
     * Latitude of natural origin must be either 90 degrees or -90 degrees (or equivalent in alternative angle unit).
1417
     */
1418 1
    public function polarStereographicVariantA(
1419
        Projected $to,
1420
        Angle $latitudeOfNaturalOrigin,
1421
        Angle $longitudeOfNaturalOrigin,
1422
        Scale $scaleFactorAtNaturalOrigin,
1423
        Length $falseEasting,
1424
        Length $falseNorthing
1425
    ): ProjectedPoint {
1426 1
        $latitude = $this->latitude->asRadians()->getValue();
1427 1
        $longitude = $this->longitude->asRadians()->getValue();
1428 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1429 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1430 1
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1431 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1432 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1433
1434 1
        if ($latitudeOrigin < 0) {
1435
            $t = tan(M_PI / 4 + $latitude / 2) / (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1436
        } else {
1437 1
            $t = tan(M_PI / 4 - $latitude / 2) * (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1438
        }
1439 1
        $rho = 2 * $a * $kO * $t / sqrt((1 + $e) ** (1 + $e) * (1 - $e) ** (1 - $e));
1440
1441 1
        $theta = $longitude - $longitudeOrigin;
1442 1
        $dE = $rho * sin($theta);
1443 1
        $dN = $rho * cos($theta);
1444
1445 1
        $easting = $falseEasting->asMetres()->getValue() + $dE;
1446 1
        if ($latitudeOrigin < 0) {
1447
            $northing = $falseNorthing->asMetres()->getValue() + $dN;
1448
        } else {
1449 1
            $northing = $falseNorthing->asMetres()->getValue() - $dN;
1450
        }
1451
1452 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1453
    }
1454
1455
    /**
1456
     * Polar Stereographic (variant B).
1457
     */
1458 1
    public function polarStereographicVariantB(
1459
        Projected $to,
1460
        Angle $latitudeOfStandardParallel,
1461
        Angle $longitudeOfOrigin,
1462
        Length $falseEasting,
1463
        Length $falseNorthing
1464
    ): ProjectedPoint {
1465 1
        $latitude = $this->latitude->asRadians()->getValue();
1466 1
        $longitude = $this->longitude->asRadians()->getValue();
1467 1
        $firstStandardParallel = $latitudeOfStandardParallel->asRadians()->getValue();
1468 1
        $longitudeOrigin = $longitudeOfOrigin->asRadians()->getValue();
1469 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1470 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1471 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1472
1473 1
        if ($firstStandardParallel < 0) {
1474 1
            $tF = tan(M_PI / 4 + $firstStandardParallel / 2) / (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1475 1
            $t = tan(M_PI / 4 + $latitude / 2) / (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1476
        } else {
1477
            $tF = tan(M_PI / 4 - $firstStandardParallel / 2) * (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1478
            $t = tan(M_PI / 4 - $latitude / 2) * (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1479
        }
1480 1
        $mF = cos($firstStandardParallel) / sqrt(1 - $e2 * sin($firstStandardParallel) ** 2);
1481 1
        $kO = $mF * sqrt((1 + $e) ** (1 + $e) * (1 - $e) ** (1 - $e)) / (2 * $tF);
1482
1483 1
        $rho = 2 * $a * $kO * $t / sqrt((1 + $e) ** (1 + $e) * (1 - $e) ** (1 - $e));
1484
1485 1
        $theta = $longitude - $longitudeOrigin;
1486 1
        $dE = $rho * sin($theta);
1487 1
        $dN = $rho * cos($theta);
1488
1489 1
        $easting = $falseEasting->asMetres()->getValue() + $dE;
1490 1
        if ($firstStandardParallel < 0) {
1491 1
            $northing = $falseNorthing->asMetres()->getValue() + $dN;
1492
        } else {
1493
            $northing = $falseNorthing->asMetres()->getValue() - $dN;
1494
        }
1495
1496 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1497
    }
1498
1499
    /**
1500
     * Polar Stereographic (variant C).
1501
     */
1502 1
    public function polarStereographicVariantC(
1503
        Projected $to,
1504
        Angle $latitudeOfStandardParallel,
1505
        Angle $longitudeOfOrigin,
1506
        Length $eastingAtFalseOrigin,
1507
        Length $northingAtFalseOrigin
1508
    ): ProjectedPoint {
1509 1
        $latitude = $this->latitude->asRadians()->getValue();
1510 1
        $longitude = $this->longitude->asRadians()->getValue();
1511 1
        $firstStandardParallel = $latitudeOfStandardParallel->asRadians()->getValue();
1512 1
        $longitudeOrigin = $longitudeOfOrigin->asRadians()->getValue();
1513 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1514 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1515 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1516
1517 1
        if ($firstStandardParallel < 0) {
1518 1
            $tF = tan(M_PI / 4 + $firstStandardParallel / 2) / (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1519 1
            $t = tan(M_PI / 4 + $latitude / 2) / (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1520
        } else {
1521
            $tF = tan(M_PI / 4 - $firstStandardParallel / 2) * (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1522
            $t = tan(M_PI / 4 - $latitude / 2) * (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1523
        }
1524 1
        $mF = cos($firstStandardParallel) / sqrt(1 - $e2 * sin($firstStandardParallel) ** 2);
1525
1526 1
        $rhoF = $a * $mF;
1527 1
        $rho = $rhoF * $t / $tF;
1528
1529 1
        $theta = $longitude - $longitudeOrigin;
1530 1
        $dE = $rho * sin($theta);
1531 1
        $dN = $rho * cos($theta);
1532
1533 1
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $dE;
1534 1
        if ($firstStandardParallel < 0) {
1535 1
            $northing = $northingAtFalseOrigin->asMetres()->getValue() - $rhoF + $dN;
1536
        } else {
1537
            $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rhoF - $dN;
1538
        }
1539
1540 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1541
    }
1542
1543
    /**
1544
     * Popular Visualisation Pseudo Mercator
1545
     * Applies spherical formulas to the ellipsoid. As such does not have the properties of a true Mercator projection.
1546
     */
1547 1
    public function popularVisualisationPseudoMercator(
1548
        Projected $to,
1549
        Angle $latitudeOfNaturalOrigin,
1550
        Angle $longitudeOfNaturalOrigin,
1551
        Length $falseEasting,
1552
        Length $falseNorthing
1553
    ): ProjectedPoint {
1554 1
        $latitude = $this->latitude->asRadians()->getValue();
1555 1
        $longitude = $this->longitude->asRadians()->getValue();
1556 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
0 ignored issues
show
Unused Code introduced by
The assignment to $latitudeOrigin is dead and can be removed.
Loading history...
1557 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1558 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1559
1560 1
        $easting = $falseEasting->asMetres()->getValue() + $a * ($longitude - $longitudeOrigin);
1561 1
        $northing = $falseNorthing->asMetres()->getValue() + $a * log(tan(M_PI / 4 + $latitude / 2));
1562
1563 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1564
    }
1565
1566
    /**
1567
     * Mercator (variant A)
1568
     * Note that in these formulas the parameter latitude of natural origin (latO) is not used. However for this
1569
     * Mercator (variant A) method the EPSG dataset includes this parameter, which must have a value of zero, for
1570
     * completeness in CRS labelling.
1571
     */
1572 2
    public function mercatorVariantA(
1573
        Projected $to,
1574
        Angle $latitudeOfNaturalOrigin,
1575
        Angle $longitudeOfNaturalOrigin,
1576
        Scale $scaleFactorAtNaturalOrigin,
1577
        Length $falseEasting,
1578
        Length $falseNorthing
1579
    ): ProjectedPoint {
1580 2
        $latitude = $this->latitude->asRadians()->getValue();
1581 2
        $longitude = $this->longitude->asRadians()->getValue();
1582
1583 2
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
0 ignored issues
show
Unused Code introduced by
The assignment to $latitudeOrigin is dead and can be removed.
Loading history...
1584 2
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1585 2
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1586
1587 2
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1588 2
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1589
1590 2
        $easting = $falseEasting->asMetres()->getValue() + $a * $kO * ($longitude - $longitudeOrigin);
1591 2
        $northing = $falseNorthing->asMetres()->getValue() + $a * $kO * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1592
1593 2
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1594
    }
1595
1596
    /**
1597
     * Mercator (variant B)
1598
     * Used for most nautical charts.
1599
     */
1600 1
    public function mercatorVariantB(
1601
        Projected $to,
1602
        Angle $latitudeOf1stStandardParallel,
1603
        Angle $longitudeOfNaturalOrigin,
1604
        Length $falseEasting,
1605
        Length $falseNorthing
1606
    ): ProjectedPoint {
1607 1
        $latitude = $this->latitude->asRadians()->getValue();
1608 1
        $longitude = $this->longitude->asRadians()->getValue();
1609 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1610 1
        $firstStandardParallel = $latitudeOf1stStandardParallel->asRadians()->getValue();
1611 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1612 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1613 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1614
1615 1
        $kO = cos($firstStandardParallel) / sqrt(1 - $e2 * sin($firstStandardParallel) ** 2);
1616
1617 1
        $easting = $falseEasting->asMetres()->getValue() + $a * $kO * ($longitude - $longitudeOrigin);
1618 1
        $northing = $falseNorthing->asMetres()->getValue() + $a * $kO * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1619
1620 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1621
    }
1622
1623
    /**
1624
     * Longitude rotation
1625
     * This transformation allows calculation of the longitude of a point in the target system by adding the parameter
1626
     * value to the longitude value of the point in the source system.
1627
     */
1628 1
    public function longitudeRotation(
1629
        Geographic $to,
1630
        Angle $longitudeOffset
1631
    ): self {
1632 1
        $newLongitude = $this->longitude->add($longitudeOffset);
1633
1634 1
        return static::create($this->latitude, $newLongitude, $this->height, $to, $this->epoch);
1635
    }
1636
1637
    /**
1638
     * Hotine Oblique Mercator (variant A).
1639
     */
1640 1
    public function obliqueMercatorHotineVariantA(
1641
        Projected $to,
1642
        Angle $latitudeOfProjectionCentre,
1643
        Angle $longitudeOfProjectionCentre,
1644
        Angle $azimuthOfInitialLine,
1645
        Angle $angleFromRectifiedToSkewGrid,
1646
        Scale $scaleFactorOnInitialLine,
1647
        Length $falseEasting,
1648
        Length $falseNorthing
1649
    ): ProjectedPoint {
1650 1
        $latitude = $this->latitude->asRadians()->getValue();
1651 1
        $longitude = $this->longitude->asRadians()->getValue();
1652 1
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1653 1
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1654 1
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1655 1
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1656 1
        $gammaC = $angleFromRectifiedToSkewGrid->asRadians()->getValue();
1657 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1658 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1659 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1660
1661 1
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1662 1
        $A = $a * $B * $kC * sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2);
1663 1
        $tO = tan(M_PI / 4 - $latC / 2) / ((1 - $e * sin($latC)) / (1 + $e * sin($latC))) ** ($e / 2);
1664 1
        $D = $B * sqrt((1 - $e2)) / (cos($latC) * sqrt(1 - $e2 * sin($latC) ** 2));
1665 1
        $DD = max(1, $D ** 2);
1666 1
        $F = $D + sqrt($DD - 1) * static::sign($latC);
1667 1
        $H = $F * ($tO) ** $B;
1668 1
        $G = ($F - 1 / $F) / 2;
1669 1
        $gammaO = self::asin(sin($alphaC) / $D);
1670 1
        $lonO = $lonC - (self::asin($G * tan($gammaO))) / $B;
1671
1672 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1673 1
        $Q = $H / $t ** $B;
1674 1
        $S = ($Q - 1 / $Q) / 2;
1675 1
        $T = ($Q + 1 / $Q) / 2;
1676 1
        $V = sin($B * ($longitude - $lonO));
1677 1
        $U = (-$V * cos($gammaO) + $S * sin($gammaO)) / $T;
1678 1
        $v = $A * log((1 - $U) / (1 + $U)) / (2 * $B);
1679 1
        $u = $A * atan2(($S * cos($gammaO) + $V * sin($gammaO)), cos($B * ($longitude - $lonO))) / $B;
1680
1681 1
        $easting = $v * cos($gammaC) + $u * sin($gammaC) + $falseEasting->asMetres()->getValue();
1682 1
        $northing = $u * cos($gammaC) - $v * sin($gammaC) + $falseNorthing->asMetres()->getValue();
1683
1684 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1685
    }
1686
1687
    /**
1688
     * Hotine Oblique Mercator (variant B).
1689
     */
1690 1
    public function obliqueMercatorHotineVariantB(
1691
        Projected $to,
1692
        Angle $latitudeOfProjectionCentre,
1693
        Angle $longitudeOfProjectionCentre,
1694
        Angle $azimuthOfInitialLine,
1695
        Angle $angleFromRectifiedToSkewGrid,
1696
        Scale $scaleFactorOnInitialLine,
1697
        Length $eastingAtProjectionCentre,
1698
        Length $northingAtProjectionCentre
1699
    ): ProjectedPoint {
1700 1
        $latitude = $this->latitude->asRadians()->getValue();
1701 1
        $longitude = $this->longitude->asRadians()->getValue();
1702 1
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1703 1
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1704 1
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1705 1
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1706 1
        $gammaC = $angleFromRectifiedToSkewGrid->asRadians()->getValue();
1707 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1708 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1709 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1710
1711 1
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1712 1
        $A = $a * $B * $kC * sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2);
1713 1
        $tO = tan(M_PI / 4 - $latC / 2) / ((1 - $e * sin($latC)) / (1 + $e * sin($latC))) ** ($e / 2);
1714 1
        $D = $B * sqrt((1 - $e2)) / (cos($latC) * sqrt(1 - $e2 * sin($latC) ** 2));
1715 1
        $DD = max(1, $D ** 2);
1716 1
        $F = $D + sqrt($DD - 1) * static::sign($latC);
1717 1
        $H = $F * ($tO) ** $B;
1718 1
        $G = ($F - 1 / $F) / 2;
1719 1
        $gammaO = self::asin(sin($alphaC) / $D);
1720 1
        $lonO = $lonC - (self::asin($G * tan($gammaO))) / $B;
1721 1
        $vC = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $vC is dead and can be removed.
Loading history...
1722 1
        if ($alphaC === M_PI / 2) {
1723
            $uC = $A * ($lonC - $lonO);
1724
        } else {
1725 1
            $uC = ($A / $B) * atan2(sqrt($DD - 1), cos($alphaC)) * static::sign($latC);
1726
        }
1727
1728 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1729 1
        $Q = $H / $t ** $B;
1730 1
        $S = ($Q - 1 / $Q) / 2;
1731 1
        $T = ($Q + 1 / $Q) / 2;
1732 1
        $V = sin($B * ($longitude - $lonO));
1733 1
        $U = (-$V * cos($gammaO) + $S * sin($gammaO)) / $T;
1734 1
        $v = $A * log((1 - $U) / (1 + $U)) / (2 * $B);
1735
1736 1
        if ($alphaC === M_PI / 2) {
1737
            if ($longitude === $lonC) {
1738
                $u = 0;
1739
            } else {
1740
                $u = ($A * atan(($S * cos($gammaO) + $V * sin($gammaO)) / cos($B * ($longitude - $lonO))) / $B) - (abs($uC) * static::sign($latC) * static::sign($lonC - $longitude));
1741
            }
1742
        } else {
1743 1
            $u = ($A * atan2(($S * cos($gammaO) + $V * sin($gammaO)), cos($B * ($longitude - $lonO))) / $B) - (abs($uC) * static::sign($latC));
1744
        }
1745
1746 1
        $easting = $v * cos($gammaC) + $u * sin($gammaC) + $eastingAtProjectionCentre->asMetres()->getValue();
1747 1
        $northing = $u * cos($gammaC) - $v * sin($gammaC) + $northingAtProjectionCentre->asMetres()->getValue();
1748
1749 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1750
    }
1751
1752
    /**
1753
     * Laborde Oblique Mercator.
1754
     */
1755 1
    public function obliqueMercatorLaborde(
1756
        Projected $to,
1757
        Angle $latitudeOfProjectionCentre,
1758
        Angle $longitudeOfProjectionCentre,
1759
        Angle $azimuthOfInitialLine,
1760
        Scale $scaleFactorOnInitialLine,
1761
        Length $falseEasting,
1762
        Length $falseNorthing
1763
    ): ProjectedPoint {
1764 1
        $latitude = $this->latitude->asRadians()->getValue();
1765 1
        $longitude = $this->longitude->asRadians()->getValue();
1766 1
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1767 1
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1768 1
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1769 1
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1770 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1771 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1772 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1773
1774 1
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1775 1
        $latS = self::asin(sin($latC) / $B);
1776 1
        $R = $a * $kC * (sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2));
1777 1
        $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));
1778
1779 1
        $L = $B * ($longitude - $lonC);
1780 1
        $q = $C + $B * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1781 1
        $P = 2 * atan(M_E ** $q) - M_PI / 2;
1782 1
        $U = cos($P) * cos($L) * cos($latS) + sin($P) * sin($latS);
1783 1
        $V = cos($P) * cos($L) * sin($latS) - sin($P) * cos($latS);
1784 1
        $W = cos($P) * sin($L);
1785 1
        $d = sqrt($U ** 2 + $V ** 2);
1786 1
        if ($d === 0.0) {
1787
            $LPrime = 0;
1788
            $PPrime = static::sign($W) * M_PI / 2;
1789
        } else {
1790 1
            $LPrime = 2 * atan($V / ($U + $d));
1791 1
            $PPrime = atan($W / $d);
1792
        }
1793 1
        $H = new ComplexNumber(-$LPrime, log(tan(M_PI / 4 + $PPrime / 2)));
1794 1
        $G = (new ComplexNumber(1 - cos(2 * $alphaC), sin(2 * $alphaC)))->divide(new ComplexNumber(12, 0));
1795
1796 1
        $easting = $falseEasting->asMetres()->getValue() + $R * $H->pow(3)->multiply($G)->add($H)->getImaginary();
1797 1
        $northing = $falseNorthing->asMetres()->getValue() + $R * $H->pow(3)->multiply($G)->add($H)->getReal();
1798
1799 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1800
    }
1801
1802
    /**
1803
     * Transverse Mercator.
1804
     */
1805 11
    public function transverseMercator(
1806
        Projected $to,
1807
        Angle $latitudeOfNaturalOrigin,
1808
        Angle $longitudeOfNaturalOrigin,
1809
        Scale $scaleFactorAtNaturalOrigin,
1810
        Length $falseEasting,
1811
        Length $falseNorthing
1812
    ): ProjectedPoint {
1813 11
        $latitude = $this->latitude->asRadians()->getValue();
1814 11
        $longitude = $this->longitude->asRadians()->getValue();
1815 11
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1816 11
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1817 11
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1818 11
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1819 11
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1820 11
        $f = $this->crs->getDatum()->getEllipsoid()->getInverseFlattening();
1821
1822 11
        $n = $f / (2 - $f);
1823 11
        $B = ($a / (1 + $n)) * (1 + $n ** 2 / 4 + $n ** 4 / 64);
1824
1825 11
        $h1 = $n / 2 - (2 / 3) * $n ** 2 + (5 / 16) * $n ** 3 + (41 / 180) * $n ** 4;
1826 11
        $h2 = (13 / 48) * $n ** 2 - (3 / 5) * $n ** 3 + (557 / 1440) * $n ** 4;
1827 11
        $h3 = (61 / 240) * $n ** 3 - (103 / 140) * $n ** 4;
1828 11
        $h4 = (49561 / 161280) * $n ** 4;
1829
1830 11
        if ($latitudeOrigin === 0.0) {
0 ignored issues
show
introduced by
The condition $latitudeOrigin === 0.0 is always false.
Loading history...
1831 9
            $mO = 0;
1832 2
        } elseif ($latitudeOrigin === M_PI / 2) {
1833
            $mO = $B * M_PI / 2;
1834 2
        } elseif ($latitudeOrigin === -M_PI / 2) {
1835
            $mO = $B * -M_PI / 2;
1836
        } else {
1837 2
            $qO = asinh(tan($latitudeOrigin)) - ($e * atanh($e * sin($latitudeOrigin)));
1838 2
            $betaO = atan(sinh($qO));
1839 2
            $xiO0 = self::asin(sin($betaO));
1840 2
            $xiO1 = $h1 * sin(2 * $xiO0);
1841 2
            $xiO2 = $h2 * sin(4 * $xiO0);
1842 2
            $xiO3 = $h3 * sin(6 * $xiO0);
1843 2
            $xiO4 = $h4 * sin(8 * $xiO0);
1844 2
            $xiO = $xiO0 + $xiO1 + $xiO2 + $xiO3 + $xiO4;
1845 2
            $mO = $B * $xiO;
1846
        }
1847
1848 11
        $Q = asinh(tan($latitude)) - ($e * atanh($e * sin($latitude)));
1849 11
        $beta = atan(sinh($Q));
1850 11
        $eta0 = atanh(cos($beta) * sin($longitude - $longitudeOrigin));
1851 11
        $xi0 = self::asin(sin($beta) * cosh($eta0));
1852 11
        $xi1 = $h1 * sin(2 * $xi0) * cosh(2 * $eta0);
1853 11
        $eta1 = $h1 * cos(2 * $xi0) * sinh(2 * $eta0);
1854 11
        $xi2 = $h2 * sin(4 * $xi0) * cosh(4 * $eta0);
1855 11
        $eta2 = $h2 * cos(4 * $xi0) * sinh(4 * $eta0);
1856 11
        $xi3 = $h3 * sin(6 * $xi0) * cosh(6 * $eta0);
1857 11
        $eta3 = $h3 * cos(6 * $xi0) * sinh(6 * $eta0);
1858 11
        $xi4 = $h4 * sin(8 * $xi0) * cosh(8 * $eta0);
1859 11
        $eta4 = $h4 * cos(8 * $xi0) * sinh(8 * $eta0);
1860 11
        $xi = $xi0 + $xi1 + $xi2 + $xi3 + $xi4;
1861 11
        $eta = $eta0 + $eta1 + $eta2 + $eta3 + $eta4;
1862
1863 11
        $easting = $falseEasting->asMetres()->getValue() + $kO * $B * $eta;
1864 11
        $northing = $falseNorthing->asMetres()->getValue() + $kO * ($B * $xi - $mO);
1865
1866 11
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1867
    }
1868
1869
    /**
1870
     * Transverse Mercator Zoned Grid System
1871
     * If locations fall outwith the fixed zones the general Transverse Mercator method (code 9807) must be used for
1872
     * each zone.
1873
     */
1874 4
    public function transverseMercatorZonedGrid(
1875
        Projected $to,
1876
        Angle $latitudeOfNaturalOrigin,
1877
        Angle $initialLongitude,
1878
        Angle $zoneWidth,
1879
        Scale $scaleFactorAtNaturalOrigin,
1880
        Length $falseEasting,
1881
        Length $falseNorthing
1882
    ): ProjectedPoint {
1883 4
        $W = $zoneWidth->asDegrees()->getValue();
1884 4
        $Z = ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / $W) % (360 / $W) + 1;
1885
1886 4
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * $W - $W / 2));
1887 4
        $falseEasting = $falseEasting->add(new Metre($Z * 1000000));
1888
1889 4
        return $this->transverseMercator($to, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
1890
    }
1891
1892
    /**
1893
     * New Zealand Map Grid.
1894
     */
1895 3
    public function newZealandMapGrid(
1896
        Projected $to,
1897
        Angle $latitudeOfNaturalOrigin,
1898
        Angle $longitudeOfNaturalOrigin,
1899
        Length $falseEasting,
1900
        Length $falseNorthing
1901
    ): ProjectedPoint {
1902 3
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1903
1904 3
        $deltaLatitudeToOrigin = Angle::convert($this->latitude->subtract($latitudeOfNaturalOrigin), Angle::EPSG_ARC_SECOND)->getValue();
1905 3
        $deltaLongitudeToOrigin = $this->longitude->subtract($longitudeOfNaturalOrigin)->asRadians();
1906
1907 3
        $deltaPsi = 0;
1908 3
        $deltaPsi += 0.6399175073 * ($deltaLatitudeToOrigin * 0.00001) ** 1;
1909 3
        $deltaPsi += -0.1358797613 * ($deltaLatitudeToOrigin * 0.00001) ** 2;
1910 3
        $deltaPsi += 0.063294409 * ($deltaLatitudeToOrigin * 0.00001) ** 3;
1911 3
        $deltaPsi += -0.02526853 * ($deltaLatitudeToOrigin * 0.00001) ** 4;
1912 3
        $deltaPsi += 0.0117879 * ($deltaLatitudeToOrigin * 0.00001) ** 5;
1913 3
        $deltaPsi += -0.0055161 * ($deltaLatitudeToOrigin * 0.00001) ** 6;
1914 3
        $deltaPsi += 0.0026906 * ($deltaLatitudeToOrigin * 0.00001) ** 7;
1915 3
        $deltaPsi += -0.001333 * ($deltaLatitudeToOrigin * 0.00001) ** 8;
1916 3
        $deltaPsi += 0.00067 * ($deltaLatitudeToOrigin * 0.00001) ** 9;
1917 3
        $deltaPsi += -0.00034 * ($deltaLatitudeToOrigin * 0.00001) ** 10;
1918
1919 3
        $zeta = new ComplexNumber($deltaPsi, $deltaLongitudeToOrigin->getValue());
1920
1921 3
        $B1 = new ComplexNumber(0.7557853228, 0.0);
1922 3
        $B2 = new ComplexNumber(0.249204646, 0.003371507);
1923 3
        $B3 = new ComplexNumber(-0.001541739, 0.041058560);
1924 3
        $B4 = new ComplexNumber(-0.10162907, 0.01727609);
1925 3
        $B5 = new ComplexNumber(-0.26623489, -0.36249218);
1926 3
        $B6 = new ComplexNumber(-0.6870983, -1.1651967);
1927 3
        $z = new ComplexNumber(0, 0);
1928 3
        $z = $z->add($B1->multiply($zeta->pow(1)));
1929 3
        $z = $z->add($B2->multiply($zeta->pow(2)));
1930 3
        $z = $z->add($B3->multiply($zeta->pow(3)));
1931 3
        $z = $z->add($B4->multiply($zeta->pow(4)));
1932 3
        $z = $z->add($B5->multiply($zeta->pow(5)));
1933 3
        $z = $z->add($B6->multiply($zeta->pow(6)));
1934
1935 3
        $easting = $falseEasting->asMetres()->getValue() + $z->getImaginary() * $a;
1936 3
        $northing = $falseNorthing->asMetres()->getValue() + $z->getReal() * $a;
1937
1938 3
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1939
    }
1940
1941
    /**
1942
     * Madrid to ED50 polynomial.
1943
     */
1944 1
    public function madridToED50Polynomial(
1945
        Geographic2D $to,
1946
        Scale $A0,
1947
        Scale $A1,
1948
        Scale $A2,
1949
        Scale $A3,
1950
        Angle $B00,
1951
        Scale $B0,
1952
        Scale $B1,
1953
        Scale $B2,
1954
        Scale $B3
1955
    ): self {
1956 1
        $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());
1957 1
        $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()));
1958
1959 1
        return self::create($this->latitude->add($dLatitude), $this->longitude->add($dLongitude), null, $to, $this->epoch);
1960
    }
1961
1962
    /**
1963
     * Geographic3D to 2D conversion.
1964
     */
1965 5
    public function threeDToTwoD(
1966
        Geographic $to
1967
    ): self {
1968 5
        if ($to instanceof Geographic2D) {
1969 5
            return static::create($this->latitude, $this->longitude, null, $to, $this->epoch);
1970
        }
1971
1972
        return static::create($this->latitude, $this->longitude, new Metre(0), $to, $this->epoch);
1973
    }
1974
1975
    /**
1976
     * Geographic2D offsets.
1977
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
1978
     * coordinate values of the point in the source system.
1979
     */
1980 1
    public function geographic2DOffsets(
1981
        Geographic $to,
1982
        Angle $latitudeOffset,
1983
        Angle $longitudeOffset
1984
    ): self {
1985 1
        $toLatitude = $this->latitude->add($latitudeOffset);
1986 1
        $toLongitude = $this->longitude->add($longitudeOffset);
1987
1988 1
        return static::create($toLatitude, $toLongitude, null, $to, $this->epoch);
1989
    }
1990
1991
    /*
1992
     * Geographic2D with Height Offsets.
1993
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
1994
     * coordinate values of the point in the source system.
1995
     */
1996
    public function geographic2DWithHeightOffsets(
1997
        Compound $to,
1998
        Angle $latitudeOffset,
1999
        Angle $longitudeOffset,
2000
        Length $geoidUndulation
2001
    ): CompoundPoint {
2002
        $toLatitude = $this->latitude->add($latitudeOffset);
2003
        $toLongitude = $this->longitude->add($longitudeOffset);
2004
        $toHeight = $this->height->add($geoidUndulation);
0 ignored issues
show
Bug introduced by
The method add() does not exist on null. ( Ignorable by Annotation )

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

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

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

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

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

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

2007
        $vertical = VerticalPoint::create(/** @scrutinizer ignore-type */ $toHeight, $to->getVertical(), $this->epoch);
Loading history...
2008
2009
        return CompoundPoint::create($horizontal, $vertical, $to, $this->epoch);
2010
    }
2011
2012
    /**
2013
     * General polynomial.
2014
     * @param Coefficient[] $powerCoefficients
2015
     */
2016 2
    public function generalPolynomial(
2017
        Geographic $to,
2018
        Angle $ordinate1OfEvaluationPointInSourceCRS,
2019
        Angle $ordinate2OfEvaluationPointInSourceCRS,
2020
        Angle $ordinate1OfEvaluationPointInTargetCRS,
2021
        Angle $ordinate2OfEvaluationPointInTargetCRS,
2022
        Scale $scalingFactorForSourceCRSCoordDifferences,
2023
        Scale $scalingFactorForTargetCRSCoordDifferences,
2024
        Scale $A0,
2025
        Scale $B0,
2026
        array $powerCoefficients
2027
    ): self {
2028 2
        $xs = $this->latitude->getValue();
2029 2
        $ys = $this->longitude->getValue();
2030
2031 2
        $t = $this->generalPolynomialUnitless(
2032 2
            $xs,
2033
            $ys,
2034
            $ordinate1OfEvaluationPointInSourceCRS,
2035
            $ordinate2OfEvaluationPointInSourceCRS,
2036
            $ordinate1OfEvaluationPointInTargetCRS,
2037
            $ordinate2OfEvaluationPointInTargetCRS,
2038
            $scalingFactorForSourceCRSCoordDifferences,
2039
            $scalingFactorForTargetCRSCoordDifferences,
2040
            $A0,
2041
            $B0,
2042
            $powerCoefficients
2043
        );
2044
2045 2
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2046 2
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2047 2
            $xtUnit = Angle::EPSG_DEGREE;
2048
        }
2049 2
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2050 2
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2051 2
            $ytUnit = Angle::EPSG_DEGREE;
2052
        }
2053
2054 2
        return static::create(
2055 2
            Angle::makeUnit($t['xt'], $xtUnit),
2056 2
            Angle::makeUnit($t['yt'], $ytUnit),
2057 2
            $this->height,
2058
            $to,
2059 2
            $this->epoch
2060
        );
2061
    }
2062
2063
    /**
2064
     * Reversible polynomial.
2065
     * @param Coefficient[] $powerCoefficients
2066
     */
2067 4
    public function reversiblePolynomial(
2068
        Geographic $to,
2069
        Angle $ordinate1OfEvaluationPoint,
2070
        Angle $ordinate2OfEvaluationPoint,
2071
        Scale $scalingFactorForCoordDifferences,
2072
        Scale $A0,
2073
        Scale $B0,
2074
        $powerCoefficients
2075
    ): self {
2076 4
        $xs = $this->latitude->getValue();
2077 4
        $ys = $this->longitude->getValue();
2078
2079 4
        $t = $this->reversiblePolynomialUnitless(
2080 4
            $xs,
2081
            $ys,
2082
            $ordinate1OfEvaluationPoint,
2083
            $ordinate2OfEvaluationPoint,
2084
            $scalingFactorForCoordDifferences,
2085
            $A0,
2086
            $B0,
2087
            $powerCoefficients
2088
        );
2089
2090 4
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2091 4
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2092 4
            $xtUnit = Angle::EPSG_DEGREE;
2093
        }
2094 4
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2095 4
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2096 4
            $ytUnit = Angle::EPSG_DEGREE;
2097
        }
2098
2099 4
        return static::create(
2100 4
            Angle::makeUnit($t['xt'], $xtUnit),
2101 4
            Angle::makeUnit($t['yt'], $ytUnit),
2102 4
            $this->height,
2103
            $to,
2104 4
            $this->epoch
2105
        );
2106
    }
2107
2108
    /**
2109
     * Axis Order Reversal.
2110
     */
2111
    public function axisReversal(
2112
        Geographic $to
2113
    ) {
2114
        // axes are read in from the CRS, this is a book-keeping adjustment only
2115
        return static::create($this->latitude, $this->longitude, $this->height, $to, $this->epoch);
2116
    }
2117
2118 37
    public function asGeographicValue(): GeographicValue
2119
    {
2120 37
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2121
    }
2122
2123 2
    public function asUTMPoint(): UTMPoint
2124
    {
2125 2
        $hemisphere = $this->getLatitude()->asDegrees()->getValue() >= 0 ? UTMPoint::HEMISPHERE_NORTH : UTMPoint::HEMISPHERE_SOUTH;
2126 2
        $latitudeOfNaturalOrigin = new Degree(0);
2127 2
        $initialLongitude = new Degree(-180);
2128 2
        $scaleFactorAtNaturalOrigin = new Unity(0.9996);
2129 2
        $falseEasting = new Metre(500000);
2130 2
        $falseNorthing = $hemisphere === UTMPoint::HEMISPHERE_NORTH ? new Metre(0) : new Metre(10000000);
2131 2
        $Z = ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / 6) % (360 / 6) + 1;
2132 2
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * 6 - 3));
2133
2134 2
        $projectedCRS = new Projected(
2135 2
            'UTM/' . $this->crs->getSRID(),
2136 2
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2137 2
            $this->crs->getDatum(),
2138 2
            GeographicPolygon::createWorld() // this is a dummy CRS for the transform only, details don't matter
2139
        );
2140
2141 2
        $asProjected = $this->transverseMercator($projectedCRS, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
2142
2143 2
        return new UTMPoint($asProjected->getEasting(), $asProjected->getNorthing(), $Z, $hemisphere, $this->crs, $this->epoch);
2144
    }
2145
}
2146