Passed
Push — master ( a784d9...14494d )
by Doug
60:49
created

GeographicPoint::transverseMercator()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 60
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 42
CRAP Score 4.0015

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 45
c 1
b 0
f 0
nc 4
nop 6
dl 0
loc 60
ccs 42
cts 44
cp 0.9545
crap 4.0015
rs 9.2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 hypot;
23
use function implode;
24
use function is_nan;
25
use function log;
26
use const M_E;
27
use const M_PI;
28
use function max;
29
use PHPCoord\CoordinateOperation\AutoConversion;
30
use PHPCoord\CoordinateOperation\ComplexNumber;
31
use PHPCoord\CoordinateOperation\ConvertiblePoint;
32
use PHPCoord\CoordinateOperation\GeocentricValue;
33
use PHPCoord\CoordinateOperation\GeographicValue;
34
use PHPCoord\CoordinateOperation\NADCON5Grid;
35
use PHPCoord\CoordinateOperation\NTv2Grid;
36
use PHPCoord\CoordinateOperation\OSTNOSGM15Grid;
37
use PHPCoord\CoordinateReferenceSystem\Compound;
38
use PHPCoord\CoordinateReferenceSystem\Geocentric;
39
use PHPCoord\CoordinateReferenceSystem\Geographic;
40
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
41
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
42
use PHPCoord\CoordinateReferenceSystem\Projected;
0 ignored issues
show
Bug introduced by
The type PHPCoord\CoordinateReferenceSystem\Projected was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

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

1507
        /** @scrutinizer ignore-unused */ Angle $latitudeOfNaturalOrigin,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1508
        Angle $longitudeOfNaturalOrigin,
1509
        Length $falseEasting,
1510
        Length $falseNorthing
1511 9
    ): ProjectedPoint {
1512 9
        $latitude = $this->latitude->asRadians()->getValue();
1513
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1514 9
1515 9
        $easting = $falseEasting->asMetres()->getValue() + $a * ($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue());
1516
        $northing = $falseNorthing->asMetres()->getValue() + $a * log(tan(M_PI / 4 + $latitude / 2));
1517 9
1518
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1519
    }
1520
1521
    /**
1522
     * Mercator (variant A)
1523
     * Note that in these formulas the parameter latitude of natural origin (latO) is not used. However for this
1524
     * Mercator (variant A) method the EPSG dataset includes this parameter, which must have a value of zero, for
1525
     * completeness in CRS labelling.
1526 18
     */
1527
    public function mercatorVariantA(
1528
        Projected $to,
1529
        Angle $latitudeOfNaturalOrigin,
0 ignored issues
show
Unused Code introduced by
The parameter $latitudeOfNaturalOrigin is not used and could be removed. ( Ignorable by Annotation )

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

1529
        /** @scrutinizer ignore-unused */ Angle $latitudeOfNaturalOrigin,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1530
        Angle $longitudeOfNaturalOrigin,
1531
        Scale $scaleFactorAtNaturalOrigin,
1532
        Length $falseEasting,
1533
        Length $falseNorthing
1534 18
    ): ProjectedPoint {
1535 18
        $latitude = $this->latitude->asRadians()->getValue();
1536
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1537 18
1538 18
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1539
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1540 18
1541 18
        $easting = $falseEasting->asMetres()->getValue() + $a * $kO * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
1542
        $northing = $falseNorthing->asMetres()->getValue() + $a * $kO * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1543 18
1544
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1545
    }
1546
1547
    /**
1548
     * Mercator (variant B)
1549
     * Used for most nautical charts.
1550 9
     */
1551
    public function mercatorVariantB(
1552
        Projected $to,
1553
        Angle $latitudeOf1stStandardParallel,
1554
        Angle $longitudeOfNaturalOrigin,
1555
        Length $falseEasting,
1556
        Length $falseNorthing
1557 9
    ): ProjectedPoint {
1558 9
        $latitude = $this->latitude->asRadians()->getValue();
1559 9
        $firstStandardParallel = $latitudeOf1stStandardParallel->asRadians()->getValue();
1560 9
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1561 9
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1562
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1563 9
1564
        $kO = cos($firstStandardParallel) / sqrt(1 - $e2 * sin($firstStandardParallel) ** 2);
1565 9
1566 9
        $easting = $falseEasting->asMetres()->getValue() + $a * $kO * $this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue();
1567
        $northing = $falseNorthing->asMetres()->getValue() + $a * $kO * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1568 9
1569
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1570
    }
1571
1572
    /**
1573
     * Longitude rotation
1574
     * This transformation allows calculation of the longitude of a point in the target system by adding the parameter
1575
     * value to the longitude value of the point in the source system.
1576 9
     */
1577
    public function longitudeRotation(
1578
        Geographic $to,
1579
        Angle $longitudeOffset
1580 9
    ): self {
1581
        $newLongitude = $this->longitude->add($longitudeOffset);
1582 9
1583
        return static::create($this->latitude, $newLongitude, $this->height, $to, $this->epoch);
1584
    }
1585
1586
    /**
1587
     * Hotine Oblique Mercator (variant A).
1588 9
     */
1589
    public function obliqueMercatorHotineVariantA(
1590
        Projected $to,
1591
        Angle $latitudeOfProjectionCentre,
1592
        Angle $longitudeOfProjectionCentre,
1593
        Angle $azimuthOfInitialLine,
1594
        Angle $angleFromRectifiedToSkewGrid,
1595
        Scale $scaleFactorOnInitialLine,
1596
        Length $falseEasting,
1597
        Length $falseNorthing
1598 9
    ): ProjectedPoint {
1599 9
        $latitude = $this->latitude->asRadians()->getValue();
1600 9
        $longitude = $this->longitude->asRadians()->getValue();
1601 9
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1602 9
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1603 9
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1604 9
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1605 9
        $gammaC = $angleFromRectifiedToSkewGrid->asRadians()->getValue();
1606 9
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1607 9
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1608
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1609 9
1610 9
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1611 9
        $A = $a * $B * $kC * sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2);
1612 9
        $tO = tan(M_PI / 4 - $latC / 2) / ((1 - $e * sin($latC)) / (1 + $e * sin($latC))) ** ($e / 2);
1613 9
        $D = $B * sqrt((1 - $e2)) / (cos($latC) * sqrt(1 - $e2 * sin($latC) ** 2));
1614 9
        $DD = max(1, $D ** 2);
1615 9
        $F = $D + sqrt($DD - 1) * static::sign($latC);
1616 9
        $H = $F * ($tO) ** $B;
1617 9
        $G = ($F - 1 / $F) / 2;
1618 9
        $gammaO = self::asin(sin($alphaC) / $D);
1619
        $lonO = $lonC - (self::asin($G * tan($gammaO))) / $B;
1620 9
1621 9
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1622 9
        $Q = $H / $t ** $B;
1623 9
        $S = ($Q - 1 / $Q) / 2;
1624 9
        $T = ($Q + 1 / $Q) / 2;
1625 9
        $V = sin($B * ($longitude - $lonO));
1626 9
        $U = (-$V * cos($gammaO) + $S * sin($gammaO)) / $T;
1627 9
        $v = $A * log((1 - $U) / (1 + $U)) / (2 * $B);
1628
        $u = $A * atan2(($S * cos($gammaO) + $V * sin($gammaO)), cos($B * ($longitude - $lonO))) / $B;
1629 9
1630 9
        $easting = $v * cos($gammaC) + $u * sin($gammaC) + $falseEasting->asMetres()->getValue();
1631
        $northing = $u * cos($gammaC) - $v * sin($gammaC) + $falseNorthing->asMetres()->getValue();
1632 9
1633
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1634
    }
1635
1636
    /**
1637
     * Hotine Oblique Mercator (variant B).
1638 9
     */
1639
    public function obliqueMercatorHotineVariantB(
1640
        Projected $to,
1641
        Angle $latitudeOfProjectionCentre,
1642
        Angle $longitudeOfProjectionCentre,
1643
        Angle $azimuthOfInitialLine,
1644
        Angle $angleFromRectifiedToSkewGrid,
1645
        Scale $scaleFactorOnInitialLine,
1646
        Length $eastingAtProjectionCentre,
1647
        Length $northingAtProjectionCentre
1648 9
    ): ProjectedPoint {
1649 9
        $latitude = $this->latitude->asRadians()->getValue();
1650 9
        $longitude = $this->longitude->asRadians()->getValue();
1651 9
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1652 9
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1653 9
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1654 9
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1655 9
        $gammaC = $angleFromRectifiedToSkewGrid->asRadians()->getValue();
1656 9
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1657 9
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1658
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1659 9
1660 9
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1661 9
        $A = $a * $B * $kC * sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2);
1662 9
        $tO = tan(M_PI / 4 - $latC / 2) / ((1 - $e * sin($latC)) / (1 + $e * sin($latC))) ** ($e / 2);
1663 9
        $D = $B * sqrt((1 - $e2)) / (cos($latC) * sqrt(1 - $e2 * sin($latC) ** 2));
1664 9
        $DD = max(1, $D ** 2);
1665 9
        $F = $D + sqrt($DD - 1) * static::sign($latC);
1666 9
        $H = $F * ($tO) ** $B;
1667 9
        $G = ($F - 1 / $F) / 2;
1668 9
        $gammaO = self::asin(sin($alphaC) / $D);
1669 9
        $lonO = $lonC - (self::asin($G * tan($gammaO))) / $B;
1670 9
        $vC = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $vC is dead and can be removed.
Loading history...
1671
        if ($alphaC === M_PI / 2) {
1672
            $uC = $A * ($lonC - $lonO);
1673 9
        } else {
1674
            $uC = ($A / $B) * atan2(sqrt($DD - 1), cos($alphaC)) * static::sign($latC);
1675
        }
1676 9
1677 9
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1678 9
        $Q = $H / $t ** $B;
1679 9
        $S = ($Q - 1 / $Q) / 2;
1680 9
        $T = ($Q + 1 / $Q) / 2;
1681 9
        $V = sin($B * ($longitude - $lonO));
1682 9
        $U = (-$V * cos($gammaO) + $S * sin($gammaO)) / $T;
1683
        $v = $A * log((1 - $U) / (1 + $U)) / (2 * $B);
1684 9
1685
        if ($alphaC === M_PI / 2) {
1686
            if ($longitude === $lonC) {
1687
                $u = 0;
1688
            } else {
1689
                $u = ($A * atan(($S * cos($gammaO) + $V * sin($gammaO)) / cos($B * ($longitude - $lonO))) / $B) - (abs($uC) * static::sign($latC) * static::sign($lonC - $longitude));
1690
            }
1691 9
        } else {
1692
            $u = ($A * atan2(($S * cos($gammaO) + $V * sin($gammaO)), cos($B * ($longitude - $lonO))) / $B) - (abs($uC) * static::sign($latC));
1693
        }
1694 9
1695 9
        $easting = $v * cos($gammaC) + $u * sin($gammaC) + $eastingAtProjectionCentre->asMetres()->getValue();
1696
        $northing = $u * cos($gammaC) - $v * sin($gammaC) + $northingAtProjectionCentre->asMetres()->getValue();
1697 9
1698
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1699
    }
1700
1701
    /**
1702
     * Laborde Oblique Mercator.
1703 9
     */
1704
    public function obliqueMercatorLaborde(
1705
        Projected $to,
1706
        Angle $latitudeOfProjectionCentre,
1707
        Angle $longitudeOfProjectionCentre,
1708
        Angle $azimuthOfInitialLine,
1709
        Scale $scaleFactorOnInitialLine,
1710
        Length $falseEasting,
1711
        Length $falseNorthing
1712 9
    ): ProjectedPoint {
1713 9
        $latitude = $this->latitude->asRadians()->getValue();
1714 9
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1715 9
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1716 9
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1717 9
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1718 9
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1719
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1720 9
1721 9
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1722 9
        $latS = self::asin(sin($latC) / $B);
1723 9
        $R = $a * $kC * (sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2));
1724
        $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));
1725 9
1726 9
        $L = $B * $this->normaliseLongitude($this->longitude->subtract($longitudeOfProjectionCentre))->asRadians()->getValue();
1727 9
        $q = $C + $B * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1728 9
        $P = 2 * atan(M_E ** $q) - M_PI / 2;
1729 9
        $U = cos($P) * cos($L) * cos($latS) + sin($P) * sin($latS);
1730 9
        $V = cos($P) * cos($L) * sin($latS) - sin($P) * cos($latS);
1731 9
        $W = cos($P) * sin($L);
1732 9
        $d = hypot($U, $V);
1733
        if ($d === 0.0) {
1734
            $LPrime = 0;
1735
            $PPrime = static::sign($W) * M_PI / 2;
1736 9
        } else {
1737 9
            $LPrime = 2 * atan($V / ($U + $d));
1738
            $PPrime = atan($W / $d);
1739 9
        }
1740 9
        $H = new ComplexNumber(-$LPrime, log(tan(M_PI / 4 + $PPrime / 2)));
1741
        $G = (new ComplexNumber(1 - cos(2 * $alphaC), sin(2 * $alphaC)))->divide(new ComplexNumber(12, 0));
1742 9
1743 9
        $easting = $falseEasting->asMetres()->getValue() + $R * $H->pow(3)->multiply($G)->add($H)->getImaginary();
1744
        $northing = $falseNorthing->asMetres()->getValue() + $R * $H->pow(3)->multiply($G)->add($H)->getReal();
1745 9
1746
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1747
    }
1748
1749
    /**
1750
     * Transverse Mercator.
1751 108
     */
1752
    public function transverseMercator(
1753
        Projected $to,
1754
        Angle $latitudeOfNaturalOrigin,
1755
        Angle $longitudeOfNaturalOrigin,
1756
        Scale $scaleFactorAtNaturalOrigin,
1757
        Length $falseEasting,
1758
        Length $falseNorthing
1759 108
    ): ProjectedPoint {
1760 108
        $latitude = $this->latitude->asRadians()->getValue();
1761 108
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1762 108
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1763 108
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1764 108
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1765
        $f = $this->crs->getDatum()->getEllipsoid()->getInverseFlattening();
1766 108
1767 108
        $n = $f / (2 - $f);
1768
        $B = ($a / (1 + $n)) * (1 + $n ** 2 / 4 + $n ** 4 / 64);
1769 108
1770 108
        $h1 = $n / 2 - (2 / 3) * $n ** 2 + (5 / 16) * $n ** 3 + (41 / 180) * $n ** 4;
1771 108
        $h2 = (13 / 48) * $n ** 2 - (3 / 5) * $n ** 3 + (557 / 1440) * $n ** 4;
1772 108
        $h3 = (61 / 240) * $n ** 3 - (103 / 140) * $n ** 4;
1773
        $h4 = (49561 / 161280) * $n ** 4;
1774 108
1775 81
        if ($latitudeOrigin === 0.0) {
0 ignored issues
show
introduced by
The condition $latitudeOrigin === 0.0 is always false.
Loading history...
1776 27
            $mO = 0;
1777
        } elseif ($latitudeOrigin === M_PI / 2) {
1778 27
            $mO = $B * M_PI / 2;
1779
        } elseif ($latitudeOrigin === -M_PI / 2) {
1780
            $mO = $B * -M_PI / 2;
1781 27
        } else {
1782 27
            $qO = asinh(tan($latitudeOrigin)) - ($e * atanh($e * sin($latitudeOrigin)));
1783 27
            $betaO = atan(sinh($qO));
1784 27
            $xiO0 = self::asin(sin($betaO));
1785 27
            $xiO1 = $h1 * sin(2 * $xiO0);
1786 27
            $xiO2 = $h2 * sin(4 * $xiO0);
1787 27
            $xiO3 = $h3 * sin(6 * $xiO0);
1788 27
            $xiO4 = $h4 * sin(8 * $xiO0);
1789 27
            $xiO = $xiO0 + $xiO1 + $xiO2 + $xiO3 + $xiO4;
1790
            $mO = $B * $xiO;
1791
        }
1792 108
1793 108
        $Q = asinh(tan($latitude)) - ($e * atanh($e * sin($latitude)));
1794 108
        $beta = atan(sinh($Q));
1795 108
        $eta0 = atanh(cos($beta) * sin($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue()));
1796 108
        $xi0 = self::asin(sin($beta) * cosh($eta0));
1797 108
        $xi1 = $h1 * sin(2 * $xi0) * cosh(2 * $eta0);
1798 108
        $eta1 = $h1 * cos(2 * $xi0) * sinh(2 * $eta0);
1799 108
        $xi2 = $h2 * sin(4 * $xi0) * cosh(4 * $eta0);
1800 108
        $eta2 = $h2 * cos(4 * $xi0) * sinh(4 * $eta0);
1801 108
        $xi3 = $h3 * sin(6 * $xi0) * cosh(6 * $eta0);
1802 108
        $eta3 = $h3 * cos(6 * $xi0) * sinh(6 * $eta0);
1803 108
        $xi4 = $h4 * sin(8 * $xi0) * cosh(8 * $eta0);
1804 108
        $eta4 = $h4 * cos(8 * $xi0) * sinh(8 * $eta0);
1805 108
        $xi = $xi0 + $xi1 + $xi2 + $xi3 + $xi4;
1806
        $eta = $eta0 + $eta1 + $eta2 + $eta3 + $eta4;
1807 108
1808 108
        $easting = $falseEasting->asMetres()->getValue() + $kO * $B * $eta;
1809
        $northing = $falseNorthing->asMetres()->getValue() + $kO * ($B * $xi - $mO);
1810 108
1811
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1812
    }
1813
1814
    /**
1815
     * Transverse Mercator Zoned Grid System
1816
     * If locations fall outwith the fixed zones the general Transverse Mercator method (code 9807) must be used for
1817
     * each zone.
1818 36
     */
1819
    public function transverseMercatorZonedGrid(
1820
        Projected $to,
1821
        Angle $latitudeOfNaturalOrigin,
1822
        Angle $initialLongitude,
1823
        Angle $zoneWidth,
1824
        Scale $scaleFactorAtNaturalOrigin,
1825
        Length $falseEasting,
1826
        Length $falseNorthing
1827 36
    ): ProjectedPoint {
1828 36
        $W = $zoneWidth->asDegrees()->getValue();
1829
        $Z = ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / $W) % (360 / $W) + 1;
1830 36
1831 36
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * $W - $W / 2));
1832
        $falseEasting = $falseEasting->add(new Metre($Z * 1000000));
1833 36
1834
        return $this->transverseMercator($to, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
1835
    }
1836
1837
    /**
1838
     * New Zealand Map Grid.
1839 27
     */
1840
    public function newZealandMapGrid(
1841
        Projected $to,
1842
        Angle $latitudeOfNaturalOrigin,
1843
        Angle $longitudeOfNaturalOrigin,
1844
        Length $falseEasting,
1845
        Length $falseNorthing
1846 27
    ): ProjectedPoint {
1847
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1848 27
1849 27
        $deltaLatitudeToOrigin = Angle::convert($this->latitude->subtract($latitudeOfNaturalOrigin), Angle::EPSG_ARC_SECOND)->getValue();
1850
        $deltaLongitudeToOrigin = $this->longitude->subtract($longitudeOfNaturalOrigin)->asRadians();
1851 27
1852 27
        $deltaPsi = 0;
1853 27
        $deltaPsi += 0.6399175073 * ($deltaLatitudeToOrigin * 0.00001) ** 1;
1854 27
        $deltaPsi += -0.1358797613 * ($deltaLatitudeToOrigin * 0.00001) ** 2;
1855 27
        $deltaPsi += 0.063294409 * ($deltaLatitudeToOrigin * 0.00001) ** 3;
1856 27
        $deltaPsi += -0.02526853 * ($deltaLatitudeToOrigin * 0.00001) ** 4;
1857 27
        $deltaPsi += 0.0117879 * ($deltaLatitudeToOrigin * 0.00001) ** 5;
1858 27
        $deltaPsi += -0.0055161 * ($deltaLatitudeToOrigin * 0.00001) ** 6;
1859 27
        $deltaPsi += 0.0026906 * ($deltaLatitudeToOrigin * 0.00001) ** 7;
1860 27
        $deltaPsi += -0.001333 * ($deltaLatitudeToOrigin * 0.00001) ** 8;
1861 27
        $deltaPsi += 0.00067 * ($deltaLatitudeToOrigin * 0.00001) ** 9;
1862
        $deltaPsi += -0.00034 * ($deltaLatitudeToOrigin * 0.00001) ** 10;
1863 27
1864
        $zeta = new ComplexNumber($deltaPsi, $deltaLongitudeToOrigin->getValue());
1865 27
1866 27
        $B1 = new ComplexNumber(0.7557853228, 0.0);
1867 27
        $B2 = new ComplexNumber(0.249204646, 0.003371507);
1868 27
        $B3 = new ComplexNumber(-0.001541739, 0.041058560);
1869 27
        $B4 = new ComplexNumber(-0.10162907, 0.01727609);
1870 27
        $B5 = new ComplexNumber(-0.26623489, -0.36249218);
1871 27
        $B6 = new ComplexNumber(-0.6870983, -1.1651967);
1872 27
        $z = new ComplexNumber(0, 0);
1873 27
        $z = $z->add($B1->multiply($zeta->pow(1)));
1874 27
        $z = $z->add($B2->multiply($zeta->pow(2)));
1875 27
        $z = $z->add($B3->multiply($zeta->pow(3)));
1876 27
        $z = $z->add($B4->multiply($zeta->pow(4)));
1877 27
        $z = $z->add($B5->multiply($zeta->pow(5)));
1878
        $z = $z->add($B6->multiply($zeta->pow(6)));
1879 27
1880 27
        $easting = $falseEasting->asMetres()->getValue() + $z->getImaginary() * $a;
1881
        $northing = $falseNorthing->asMetres()->getValue() + $z->getReal() * $a;
1882 27
1883
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1884
    }
1885
1886
    /**
1887
     * Madrid to ED50 polynomial.
1888 9
     */
1889
    public function madridToED50Polynomial(
1890
        Geographic2D $to,
1891
        Scale $A0,
1892
        Scale $A1,
1893
        Scale $A2,
1894
        Scale $A3,
1895
        Angle $B00,
1896
        Scale $B0,
1897
        Scale $B1,
1898
        Scale $B2,
1899
        Scale $B3
1900 9
    ): self {
1901 9
        $dLatitude = new ArcSecond($A0->add($A1->multiply($this->latitude->getValue()))->add($A2->multiply($this->longitude->getValue()))->add($A3->multiply($this->height ? $this->height->getValue() : 0))->getValue());
1902
        $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()));
1903 9
1904
        return self::create($this->latitude->add($dLatitude), $this->longitude->add($dLongitude), null, $to, $this->epoch);
1905
    }
1906
1907
    /**
1908
     * Geographic3D to 2D conversion.
1909 45
     */
1910
    public function threeDToTwoD(
1911
        Geographic $to
1912 45
    ): self {
1913 45
        if ($to instanceof Geographic2D) {
1914
            return static::create($this->latitude, $this->longitude, null, $to, $this->epoch);
1915
        }
1916
1917
        return static::create($this->latitude, $this->longitude, new Metre(0), $to, $this->epoch);
1918
    }
1919
1920
    /**
1921
     * Geographic2D offsets.
1922
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
1923
     * coordinate values of the point in the source system.
1924 9
     */
1925
    public function geographic2DOffsets(
1926
        Geographic $to,
1927
        Angle $latitudeOffset,
1928
        Angle $longitudeOffset
1929 9
    ): self {
1930 9
        $toLatitude = $this->latitude->add($latitudeOffset);
1931
        $toLongitude = $this->longitude->add($longitudeOffset);
1932 9
1933
        return static::create($toLatitude, $toLongitude, null, $to, $this->epoch);
1934
    }
1935
1936
    /*
1937
     * Geographic2D with Height Offsets.
1938
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
1939
     * coordinate values of the point in the source system.
1940
     */
1941
    public function geographic2DWithHeightOffsets(
1942
        Compound $to,
1943
        Angle $latitudeOffset,
1944
        Angle $longitudeOffset,
1945
        Length $geoidUndulation
1946
    ): CompoundPoint {
1947
        $toLatitude = $this->latitude->add($latitudeOffset);
1948
        $toLongitude = $this->longitude->add($longitudeOffset);
1949
        $toHeight = $this->height->add($geoidUndulation);
0 ignored issues
show
Bug introduced by
The method add() does not exist on null. ( Ignorable by Annotation )

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

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

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

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

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

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

1952
        $vertical = VerticalPoint::create(/** @scrutinizer ignore-type */ $toHeight, $to->getVertical(), $this->epoch);
Loading history...
1953
1954
        return CompoundPoint::create($horizontal, $vertical, $to, $this->epoch);
1955
    }
1956
1957
    /**
1958
     * General polynomial.
1959
     * @param Coefficient[] $powerCoefficients
1960 18
     */
1961
    public function generalPolynomial(
1962
        Geographic $to,
1963
        Angle $ordinate1OfEvaluationPointInSourceCRS,
1964
        Angle $ordinate2OfEvaluationPointInSourceCRS,
1965
        Angle $ordinate1OfEvaluationPointInTargetCRS,
1966
        Angle $ordinate2OfEvaluationPointInTargetCRS,
1967
        Scale $scalingFactorForSourceCRSCoordDifferences,
1968
        Scale $scalingFactorForTargetCRSCoordDifferences,
1969
        Scale $A0,
1970
        Scale $B0,
1971
        array $powerCoefficients
1972 18
    ): self {
1973 18
        $xs = $this->latitude->getValue();
1974
        $ys = $this->longitude->getValue();
1975 18
1976 18
        $t = $this->generalPolynomialUnitless(
1977
            $xs,
1978
            $ys,
1979
            $ordinate1OfEvaluationPointInSourceCRS,
1980
            $ordinate2OfEvaluationPointInSourceCRS,
1981
            $ordinate1OfEvaluationPointInTargetCRS,
1982
            $ordinate2OfEvaluationPointInTargetCRS,
1983
            $scalingFactorForSourceCRSCoordDifferences,
1984
            $scalingFactorForTargetCRSCoordDifferences,
1985
            $A0,
1986
            $B0,
1987
            $powerCoefficients
1988
        );
1989 18
1990 18
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
1991 18
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
1992
            $xtUnit = Angle::EPSG_DEGREE;
1993 18
        }
1994 18
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
1995 18
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
1996
            $ytUnit = Angle::EPSG_DEGREE;
1997
        }
1998 18
1999 18
        return static::create(
2000 18
            Angle::makeUnit($t['xt'], $xtUnit),
2001 18
            Angle::makeUnit($t['yt'], $ytUnit),
2002
            $this->height,
2003 18
            $to,
2004
            $this->epoch
2005
        );
2006
    }
2007
2008
    /**
2009
     * Reversible polynomial.
2010
     * @param Coefficient[] $powerCoefficients
2011 36
     */
2012
    public function reversiblePolynomial(
2013
        Geographic $to,
2014
        Angle $ordinate1OfEvaluationPoint,
2015
        Angle $ordinate2OfEvaluationPoint,
2016
        Scale $scalingFactorForCoordDifferences,
2017
        Scale $A0,
2018
        Scale $B0,
2019
        $powerCoefficients
2020 36
    ): self {
2021 36
        $xs = $this->latitude->getValue();
2022
        $ys = $this->longitude->getValue();
2023 36
2024 36
        $t = $this->reversiblePolynomialUnitless(
2025
            $xs,
2026
            $ys,
2027
            $ordinate1OfEvaluationPoint,
2028
            $ordinate2OfEvaluationPoint,
2029
            $scalingFactorForCoordDifferences,
2030
            $A0,
2031
            $B0,
2032
            $powerCoefficients
2033
        );
2034 36
2035 36
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2036 36
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2037
            $xtUnit = Angle::EPSG_DEGREE;
2038 36
        }
2039 36
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2040 36
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2041
            $ytUnit = Angle::EPSG_DEGREE;
2042
        }
2043 36
2044 36
        return static::create(
2045 36
            Angle::makeUnit($t['xt'], $xtUnit),
2046 36
            Angle::makeUnit($t['yt'], $ytUnit),
2047
            $this->height,
2048 36
            $to,
2049
            $this->epoch
2050
        );
2051
    }
2052
2053
    /**
2054
     * Axis Order Reversal.
2055
     */
2056
    public function axisReversal(
2057
        Geographic $to
2058
    ): self {
2059
        // axes are read in from the CRS, this is a book-keeping adjustment only
2060
        return static::create($this->latitude, $this->longitude, $this->height, $to, $this->epoch);
2061
    }
2062
2063
    /**
2064
     * Ordnance Survey National Transformation
2065
     * Geodetic transformation between ETRS89 (or WGS 84) and OSGB36 / National Grid.  Uses ETRS89 / National Grid as
2066
     * an intermediate coordinate system for bi-linear interpolation of gridded grid coordinate differences.
2067
     */
2068
    public function OSTN15(
2069
        Projected $to,
0 ignored issues
show
Unused Code introduced by
The parameter $to is not used and could be removed. ( Ignorable by Annotation )

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

2069
        /** @scrutinizer ignore-unused */ Projected $to,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2070
        OSTNOSGM15Grid $eastingAndNorthingDifferenceFile
2071
    ): ProjectedPoint {
2072
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2073
        $etrs89NationalGrid = new Projected(
2074
            'ETRS89 / National Grid',
2075
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2076
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2077
            $osgb36NationalGrid->getBoundingArea()
2078
        );
2079
2080
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2081
2082
        return $eastingAndNorthingDifferenceFile->applyForwardHorizontalAdjustment($projected);
2083
    }
2084
2085
    /**
2086
     * Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB).
2087
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2088
     * coordinate differences.
2089
     */
2090
    public function geographic3DTo2DPlusGravityHeightOSGM15(
2091
        Compound $to,
2092
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile,
2093
        string $EPSGCodeForInterpolationCRS
0 ignored issues
show
Unused Code introduced by
The parameter $EPSGCodeForInterpolationCRS is not used and could be removed. ( Ignorable by Annotation )

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

2093
        /** @scrutinizer ignore-unused */ string $EPSGCodeForInterpolationCRS

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2094
    ): CompoundPoint {
2095
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2096
        $etrs89NationalGrid = new Projected(
2097
            'ETRS89 / National Grid',
2098
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2099
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2100
            $osgb36NationalGrid->getBoundingArea()
2101
        );
2102
2103
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2104
2105
        $horizontalPoint = self::create(
2106
            $this->latitude,
2107
            $this->longitude,
2108
            null,
2109
            $to->getHorizontal(),
2110
            $this->getCoordinateEpoch()
2111
        );
2112
2113
        $verticalPoint = VerticalPoint::create(
2114
            $this->height->subtract($geoidHeightCorrectionModelFile->getVerticalAdjustment($projected)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...Adjustment($projected)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

2114
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getVerticalAdjustment($projected)),
Loading history...
2115
            $to->getVertical(),
2116
            $this->getCoordinateEpoch()
2117
        );
2118
2119
        return CompoundPoint::create(
2120
            $horizontalPoint,
2121
            $verticalPoint,
2122
            $to,
2123
            $this->getCoordinateEpoch()
2124
        );
2125
    }
2126
2127
    /**
2128
     * Geographic3D to GravityRelatedHeight (OSGM-GB).
2129
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2130
     * coordinate differences.
2131
     */
2132
    public function geographic3DToGravityHeightOSGM15(
2133
        Vertical $to,
2134
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
2135
    ): VerticalPoint {
2136
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2137
        $etrs89NationalGrid = new Projected(
2138 351
            'ETRS89 / National Grid',
2139
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2140 351
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2141
            $osgb36NationalGrid->getBoundingArea()
2142
        );
2143 18
2144
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2145 18
2146 18
        return VerticalPoint::create(
2147 18
            $this->height->subtract($geoidHeightCorrectionModelFile->getVerticalAdjustment($projected)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...Adjustment($projected)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

2147
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getVerticalAdjustment($projected)),
Loading history...
2148 18
            $to,
2149 18
            $this->getCoordinateEpoch()
2150 18
        );
2151 18
    }
2152 18
2153
    /**
2154 18
     * NADCON5.
2155 18
     * Geodetic transformation operating on geographic coordinate differences by bi-quadratic interpolation.  Input
2156 18
     * expects longitudes to be positive east in range 0-360° (0° = Greenwich).
2157 18
     */
2158 18
    public function NADCON5(
2159
        Geographic $to,
2160
        NADCON5Grid $latitudeDifferenceFile,
2161 18
        NADCON5Grid $longitudeDifferenceFile,
2162
        ?NADCON5Grid $ellipsoidalHeightDifferenceFile = null,
2163 18
        bool $inReverse
2164
    ): self {
2165
        /*
2166
         * Ideally most of this logic (especially reverse case) would be in the NADCON5Grid class like the other grids,
2167
         * but NADCON5 uses different files for latitude/longitude/height that need to be combined at runtime so that
2168
         * isn't possible.
2169
         */
2170
        if (!$inReverse) {
2171
            $latitudeAdjustment = new ArcSecond($latitudeDifferenceFile->getForwardAdjustment($this));
2172
            $longitudeAdjustment = new ArcSecond($longitudeDifferenceFile->getForwardAdjustment($this));
2173
            $heightAdjustment = $this->getHeight() && $ellipsoidalHeightDifferenceFile ? new Metre($ellipsoidalHeightDifferenceFile->getForwardAdjustment($this)) : null;
2174
2175
            return self::create($this->latitude->add($latitudeAdjustment), $this->longitude->add($longitudeAdjustment), $heightAdjustment ? $this->height->add($heightAdjustment) : null, $to, $this->getCoordinateEpoch());
2176
        }
2177
2178
        $iteration = $this;
2179
2180
        do {
2181
            $prevIteration = $iteration;
2182
            $latitudeAdjustment = new ArcSecond($latitudeDifferenceFile->getForwardAdjustment($iteration));
2183
            $longitudeAdjustment = new ArcSecond($longitudeDifferenceFile->getForwardAdjustment($iteration));
2184
            $heightAdjustment = $this->getHeight() && $ellipsoidalHeightDifferenceFile ? new Metre($ellipsoidalHeightDifferenceFile->getForwardAdjustment($iteration)) : null;
2185
            $iteration = self::create($this->latitude->subtract($latitudeAdjustment), $this->longitude->subtract($longitudeAdjustment), $heightAdjustment ? $this->height->subtract($heightAdjustment) : null, $to, $this->getCoordinateEpoch());
2186
        } while (abs($iteration->latitude->subtract($prevIteration->latitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && abs($iteration->longitude->subtract($prevIteration->longitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && ($this->height === null || abs($iteration->height->subtract($prevIteration->height)->getValue()) > self::ITERATION_CONVERGENCE_GRID));
0 ignored issues
show
Bug introduced by
It seems like $prevIteration->height can also be of type null; however, parameter $unit of PHPCoord\UnitOfMeasure\Length\Length::subtract() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

2186
        } while (abs($iteration->latitude->subtract($prevIteration->latitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && abs($iteration->longitude->subtract($prevIteration->longitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && ($this->height === null || abs($iteration->height->subtract(/** @scrutinizer ignore-type */ $prevIteration->height)->getValue()) > self::ITERATION_CONVERGENCE_GRID));
Loading history...
Bug introduced by
The method subtract() does not exist on null. ( Ignorable by Annotation )

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

2186
        } while (abs($iteration->latitude->subtract($prevIteration->latitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && abs($iteration->longitude->subtract($prevIteration->longitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && ($this->height === null || abs($iteration->height->/** @scrutinizer ignore-call */ subtract($prevIteration->height)->getValue()) > self::ITERATION_CONVERGENCE_GRID));

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

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

Loading history...
2187
2188
        return $iteration;
2189
    }
2190
2191
    /**
2192
     * NTv2
2193
     * Geodetic transformation operating on geographic coordinate differences by bi-linear interpolation.  Supersedes
2194
     * NTv1 (transformation method code 9614).  Input expects longitudes to be positive west.
2195
     */
2196
    public function NTv2(
2197
        Geographic $to,
2198
        NTv2Grid $latitudeAndLongitudeDifferenceFile,
2199
        bool $inReverse
2200
    ): self {
2201
        if (!$inReverse) {
2202
            return $latitudeAndLongitudeDifferenceFile->applyForwardAdjustment($this, $to);
2203
        } else {
2204
            return $latitudeAndLongitudeDifferenceFile->applyReverseAdjustment($this, $to);
2205
        }
2206
    }
2207
2208
    public function asGeographicValue(): GeographicValue
2209
    {
2210
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2211
    }
2212
2213
    public function asUTMPoint(): UTMPoint
2214
    {
2215
        $hemisphere = $this->getLatitude()->asDegrees()->getValue() >= 0 ? UTMPoint::HEMISPHERE_NORTH : UTMPoint::HEMISPHERE_SOUTH;
2216
        $latitudeOfNaturalOrigin = new Degree(0);
2217
        $initialLongitude = new Degree(-180);
2218
        $scaleFactorAtNaturalOrigin = new Unity(0.9996);
2219
        $falseEasting = new Metre(500000);
2220
        $falseNorthing = $hemisphere === UTMPoint::HEMISPHERE_NORTH ? new Metre(0) : new Metre(10000000);
2221
        $Z = ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / 6) % (360 / 6) + 1;
2222
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * 6 - 3));
2223
2224
        $projectedCRS = new Projected(
2225
            'UTM/' . $this->crs->getSRID(),
2226
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2227
            $this->crs->getDatum(),
2228
            BoundingArea::createWorld() // this is a dummy CRS for the transform only, details don't matter
2229
        );
2230
2231
        $asProjected = $this->transverseMercator($projectedCRS, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
2232
2233
        return new UTMPoint($asProjected->getEasting(), $asProjected->getNorthing(), $Z, $hemisphere, $this->crs, $this->epoch);
2234
    }
2235
}
2236