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

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

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

1980
        /** @scrutinizer ignore-call */ 
1981
        $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...
1981
1982
        $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

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

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

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

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

2124
            /** @scrutinizer ignore-type */ $to->getHorizontal(),
Loading history...
2125 1
            $this->latitude,
2126 1
            $this->longitude,
2127 1
            null,
2128 1
            $this->getCoordinateEpoch()
2129
        );
2130
2131 1
        $verticalPoint = VerticalPoint::create(
2132 1
            $to->getVertical(),
2133 1
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...Adjustment($projected)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

2166
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
Loading history...
2167 1
            $this->getCoordinateEpoch()
2168
        );
2169
    }
2170
2171
    /**
2172
     * Geog3D to Geog2D+GravityRelatedHeight.
2173
     */
2174 2
    public function geographic3DTo2DPlusGravityHeightFromGrid(
2175
        Compound $to,
2176
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2177
    ): CompoundPoint {
2178 2
        $horizontalPoint = self::create(
2179 2
            $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

2179
            /** @scrutinizer ignore-type */ $to->getHorizontal(),
Loading history...
2180 2
            $this->latitude,
2181 2
            $this->longitude,
2182 2
            null,
2183 2
            $this->getCoordinateEpoch()
2184
        );
2185
2186 2
        $verticalPoint = VerticalPoint::create(
2187 2
            $to->getVertical(),
2188 2
            $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

2188
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
Loading history...
2189 2
            $this->getCoordinateEpoch()
2190
        );
2191
2192 2
        return CompoundPoint::create(
2193 2
            $to,
2194 2
            $horizontalPoint,
2195 2
            $verticalPoint,
2196 2
            $this->getCoordinateEpoch()
2197
        );
2198
    }
2199
2200
    /**
2201
     * Geographic3D to GravityRelatedHeight.
2202
     */
2203 2
    public function geographic3DToGravityHeightFromGrid(
2204
        Vertical $to,
2205
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2206
    ): VerticalPoint {
2207 2
        return VerticalPoint::create(
2208 2
            $to,
2209 2
            $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

2209
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
Loading history...
2210 2
            $this->getCoordinateEpoch()
2211
        );
2212
    }
2213
2214
    /**
2215
     * NADCON5.
2216
     * @internal just a wrapper
2217
     */
2218 7
    public function offsetsFromGridNADCON5(
2219
        Geographic2D|Geographic3D $to,
2220
        NADCON5Grid $latitudeDifferenceFile,
2221
        NADCON5Grid $longitudeDifferenceFile,
2222
        ?NADCON5Grid $ellipsoidalHeightDifferenceFile,
2223
        bool $inReverse
2224
    ): self {
2225 7
        $aggregation = new NADCON5Grids($longitudeDifferenceFile, $latitudeDifferenceFile, $ellipsoidalHeightDifferenceFile);
2226
2227 7
        return $this->offsetsFromGrid($to, $aggregation, $inReverse);
2228
    }
2229
2230
    /**
2231
     * Geographic offsets from grid.
2232
     */
2233 12
    public function offsetsFromGrid(
2234
        Geographic2D|Geographic3D $to,
2235
        GeographicGrid $offsetsFile,
2236
        bool $inReverse
2237
    ): self {
2238 12
        if (!$inReverse) {
2239 7
            return $offsetsFile->applyForwardAdjustment($this, $to);
2240
        }
2241
2242 5
        return $offsetsFile->applyReverseAdjustment($this, $to);
2243
    }
2244
2245 353
    public function asGeographicValue(): GeographicValue
2246
    {
2247 353
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2248
    }
2249
2250 18
    public function asUTMPoint(): UTMPoint
2251
    {
2252 18
        $hemisphere = $this->getLatitude()->asDegrees()->getValue() >= 0 ? UTMPoint::HEMISPHERE_NORTH : UTMPoint::HEMISPHERE_SOUTH;
2253 18
        $latitudeOfNaturalOrigin = new Degree(0);
2254 18
        $initialLongitude = new Degree(-180);
2255 18
        $scaleFactorAtNaturalOrigin = new Unity(0.9996);
2256 18
        $falseEasting = new Metre(500000);
2257 18
        $falseNorthing = $hemisphere === UTMPoint::HEMISPHERE_NORTH ? new Metre(0) : new Metre(10000000);
2258 18
        $Z = (int) ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / 6) % (360 / 6) + 1;
2259 18
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * 6 - 3));
2260
2261 18
        $projectedCRS = new Projected(
2262 18
            'UTM/' . $this->crs->getSRID(),
2263 18
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2264 18
            $this->crs->getDatum(),
2265 18
            BoundingArea::createWorld() // this is a dummy CRS for the transform only, details don't matter
2266
        );
2267
2268 18
        $asProjected = $this->transverseMercator($projectedCRS, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
2269
2270 18
        return new UTMPoint($this->crs, $asProjected->getEasting(), $asProjected->getNorthing(), $Z, $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

2270
        return new UTMPoint(/** @scrutinizer ignore-type */ $this->crs, $asProjected->getEasting(), $asProjected->getNorthing(), $Z, $hemisphere, $this->epoch);
Loading history...
2271
    }
2272
}
2273