Passed
Push — 4.0.x ( 414ad0...5c8c8d )
by Doug
05:04 queued 12s
created

GeographicPoint::positionVectorTransformation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 22
ccs 6
cts 6
cp 1
rs 9.8666
cc 1
nc 1
nop 8
crap 1

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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

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

2004
        $horizontal = static::create($toLatitude, $toLongitude, null, /** @scrutinizer ignore-type */ $to->getHorizontal(), $this->epoch);
Loading history...
2005
        $vertical = VerticalPoint::create($toHeight, $to->getVertical(), $this->epoch);
2006
2007
        return CompoundPoint::create($horizontal, $vertical, $to, $this->epoch);
2008
    }
2009
2010
    /**
2011
     * General polynomial.
2012
     * @param Coefficient[] $powerCoefficients
2013
     */
2014 2
    public function generalPolynomial(
2015
        Geographic $to,
2016
        Angle $ordinate1OfEvaluationPointInSourceCRS,
2017
        Angle $ordinate2OfEvaluationPointInSourceCRS,
2018
        Angle $ordinate1OfEvaluationPointInTargetCRS,
2019
        Angle $ordinate2OfEvaluationPointInTargetCRS,
2020
        Scale $scalingFactorForSourceCRSCoordDifferences,
2021
        Scale $scalingFactorForTargetCRSCoordDifferences,
2022
        Scale $A0,
2023
        Scale $B0,
2024
        array $powerCoefficients
2025
    ): self {
2026 2
        $xs = $this->latitude->getValue();
2027 2
        $ys = $this->longitude->getValue();
2028
2029 2
        $t = $this->generalPolynomialUnitless(
2030 2
            $xs,
2031
            $ys,
2032
            $ordinate1OfEvaluationPointInSourceCRS,
2033
            $ordinate2OfEvaluationPointInSourceCRS,
2034
            $ordinate1OfEvaluationPointInTargetCRS,
2035
            $ordinate2OfEvaluationPointInTargetCRS,
2036
            $scalingFactorForSourceCRSCoordDifferences,
2037
            $scalingFactorForTargetCRSCoordDifferences,
2038
            $A0,
2039
            $B0,
2040
            $powerCoefficients
2041
        );
2042
2043 2
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2044 2
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2045 2
            $xtUnit = Angle::EPSG_DEGREE;
2046
        }
2047 2
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2048 2
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2049 2
            $ytUnit = Angle::EPSG_DEGREE;
2050
        }
2051
2052 2
        return static::create(
2053 2
            Angle::makeUnit($t['xt'], $xtUnit),
2054 2
            Angle::makeUnit($t['yt'], $ytUnit),
2055 2
            $this->height,
2056
            $to,
2057 2
            $this->epoch
2058
        );
2059
    }
2060
2061
    /**
2062
     * Reversible polynomial.
2063
     * @param Coefficient[] $powerCoefficients
2064
     */
2065 4
    public function reversiblePolynomial(
2066
        Geographic $to,
2067
        Angle $ordinate1OfEvaluationPoint,
2068
        Angle $ordinate2OfEvaluationPoint,
2069
        Scale $scalingFactorForCoordDifferences,
2070
        Scale $A0,
2071
        Scale $B0,
2072
        $powerCoefficients
2073
    ): self {
2074 4
        $xs = $this->latitude->getValue();
2075 4
        $ys = $this->longitude->getValue();
2076
2077 4
        $t = $this->reversiblePolynomialUnitless(
2078 4
            $xs,
2079
            $ys,
2080
            $ordinate1OfEvaluationPoint,
2081
            $ordinate2OfEvaluationPoint,
2082
            $scalingFactorForCoordDifferences,
2083
            $A0,
2084
            $B0,
2085
            $powerCoefficients
2086
        );
2087
2088 4
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2089 4
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2090 4
            $xtUnit = Angle::EPSG_DEGREE;
2091
        }
2092 4
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2093 4
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2094 4
            $ytUnit = Angle::EPSG_DEGREE;
2095
        }
2096
2097 4
        return static::create(
2098 4
            Angle::makeUnit($t['xt'], $xtUnit),
2099 4
            Angle::makeUnit($t['yt'], $ytUnit),
2100 4
            $this->height,
2101
            $to,
2102 4
            $this->epoch
2103
        );
2104
    }
2105
2106
    /**
2107
     * Axis Order Reversal.
2108
     */
2109
    public function axisReversal(
2110
        Geographic $to
2111
    ) {
2112
        // axes are read in from the CRS, this is a book-keeping adjustment only
2113
        return static::create($this->latitude, $this->longitude, $this->height, $to, $this->epoch);
2114
    }
2115
2116 34
    public function asGeographicValue(): GeographicValue
2117
    {
2118 34
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2119
    }
2120
}
2121