Passed
Push — 4.x ( 2e43a7...18aaa8 )
by Doug
06:37
created

GeographicPoint::madridToED50Polynomial()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 16
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 16
ccs 4
cts 4
cp 1
rs 10
cc 3
nc 1
nop 10
crap 3

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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

1535
        /** @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...
1536
        Angle $longitudeOfNaturalOrigin,
1537
        Length $falseEasting,
1538
        Length $falseNorthing
1539
    ): ProjectedPoint {
1540 9
        $ellipsoid = $this->crs->getDatum()->getEllipsoid();
1541 9
        $latitude = $this->latitude->asRadians()->getValue();
1542 9
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
1543
1544 9
        $easting = $falseEasting->asMetres()->getValue() + $a * ($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue());
1545 9
        $northing = $falseNorthing->asMetres()->getValue() + $a * log(tan(M_PI / 4 + $latitude / 2));
1546
1547 9
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1548
    }
1549
1550
    /**
1551
     * Mercator (variant A)
1552
     * Note that in these formulas the parameter latitude of natural origin (latO) is not used. However for this
1553
     * Mercator (variant A) method the EPSG dataset includes this parameter, which must have a value of zero, for
1554
     * completeness in CRS labelling.
1555
     */
1556 18
    public function mercatorVariantA(
1557
        Projected $to,
1558
        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

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

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

1988
        $vertical = VerticalPoint::create(/** @scrutinizer ignore-type */ $toHeight, $to->getVertical(), $this->epoch);
Loading history...
1989
1990
        return CompoundPoint::create($horizontal, $vertical, $to, $this->epoch);
1991
    }
1992
1993
    /**
1994
     * General polynomial.
1995
     * @param Coefficient[] $powerCoefficients
1996
     */
1997 18
    public function generalPolynomial(
1998
        Geographic $to,
1999
        Angle $ordinate1OfEvaluationPointInSourceCRS,
2000
        Angle $ordinate2OfEvaluationPointInSourceCRS,
2001
        Angle $ordinate1OfEvaluationPointInTargetCRS,
2002
        Angle $ordinate2OfEvaluationPointInTargetCRS,
2003
        Scale $scalingFactorForSourceCRSCoordDifferences,
2004
        Scale $scalingFactorForTargetCRSCoordDifferences,
2005
        Scale $A0,
2006
        Scale $B0,
2007
        array $powerCoefficients
2008
    ): self {
2009 18
        $xs = $this->latitude->getValue();
2010 18
        $ys = $this->longitude->getValue();
2011
2012 18
        $t = $this->generalPolynomialUnitless(
2013 18
            $xs,
2014 18
            $ys,
2015 18
            $ordinate1OfEvaluationPointInSourceCRS,
2016 18
            $ordinate2OfEvaluationPointInSourceCRS,
2017 18
            $ordinate1OfEvaluationPointInTargetCRS,
2018 18
            $ordinate2OfEvaluationPointInTargetCRS,
2019 18
            $scalingFactorForSourceCRSCoordDifferences,
2020 18
            $scalingFactorForTargetCRSCoordDifferences,
2021 18
            $A0,
2022 18
            $B0,
2023 18
            $powerCoefficients
2024 18
        );
2025
2026 18
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2027 18
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2028
2029 18
        return static::create(
2030 18
            Angle::makeUnit($t['xt'], $xtUnit),
2031 18
            Angle::makeUnit($t['yt'], $ytUnit),
2032 18
            $this->height,
2033 18
            $to,
2034 18
            $this->epoch
2035 18
        );
2036
    }
2037
2038
    /**
2039
     * Reversible polynomial.
2040
     * @param Coefficient[] $powerCoefficients
2041
     */
2042 36
    public function reversiblePolynomial(
2043
        Geographic $to,
2044
        Angle $ordinate1OfEvaluationPoint,
2045
        Angle $ordinate2OfEvaluationPoint,
2046
        Scale $scalingFactorForCoordDifferences,
2047
        Scale $A0,
2048
        Scale $B0,
2049
        $powerCoefficients
2050
    ): self {
2051 36
        $xs = $this->latitude->getValue();
2052 36
        $ys = $this->longitude->getValue();
2053
2054 36
        $t = $this->reversiblePolynomialUnitless(
2055 36
            $xs,
2056 36
            $ys,
2057 36
            $ordinate1OfEvaluationPoint,
2058 36
            $ordinate2OfEvaluationPoint,
2059 36
            $scalingFactorForCoordDifferences,
2060 36
            $A0,
2061 36
            $B0,
2062 36
            $powerCoefficients
2063 36
        );
2064
2065 36
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2066 36
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2067
2068 36
        return static::create(
2069 36
            Angle::makeUnit($t['xt'], $xtUnit),
2070 36
            Angle::makeUnit($t['yt'], $ytUnit),
2071 36
            $this->height,
2072 36
            $to,
2073 36
            $this->epoch
2074 36
        );
2075
    }
2076
2077
    /**
2078
     * Axis Order Reversal.
2079
     */
2080
    public function axisReversal(
2081
        Geographic $to
2082
    ): self {
2083
        // axes are read in from the CRS, this is a book-keeping adjustment only
2084
        return static::create($this->latitude, $this->longitude, $this->height, $to, $this->epoch);
2085
    }
2086
2087
    /**
2088
     * Ordnance Survey National Transformation
2089
     * Geodetic transformation between ETRS89 (or WGS 84) and OSGB36 / National Grid.  Uses ETRS89 / National Grid as
2090
     * an intermediate coordinate system for bi-linear interpolation of gridded grid coordinate differences.
2091
     */
2092 2
    public function OSTN15(
2093
        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

2093
        /** @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...
2094
        OSTNOSGM15Grid $eastingAndNorthingDifferenceFile
2095
    ): ProjectedPoint {
2096 2
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2097 2
        $etrs89NationalGrid = new Projected(
2098 2
            'ETRS89 / National Grid',
2099 2
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2100 2
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2101 2
            $osgb36NationalGrid->getBoundingArea()
2102 2
        );
2103
2104 2
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2105
2106 2
        return $eastingAndNorthingDifferenceFile->applyForwardHorizontalAdjustment($projected);
2107
    }
2108
2109
    /**
2110
     * Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB).
2111
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2112
     * coordinate differences.
2113
     */
2114 1
    public function geographic3DTo2DPlusGravityHeightOSGM15(
2115
        Compound $to,
2116
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile,
2117
        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

2117
        /** @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...
2118
    ): CompoundPoint {
2119 1
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2120 1
        $etrs89NationalGrid = new Projected(
2121 1
            'ETRS89 / National Grid',
2122 1
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2123 1
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2124 1
            $osgb36NationalGrid->getBoundingArea()
2125 1
        );
2126
2127 1
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2128
2129 1
        $horizontalPoint = self::create(
2130 1
            $this->latitude,
2131 1
            $this->longitude,
2132 1
            null,
2133 1
            $to->getHorizontal(),
2134 1
            $this->getCoordinateEpoch()
2135 1
        );
2136
2137 1
        $verticalPoint = VerticalPoint::create(
2138 1
            $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

2138
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getVerticalAdjustment($projected)),
Loading history...
2139 1
            $to->getVertical(),
2140 1
            $this->getCoordinateEpoch()
2141 1
        );
2142
2143 1
        return CompoundPoint::create(
2144 1
            $horizontalPoint,
2145 1
            $verticalPoint,
2146 1
            $to,
2147 1
            $this->getCoordinateEpoch()
2148 1
        );
2149
    }
2150
2151
    /**
2152
     * Geographic3D to GravityRelatedHeight (OSGM-GB).
2153
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2154
     * coordinate differences.
2155
     */
2156 1
    public function geographic3DToGravityHeightOSGM15(
2157
        Vertical $to,
2158
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
2159
    ): VerticalPoint {
2160 1
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2161 1
        $etrs89NationalGrid = new Projected(
2162 1
            'ETRS89 / National Grid',
2163 1
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2164 1
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2165 1
            $osgb36NationalGrid->getBoundingArea()
2166 1
        );
2167
2168 1
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2169
2170 1
        return VerticalPoint::create(
2171 1
            $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

2171
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getVerticalAdjustment($projected)),
Loading history...
2172 1
            $to,
2173 1
            $this->getCoordinateEpoch()
2174 1
        );
2175
    }
2176
2177
    /**
2178
     * NADCON5.
2179
     * Geodetic transformation operating on geographic coordinate differences by bi-quadratic interpolation.  Input
2180
     * expects longitudes to be positive east in range 0-360° (0° = Greenwich).
2181
     */
2182 7
    public function NADCON5(
2183
        Geographic $to,
2184
        NADCON5Grid $latitudeDifferenceFile,
2185
        NADCON5Grid $longitudeDifferenceFile,
2186
        ?NADCON5Grid $ellipsoidalHeightDifferenceFile = null,
2187
        bool $inReverse
2188
    ): self {
2189
        /*
2190
         * Ideally most of this logic (especially reverse case) would be in the NADCON5Grid class like the other grids,
2191
         * but NADCON5 uses different files for latitude/longitude/height that need to be combined at runtime so that
2192
         * isn't possible.
2193
         */
2194 7
        if (!$inReverse) {
2195 4
            $latitudeAdjustment = new ArcSecond($latitudeDifferenceFile->getForwardAdjustment($this));
2196 4
            $longitudeAdjustment = new ArcSecond($longitudeDifferenceFile->getForwardAdjustment($this));
2197 4
            $heightAdjustment = $this->getHeight() && $ellipsoidalHeightDifferenceFile ? new Metre($ellipsoidalHeightDifferenceFile->getForwardAdjustment($this)) : null;
2198
2199 4
            return self::create($this->latitude->add($latitudeAdjustment), $this->longitude->add($longitudeAdjustment), $heightAdjustment ? $this->height->add($heightAdjustment) : null, $to, $this->getCoordinateEpoch());
2200
        }
2201
2202 3
        $iteration = $this;
2203
2204
        do {
2205 3
            $prevIteration = $iteration;
2206 3
            $latitudeAdjustment = new ArcSecond($latitudeDifferenceFile->getForwardAdjustment($iteration));
2207 3
            $longitudeAdjustment = new ArcSecond($longitudeDifferenceFile->getForwardAdjustment($iteration));
2208 3
            $heightAdjustment = $this->getHeight() && $ellipsoidalHeightDifferenceFile ? new Metre($ellipsoidalHeightDifferenceFile->getForwardAdjustment($iteration)) : null;
2209 3
            $iteration = self::create($this->latitude->subtract($latitudeAdjustment), $this->longitude->subtract($longitudeAdjustment), $heightAdjustment ? $this->height->subtract($heightAdjustment) : null, $to, $this->getCoordinateEpoch());
2210 3
        } 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

2210
        } 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

2210
        } 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...
2211
2212 3
        return $iteration;
2213
    }
2214
2215
    /**
2216
     * NTv2
2217
     * Geodetic transformation operating on geographic coordinate differences by bi-linear interpolation.  Supersedes
2218
     * NTv1 (transformation method code 9614).  Input expects longitudes to be positive west.
2219
     */
2220 6
    public function NTv2(
2221
        Geographic $to,
2222
        NTv2Grid $latitudeAndLongitudeDifferenceFile,
2223
        bool $inReverse
2224
    ): self {
2225 6
        if (!$inReverse) {
2226 4
            return $latitudeAndLongitudeDifferenceFile->applyForwardAdjustment($this, $to);
2227
        } else {
2228 2
            return $latitudeAndLongitudeDifferenceFile->applyReverseAdjustment($this, $to);
2229
        }
2230
    }
2231
2232 378
    public function asGeographicValue(): GeographicValue
2233
    {
2234 378
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2235
    }
2236
2237 18
    public function asUTMPoint(): UTMPoint
2238
    {
2239 18
        $hemisphere = $this->getLatitude()->asDegrees()->getValue() >= 0 ? UTMPoint::HEMISPHERE_NORTH : UTMPoint::HEMISPHERE_SOUTH;
2240 18
        $latitudeOfNaturalOrigin = new Degree(0);
2241 18
        $initialLongitude = new Degree(-180);
2242 18
        $scaleFactorAtNaturalOrigin = new Unity(0.9996);
2243 18
        $falseEasting = new Metre(500000);
2244 18
        $falseNorthing = $hemisphere === UTMPoint::HEMISPHERE_NORTH ? new Metre(0) : new Metre(10000000);
2245 18
        $Z = (int) ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / 6) % (360 / 6) + 1;
2246 18
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * 6 - 3));
2247
2248 18
        $projectedCRS = new Projected(
2249 18
            'UTM/' . $this->crs->getSRID(),
2250 18
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2251 18
            $this->crs->getDatum(),
2252 18
            BoundingArea::createWorld() // this is a dummy CRS for the transform only, details don't matter
2253 18
        );
2254
2255 18
        $asProjected = $this->transverseMercator($projectedCRS, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
2256
2257 18
        return new UTMPoint($asProjected->getEasting(), $asProjected->getNorthing(), $Z, $hemisphere, $this->crs, $this->epoch);
2258
    }
2259
}
2260