Passed
Push — 4.x ( f9780c...a64886 )
by Doug
05:20
created

GeographicPoint::obliqueMercatorHotineVariantA()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 45
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 1

Importance

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

1936
        /** @scrutinizer ignore-call */ 
1937
        $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...
1937
1938
        $horizontal = static::create($toLatitude, $toLongitude, null, $to->getHorizontal(), $this->epoch);
1939
        $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

1939
        $vertical = VerticalPoint::create(/** @scrutinizer ignore-type */ $toHeight, $to->getVertical(), $this->epoch);
Loading history...
1940
1941
        return CompoundPoint::create($horizontal, $vertical, $to, $this->epoch);
1942
    }
1943
1944
    /**
1945
     * General polynomial of degree.
1946
     * @param Coefficient[] $powerCoefficients
1947
     */
1948 2
    public function generalPolynomial(
1949
        Geographic $to,
1950
        Angle $ordinate1OfEvaluationPointInSourceCRS,
1951
        Angle $ordinate2OfEvaluationPointInSourceCRS,
1952
        Angle $ordinate1OfEvaluationPointInTargetCRS,
1953
        Angle $ordinate2OfEvaluationPointInTargetCRS,
1954
        Scale $scalingFactorForSourceCRSCoordDifferences,
1955
        Scale $scalingFactorForTargetCRSCoordDifferences,
1956
        Scale $A0,
1957
        Scale $B0,
1958
        array $powerCoefficients
1959
    ): self {
1960 2
        $xs = $this->latitude->getValue();
1961 2
        $ys = $this->longitude->getValue();
1962
1963 2
        $t = $this->generalPolynomialUnitless(
1964 2
            $xs,
1965
            $ys,
1966
            $ordinate1OfEvaluationPointInSourceCRS,
1967
            $ordinate2OfEvaluationPointInSourceCRS,
1968
            $ordinate1OfEvaluationPointInTargetCRS,
1969
            $ordinate2OfEvaluationPointInTargetCRS,
1970
            $scalingFactorForSourceCRSCoordDifferences,
1971
            $scalingFactorForTargetCRSCoordDifferences,
1972
            $A0,
1973
            $B0,
1974
            $powerCoefficients
1975
        );
1976
1977 2
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
1978 2
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
1979 2
            $xtUnit = Angle::EPSG_DEGREE;
1980
        }
1981 2
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
1982 2
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
1983 2
            $ytUnit = Angle::EPSG_DEGREE;
1984
        }
1985
1986 2
        return static::create(
1987 2
            Angle::makeUnit($t['xt'], $xtUnit),
1988 2
            Angle::makeUnit($t['yt'], $ytUnit),
1989 2
            $this->height,
1990
            $to,
1991 2
            $this->epoch
1992
        );
1993
    }
1994
1995
    /**
1996
     * Reversible polynomial.
1997
     * @param Coefficient[] $powerCoefficients
1998
     */
1999 4
    public function reversiblePolynomial(
2000
        Geographic $to,
2001
        Angle $ordinate1OfEvaluationPoint,
2002
        Angle $ordinate2OfEvaluationPoint,
2003
        Scale $scalingFactorForCoordDifferences,
2004
        Scale $A0,
2005
        Scale $B0,
2006
        $powerCoefficients
2007
    ): self {
2008 4
        $xs = $this->latitude->getValue();
2009 4
        $ys = $this->longitude->getValue();
2010
2011 4
        $t = $this->reversiblePolynomialUnitless(
2012 4
            $xs,
2013
            $ys,
2014
            $ordinate1OfEvaluationPoint,
2015
            $ordinate2OfEvaluationPoint,
2016
            $scalingFactorForCoordDifferences,
2017
            $A0,
2018
            $B0,
2019
            $powerCoefficients
2020
        );
2021
2022 4
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2023 4
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2024 4
            $xtUnit = Angle::EPSG_DEGREE;
2025
        }
2026 4
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2027 4
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2028 4
            $ytUnit = Angle::EPSG_DEGREE;
2029
        }
2030
2031 4
        return static::create(
2032 4
            Angle::makeUnit($t['xt'], $xtUnit),
2033 4
            Angle::makeUnit($t['yt'], $ytUnit),
2034 4
            $this->height,
2035
            $to,
2036 4
            $this->epoch
2037
        );
2038
    }
2039
2040
    /**
2041
     * Axis Order Reversal.
2042
     */
2043
    public function axisReversal(
2044
        Geographic $to
2045
    ) {
2046
        // axes are read in from the CRS, this is a book-keeping adjustment only
2047
        return static::create($this->latitude, $this->longitude, $this->height, $to, $this->epoch);
2048
    }
2049
2050 10
    public function asGeographicValue(): GeographicValue
2051
    {
2052 10
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2053
    }
2054
}
2055