Passed
Push — master ( 0a16ff...1038d1 )
by Doug
28:57
created

GeographicPoint::krovak()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 43
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 29
c 1
b 0
f 0
nc 1
nop 8
dl 0
loc 43
ccs 30
cts 30
cp 1
crap 1
rs 9.456

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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

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

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

1995
        /** @scrutinizer ignore-call */ 
1996
        $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...
1996
1997
        $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

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

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

2103
        /** @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...
2104
        OSTNOSGM15Grid $eastingAndNorthingDifferenceFile
2105
    ): ProjectedPoint {
2106 3
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2107 3
        $etrs89NationalGrid = new Projected(
2108
            'ETRS89 / National Grid',
2109 3
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2110 3
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2111 3
            $osgb36NationalGrid->getBoundingArea()
2112
        );
2113
2114 3
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2115
2116 3
        return $eastingAndNorthingDifferenceFile->applyForwardHorizontalAdjustment($projected);
2117
    }
2118
2119
    /**
2120
     * Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB).
2121
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2122
     * coordinate differences.
2123
     */
2124 1
    public function geographic3DTo2DPlusGravityHeightOSGM15(
2125
        Compound $to,
2126
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
2127
    ): CompoundPoint {
2128 1
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2129 1
        $etrs89NationalGrid = new Projected(
2130
            'ETRS89 / National Grid',
2131 1
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2132 1
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2133 1
            $osgb36NationalGrid->getBoundingArea()
2134
        );
2135
2136 1
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2137
2138 1
        $horizontalPoint = self::create(
2139 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

2139
            /** @scrutinizer ignore-type */ $to->getHorizontal(),
Loading history...
2140 1
            $this->latitude,
2141 1
            $this->longitude,
2142
            null,
2143 1
            $this->getCoordinateEpoch()
2144
        );
2145
2146 1
        $verticalPoint = VerticalPoint::create(
2147 1
            $to->getVertical(),
2148 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

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

2181
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
Loading history...
2182 1
            $this->getCoordinateEpoch()
2183
        );
2184
    }
2185
2186
    /**
2187
     * Geog3D to Geog2D+GravityRelatedHeight.
2188
     */
2189 12
    public function geographic3DTo2DPlusGravityHeightFromGrid(
2190
        Compound $to,
2191
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2192
    ): CompoundPoint {
2193 12
        $horizontalPoint = self::create(
2194 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

2194
            /** @scrutinizer ignore-type */ $to->getHorizontal(),
Loading history...
2195 12
            $this->latitude,
2196 12
            $this->longitude,
2197
            null,
2198 12
            $this->getCoordinateEpoch()
2199
        );
2200
2201 12
        $verticalPoint = VerticalPoint::create(
2202 12
            $to->getVertical(),
2203 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

2203
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
Loading history...
2204 12
            $this->getCoordinateEpoch()
2205
        );
2206
2207 12
        return CompoundPoint::create(
2208
            $to,
2209
            $horizontalPoint,
2210
            $verticalPoint,
2211 12
            $this->getCoordinateEpoch()
2212
        );
2213
    }
2214
2215
    /**
2216
     * Geographic3D to GravityRelatedHeight.
2217
     */
2218 7
    public function geographic3DToGravityHeightFromGrid(
2219
        Vertical $to,
2220
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2221
    ): VerticalPoint {
2222 7
        return VerticalPoint::create(
2223
            $to,
2224 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

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

2289
        return new UTMPoint(/** @scrutinizer ignore-type */ $this->crs, $asProjected->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

2289
        return new UTMPoint($this->crs, $asProjected->getEasting(), $asProjected->/** @scrutinizer ignore-call */ 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

2289
        return new UTMPoint($this->crs, $asProjected->/** @scrutinizer ignore-call */ getEasting(), $asProjected->getNorthing(), $zone, $hemisphere, $this->epoch);
Loading history...
2290
    }
2291
}
2292