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