Passed
Push — master ( 2c19f0...d4552d )
by Doug
46:35
created

GeographicPoint::generalPolynomial()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 38
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 22
nc 1
nop 10
dl 0
loc 38
ccs 24
cts 24
cp 1
crap 1
rs 9.568
c 0
b 0
f 0

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

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

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

1991
        /** @scrutinizer ignore-call */ 
1992
        $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...
1992
1993
        $horizontal = static::create($to->getHorizontal(), $toLatitude, $toLongitude, null, $this->epoch);
0 ignored issues
show
Bug introduced by
It seems like $to->getHorizontal() can also be of type PHPCoord\CoordinateReferenceSystem\Geocentric; however, parameter $crs of PHPCoord\GeographicPoint::create() does only seem to accept PHPCoord\CoordinateRefer...enceSystem\Geographic3D, 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

1993
        $horizontal = static::create(/** @scrutinizer ignore-type */ $to->getHorizontal(), $toLatitude, $toLongitude, null, $this->epoch);
Loading history...
1994
        $vertical = VerticalPoint::create($to->getVertical(), $toHeight, $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

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

2099
        /** @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...
2100
        OSTNOSGM15Grid $eastingAndNorthingDifferenceFile
2101
    ): ProjectedPoint {
2102 3
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2103 3
        $etrs89NationalGrid = new Projected(
2104 3
            'ETRS89 / National Grid',
2105 3
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2106 3
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2107 3
            $osgb36NationalGrid->getBoundingArea()
2108 3
        );
2109
2110 3
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2111
2112 3
        return $eastingAndNorthingDifferenceFile->applyForwardHorizontalAdjustment($projected);
2113
    }
2114
2115
    /**
2116
     * Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB).
2117
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2118
     * coordinate differences.
2119
     */
2120 1
    public function geographic3DTo2DPlusGravityHeightOSGM15(
2121
        Compound $to,
2122
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
2123
    ): CompoundPoint {
2124 1
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2125 1
        $etrs89NationalGrid = new Projected(
2126 1
            'ETRS89 / National Grid',
2127 1
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2128 1
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2129 1
            $osgb36NationalGrid->getBoundingArea()
2130 1
        );
2131
2132 1
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2133
2134 1
        $horizontalPoint = self::create(
2135 1
            $to->getHorizontal(),
0 ignored issues
show
Bug introduced by
It seems like $to->getHorizontal() can also be of type PHPCoord\CoordinateReferenceSystem\Geocentric; however, parameter $crs of PHPCoord\GeographicPoint::create() does only seem to accept PHPCoord\CoordinateRefer...enceSystem\Geographic3D, 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

2135
            /** @scrutinizer ignore-type */ $to->getHorizontal(),
Loading history...
2136 1
            $this->latitude,
2137 1
            $this->longitude,
2138 1
            null,
2139 1
            $this->getCoordinateEpoch()
2140 1
        );
2141
2142 1
        $verticalPoint = VerticalPoint::create(
2143 1
            $to->getVertical(),
2144 1
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($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

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

2177
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
Loading history...
2178 1
            $this->getCoordinateEpoch()
2179 1
        );
2180
    }
2181
2182
    /**
2183
     * Geog3D to Geog2D+GravityRelatedHeight.
2184
     */
2185 12
    public function geographic3DTo2DPlusGravityHeightFromGrid(
2186
        Compound $to,
2187
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2188
    ): CompoundPoint {
2189 12
        $horizontalPoint = self::create(
2190 12
            $to->getHorizontal(),
0 ignored issues
show
Bug introduced by
It seems like $to->getHorizontal() can also be of type PHPCoord\CoordinateReferenceSystem\Geocentric; however, parameter $crs of PHPCoord\GeographicPoint::create() does only seem to accept PHPCoord\CoordinateRefer...enceSystem\Geographic3D, 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

2190
            /** @scrutinizer ignore-type */ $to->getHorizontal(),
Loading history...
2191 12
            $this->latitude,
2192 12
            $this->longitude,
2193 12
            null,
2194 12
            $this->getCoordinateEpoch()
2195 12
        );
2196
2197 12
        $verticalPoint = VerticalPoint::create(
2198 12
            $to->getVertical(),
2199 12
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...eightAdjustment($this)) 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

2199
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
Loading history...
2200 12
            $this->getCoordinateEpoch()
2201 12
        );
2202
2203 12
        return CompoundPoint::create(
2204 12
            $to,
2205 12
            $horizontalPoint,
2206 12
            $verticalPoint,
2207 12
            $this->getCoordinateEpoch()
2208 12
        );
2209
    }
2210
2211
    /**
2212
     * Geographic3D to GravityRelatedHeight.
2213
     */
2214 7
    public function geographic3DToGravityHeightFromGrid(
2215
        Vertical $to,
2216
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2217
    ): VerticalPoint {
2218 7
        return VerticalPoint::create(
2219 7
            $to,
2220 7
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...eightAdjustment($this)) 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

2220
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
Loading history...
2221 7
            $this->getCoordinateEpoch()
2222 7
        );
2223
    }
2224
2225
    /**
2226
     * NADCON5.
2227
     * @internal just a wrapper
2228
     */
2229 8
    public function offsetsFromGridNADCON5(
2230
        Geographic2D|Geographic3D $to,
2231
        NADCON5Grid $latitudeDifferenceFile,
2232
        NADCON5Grid $longitudeDifferenceFile,
2233
        ?NADCON5Grid $ellipsoidalHeightDifferenceFile,
2234
        bool $inReverse
2235
    ): self {
2236 8
        $aggregation = new NADCON5Grids($longitudeDifferenceFile, $latitudeDifferenceFile, $ellipsoidalHeightDifferenceFile);
2237
2238 8
        return $this->offsetsFromGrid($to, $aggregation, $inReverse);
2239
    }
2240
2241
    /**
2242
     * Geographic offsets from grid.
2243
     */
2244 19
    public function offsetsFromGrid(
2245
        Geographic2D|Geographic3D $to,
2246
        GeographicGrid $offsetsFile,
2247
        bool $inReverse
2248
    ): self {
2249 19
        if (!$inReverse) {
2250 13
            return $offsetsFile->applyForwardAdjustment($this, $to);
2251
        }
2252
2253 8
        return $offsetsFile->applyReverseAdjustment($this, $to);
2254
    }
2255
2256 391
    public function asGeographicValue(): GeographicValue
2257
    {
2258 391
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2259
    }
2260
2261 18
    public function asUTMPoint(): UTMPoint
2262
    {
2263 18
        $hemisphere = $this->getLatitude()->asDegrees()->getValue() >= 0 ? UTMPoint::HEMISPHERE_NORTH : UTMPoint::HEMISPHERE_SOUTH;
2264
2265 18
        $initialLongitude = new Degree(-180);
2266 18
        $zone = (int) ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / 6) % (360 / 6) + 1;
2267
2268 18
        if ($hemisphere === UTMPoint::HEMISPHERE_NORTH) {
2269 9
            $derivingConversion = 'urn:ogc:def:coordinateOperation:EPSG::' . ($zone + 16000);
2270
        } else {
2271 9
            $derivingConversion = 'urn:ogc:def:coordinateOperation:EPSG::' . ($zone + 16100);
2272
        }
2273
2274 18
        $srid = 'urn:ogc:def:crs,' . str_replace('urn:ogc:def:', '', $this->crs->getSRID()) . ',' . str_replace('urn:ogc:def:', '', Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M) . ',' . str_replace('urn:ogc:def:', '', $derivingConversion);
2275
2276 18
        $projectedCRS = new Projected(
2277 18
            $srid,
2278 18
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2279 18
            $this->crs->getDatum(),
2280 18
            BoundingArea::createWorld() // this is a dummy CRS for the transform only, details don't matter (UTMPoint creates own)
2281 18
        );
2282
2283 18
        $asProjected = $this->performOperation($derivingConversion, $projectedCRS, false);
2284
2285 18
        return new UTMPoint($this->crs, $asProjected->getEasting(), $asProjected->getNorthing(), $zone, $hemisphere, $this->epoch);
0 ignored issues
show
Bug introduced by
It seems like $this->crs can also be of type PHPCoord\CoordinateReferenceSystem\Geographic3D; however, parameter $crs of PHPCoord\UTMPoint::__construct() does only seem to accept PHPCoord\CoordinateReferenceSystem\Geographic2D, 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

2285
        return new UTMPoint(/** @scrutinizer ignore-type */ $this->crs, $asProjected->getEasting(), $asProjected->getNorthing(), $zone, $hemisphere, $this->epoch);
Loading history...
Bug introduced by
The method getEasting() does not exist on PHPCoord\Point. It seems like you code against a sub-type of PHPCoord\Point such as PHPCoord\ProjectedPoint. ( Ignorable by Annotation )

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

2285
        return new UTMPoint($this->crs, $asProjected->/** @scrutinizer ignore-call */ getEasting(), $asProjected->getNorthing(), $zone, $hemisphere, $this->epoch);
Loading history...
Bug introduced by
The method getNorthing() does not exist on PHPCoord\Point. It seems like you code against a sub-type of PHPCoord\Point such as PHPCoord\ProjectedPoint. ( Ignorable by Annotation )

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

2285
        return new UTMPoint($this->crs, $asProjected->getEasting(), $asProjected->/** @scrutinizer ignore-call */ getNorthing(), $zone, $hemisphere, $this->epoch);
Loading history...
2286
    }
2287
}
2288