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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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