Passed
Push — master ( 6ef80c...2068f7 )
by Doug
23:26
created

GeographicPoint::coordinateFrameRotation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 12
c 0
b 0
f 0
nc 1
nop 8
dl 0
loc 22
ccs 5
cts 5
cp 1
crap 1
rs 9.8666

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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

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

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

1999
        /** @scrutinizer ignore-call */ 
2000
        $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...
2000
2001
        $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

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

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

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

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

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

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

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

2198
            /** @scrutinizer ignore-type */ $to->getHorizontal(),
Loading history...
2199 12
            $this->latitude,
2200 12
            $this->longitude,
2201
            null,
2202 12
            $this->getCoordinateEpoch()
2203
        );
2204
2205 12
        $verticalPoint = VerticalPoint::create(
2206 12
            $to->getVertical(),
2207 12
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...eightAdjustment($this)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

2293
        return new UTMPoint($this->crs, $asProjected->getEasting(), $asProjected->/** @scrutinizer ignore-call */ getNorthing(), $zone, $hemisphere, $this->epoch);
Loading history...
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

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

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

2293
        return new UTMPoint($this->crs, $asProjected->/** @scrutinizer ignore-call */ getEasting(), $asProjected->getNorthing(), $zone, $hemisphere, $this->epoch);
Loading history...
2294
    }
2295
}
2296