Passed
Push — master ( 6be4bd...2c19f0 )
by Doug
51:47
created

lambertConicConformal2SPMichigan()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 23
c 0
b 0
f 0
nc 1
nop 8
dl 0
loc 36
ccs 24
cts 24
cp 1
crap 1
rs 9.552

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use DateTime;
12
use DateTimeImmutable;
13
use DateTimeInterface;
14
use PHPCoord\CoordinateOperation\AutoConversion;
15
use PHPCoord\CoordinateOperation\ComplexNumber;
16
use PHPCoord\CoordinateOperation\ConvertiblePoint;
17
use PHPCoord\CoordinateOperation\GeocentricValue;
18
use PHPCoord\CoordinateOperation\GeographicGeoidHeightGrid;
19
use PHPCoord\CoordinateOperation\GeographicGrid;
20
use PHPCoord\CoordinateOperation\GeographicValue;
21
use PHPCoord\CoordinateOperation\NADCON5Grid;
22
use PHPCoord\CoordinateOperation\NADCON5Grids;
23
use PHPCoord\CoordinateOperation\OSTNOSGM15Grid;
24
use PHPCoord\CoordinateReferenceSystem\Compound;
25
use PHPCoord\CoordinateReferenceSystem\Geocentric;
26
use PHPCoord\CoordinateReferenceSystem\Geographic;
27
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
28
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
29
use PHPCoord\CoordinateReferenceSystem\Projected;
0 ignored issues
show
Bug introduced by
The type PHPCoord\CoordinateReferenceSystem\Projected was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
1992
1993
        $horizontal = static::create($to->getHorizontal(), $toLatitude, $toLongitude, null, $this->epoch);
0 ignored issues
show
Bug introduced by
It seems like $to->getHorizontal() can also be of type PHPCoord\CoordinateReferenceSystem\Geocentric; however, parameter $crs of PHPCoord\GeographicPoint::create() does only seem to accept PHPCoord\CoordinateRefer...enceSystem\Geographic3D, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

2135
            /** @scrutinizer ignore-type */ $to->getHorizontal(),
Loading history...
2136
            $this->latitude,
2137
            $this->longitude,
2138
            null,
2139
            $this->getCoordinateEpoch()
2140
        );
2141
2142
        $verticalPoint = VerticalPoint::create(
2143
            $to->getVertical(),
2144
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...Adjustment($projected)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

2144
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
Loading history...
2145
            $this->getCoordinateEpoch()
2146
        );
2147
2148
        return CompoundPoint::create(
2149
            $to,
2150
            $horizontalPoint,
2151
            $verticalPoint,
2152
            $this->getCoordinateEpoch()
2153
        );
2154
    }
2155
2156
    /**
2157
     * Geographic3D to GravityRelatedHeight (OSGM-GB).
2158
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2159
     * coordinate differences.
2160
     */
2161
    public function geographic3DToGravityHeightOSGM15(
2162
        Vertical $to,
2163
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
2164
    ): VerticalPoint {
2165
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2166
        $etrs89NationalGrid = new Projected(
2167
            'ETRS89 / National Grid',
2168
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2169
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2170
            $osgb36NationalGrid->getBoundingArea()
2171
        );
2172
2173
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2174
2175
        return VerticalPoint::create(
2176
            $to,
2177
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...Adjustment($projected)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

2199
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
Loading history...
2200 7
            $this->getCoordinateEpoch()
2201 7
        );
2202
2203 7
        return CompoundPoint::create(
2204 7
            $to,
2205 7
            $horizontalPoint,
2206 7
            $verticalPoint,
2207 7
            $this->getCoordinateEpoch()
2208 7
        );
2209
    }
2210
2211
    /**
2212
     * Geographic3D to GravityRelatedHeight.
2213
     */
2214 4
    public function geographic3DToGravityHeightFromGrid(
2215
        Vertical $to,
2216
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2217
    ): VerticalPoint {
2218 4
        return VerticalPoint::create(
2219 4
            $to,
2220 4
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...eightAdjustment($this)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

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

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