Passed
Push — master ( 140fc1...a234d9 )
by Doug
12:58
created

GeographicPoint::generalPolynomial()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 44
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 26
c 0
b 0
f 0
dl 0
loc 44
ccs 16
cts 16
cp 1
rs 9.504
cc 3
nc 4
nop 10
crap 3

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use function abs;
12
use function asinh;
13
use function atan;
14
use function atan2;
15
use function atanh;
16
use function cos;
17
use function cosh;
18
use DateTime;
19
use DateTimeImmutable;
20
use DateTimeInterface;
21
use function get_class;
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\GeographicValue;
34
use PHPCoord\CoordinateOperation\IGNGeocentricTranslationGrid;
35
use PHPCoord\CoordinateOperation\NADCON5Grid;
36
use PHPCoord\CoordinateOperation\NTv2Grid;
37
use PHPCoord\CoordinateOperation\OSTNOSGM15Grid;
38
use PHPCoord\CoordinateReferenceSystem\Compound;
39
use PHPCoord\CoordinateReferenceSystem\Geocentric;
40
use PHPCoord\CoordinateReferenceSystem\Geographic;
41
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
42
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
43
use PHPCoord\CoordinateReferenceSystem\Projected;
0 ignored issues
show
Bug introduced by
The type PHPCoord\CoordinateReferenceSystem\Projected was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

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

1508
        /** @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...
1509
        Angle $longitudeOfNaturalOrigin,
1510
        Length $falseEasting,
1511
        Length $falseNorthing
1512
    ): ProjectedPoint {
1513 9
        $latitude = $this->latitude->asRadians()->getValue();
1514 9
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1515
1516 9
        $easting = $falseEasting->asMetres()->getValue() + $a * ($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue());
1517 9
        $northing = $falseNorthing->asMetres()->getValue() + $a * log(tan(M_PI / 4 + $latitude / 2));
1518
1519 9
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1520
    }
1521
1522
    /**
1523
     * Mercator (variant A)
1524
     * Note that in these formulas the parameter latitude of natural origin (latO) is not used. However for this
1525
     * Mercator (variant A) method the EPSG dataset includes this parameter, which must have a value of zero, for
1526
     * completeness in CRS labelling.
1527
     */
1528 18
    public function mercatorVariantA(
1529
        Projected $to,
1530
        Angle $latitudeOfNaturalOrigin,
0 ignored issues
show
Unused Code introduced by
The parameter $latitudeOfNaturalOrigin is not used and could be removed. ( Ignorable by Annotation )

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

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

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

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

1950
        /** @scrutinizer ignore-call */ 
1951
        $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...
1951
1952
        $horizontal = static::create($toLatitude, $toLongitude, null, $to->getHorizontal(), $this->epoch);
1953
        $vertical = VerticalPoint::create($toHeight, $to->getVertical(), $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

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

2070
        /** @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...
2071
        OSTNOSGM15Grid $eastingAndNorthingDifferenceFile
2072
    ): ProjectedPoint {
2073 2
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2074 2
        $etrs89NationalGrid = new Projected(
2075 2
            'ETRS89 / National Grid',
2076 2
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2077 2
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2078 2
            $osgb36NationalGrid->getBoundingArea()
2079
        );
2080
2081 2
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2082
2083 2
        return $eastingAndNorthingDifferenceFile->applyForwardHorizontalAdjustment($projected);
2084
    }
2085
2086
    /**
2087
     * Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB).
2088
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2089
     * coordinate differences.
2090
     */
2091 1
    public function geographic3DTo2DPlusGravityHeightOSGM15(
2092
        Compound $to,
2093
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
2094
    ): CompoundPoint {
2095 1
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2096 1
        $etrs89NationalGrid = new Projected(
2097 1
            'ETRS89 / National Grid',
2098 1
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2099 1
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2100 1
            $osgb36NationalGrid->getBoundingArea()
2101
        );
2102
2103 1
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2104
2105 1
        $horizontalPoint = self::create(
2106 1
            $this->latitude,
2107 1
            $this->longitude,
2108 1
            null,
2109 1
            $to->getHorizontal(),
2110 1
            $this->getCoordinateEpoch()
2111
        );
2112
2113 1
        $verticalPoint = VerticalPoint::create(
2114 1
            $this->height->subtract($geoidHeightCorrectionModelFile->getVerticalAdjustment($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

2114
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getVerticalAdjustment($projected)),
Loading history...
2115 1
            $to->getVertical(),
2116 1
            $this->getCoordinateEpoch()
2117
        );
2118
2119 1
        return CompoundPoint::create(
2120 1
            $horizontalPoint,
2121 1
            $verticalPoint,
2122 1
            $to,
2123 1
            $this->getCoordinateEpoch()
2124
        );
2125
    }
2126
2127
    /**
2128
     * Geographic3D to GravityRelatedHeight (OSGM-GB).
2129
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2130
     * coordinate differences.
2131
     */
2132 1
    public function geographic3DToGravityHeightOSGM15(
2133
        Vertical $to,
2134
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
2135
    ): VerticalPoint {
2136 1
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2137 1
        $etrs89NationalGrid = new Projected(
2138 1
            'ETRS89 / National Grid',
2139 1
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2140 1
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2141 1
            $osgb36NationalGrid->getBoundingArea()
2142
        );
2143
2144 1
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2145
2146 1
        return VerticalPoint::create(
2147 1
            $this->height->subtract($geoidHeightCorrectionModelFile->getVerticalAdjustment($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

2147
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getVerticalAdjustment($projected)),
Loading history...
2148 1
            $to,
2149 1
            $this->getCoordinateEpoch()
2150
        );
2151
    }
2152
2153
    /**
2154
     * NADCON5.
2155
     * Geodetic transformation operating on geographic coordinate differences by bi-quadratic interpolation.  Input
2156
     * expects longitudes to be positive east in range 0-360° (0° = Greenwich).
2157
     */
2158 7
    public function NADCON5(
2159
        Geographic $to,
2160
        NADCON5Grid $latitudeDifferenceFile,
2161
        NADCON5Grid $longitudeDifferenceFile,
2162
        ?NADCON5Grid $ellipsoidalHeightDifferenceFile,
2163
        bool $inReverse
2164
    ): self {
2165
        /*
2166
         * Ideally most of this logic (especially reverse case) would be in the NADCON5Grid class like the other grids,
2167
         * but NADCON5 uses different files for latitude/longitude/height that need to be combined at runtime so that
2168
         * isn't possible.
2169
         */
2170 7
        if (!$inReverse) {
2171 4
            $latitudeAdjustment = new ArcSecond($latitudeDifferenceFile->getForwardAdjustment($this));
2172 4
            $longitudeAdjustment = new ArcSecond($longitudeDifferenceFile->getForwardAdjustment($this));
2173 4
            $heightAdjustment = $this->getHeight() && $ellipsoidalHeightDifferenceFile ? new Metre($ellipsoidalHeightDifferenceFile->getForwardAdjustment($this)) : null;
2174
2175 4
            return self::create($this->latitude->add($latitudeAdjustment), $this->longitude->add($longitudeAdjustment), $heightAdjustment ? $this->height->add($heightAdjustment) : null, $to, $this->getCoordinateEpoch());
2176
        }
2177
2178 3
        $iteration = $this;
2179
2180
        do {
2181 3
            $prevIteration = $iteration;
2182 3
            $latitudeAdjustment = new ArcSecond($latitudeDifferenceFile->getForwardAdjustment($iteration));
2183 3
            $longitudeAdjustment = new ArcSecond($longitudeDifferenceFile->getForwardAdjustment($iteration));
2184 3
            $heightAdjustment = $this->getHeight() && $ellipsoidalHeightDifferenceFile ? new Metre($ellipsoidalHeightDifferenceFile->getForwardAdjustment($iteration)) : null;
2185 3
            $iteration = self::create($this->latitude->subtract($latitudeAdjustment), $this->longitude->subtract($longitudeAdjustment), $heightAdjustment ? $this->height->subtract($heightAdjustment) : null, $to, $this->getCoordinateEpoch());
2186 3
        } while (abs($iteration->latitude->subtract($prevIteration->latitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && abs($iteration->longitude->subtract($prevIteration->longitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && ($this->height === null || abs($iteration->height->subtract($prevIteration->height)->getValue()) > self::ITERATION_CONVERGENCE_GRID));
0 ignored issues
show
Bug introduced by
The method subtract() 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

2186
        } while (abs($iteration->latitude->subtract($prevIteration->latitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && abs($iteration->longitude->subtract($prevIteration->longitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && ($this->height === null || abs($iteration->height->/** @scrutinizer ignore-call */ subtract($prevIteration->height)->getValue()) > self::ITERATION_CONVERGENCE_GRID));

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...
Bug introduced by
It seems like $prevIteration->height can also be of type null; however, parameter $unit of PHPCoord\UnitOfMeasure\Length\Length::subtract() 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
        } while (abs($iteration->latitude->subtract($prevIteration->latitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && abs($iteration->longitude->subtract($prevIteration->longitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && ($this->height === null || abs($iteration->height->subtract(/** @scrutinizer ignore-type */ $prevIteration->height)->getValue()) > self::ITERATION_CONVERGENCE_GRID));
Loading history...
2187
2188 3
        return $iteration;
2189
    }
2190
2191
    /**
2192
     * NTv2
2193
     * Geodetic transformation operating on geographic coordinate differences by bi-linear interpolation.  Supersedes
2194
     * NTv1 (transformation method code 9614).  Input expects longitudes to be positive west.
2195
     */
2196 5
    public function NTv2(
2197
        Geographic $to,
2198
        NTv2Grid $latitudeAndLongitudeDifferenceFile,
2199
        bool $inReverse
2200
    ): self {
2201 5
        if (!$inReverse) {
2202 3
            return $latitudeAndLongitudeDifferenceFile->applyForwardAdjustment($this, $to);
2203
        }
2204
2205 2
        return $latitudeAndLongitudeDifferenceFile->applyReverseAdjustment($this, $to);
2206
    }
2207
2208
    /**
2209
     * Geocentric translation by Grid Interpolation (IGN France).
2210
     */
2211 3
    public function geocentricTranslationByGridInterpolationIGNF(
2212
        Geographic $to,
2213
        IGNGeocentricTranslationGrid $geocentricTranslationFile,
2214
        bool $inReverse
2215
    ): self {
2216 3
        if (!$inReverse) {
2217 2
            return $geocentricTranslationFile->applyForwardAdjustment($this, $to);
2218
        }
2219
2220 1
        return $geocentricTranslationFile->applyReverseAdjustment($this, $to);
2221
    }
2222
2223 351
    public function asGeographicValue(): GeographicValue
2224
    {
2225 351
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2226
    }
2227
2228 18
    public function asUTMPoint(): UTMPoint
2229
    {
2230 18
        $hemisphere = $this->getLatitude()->asDegrees()->getValue() >= 0 ? UTMPoint::HEMISPHERE_NORTH : UTMPoint::HEMISPHERE_SOUTH;
2231 18
        $latitudeOfNaturalOrigin = new Degree(0);
2232 18
        $initialLongitude = new Degree(-180);
2233 18
        $scaleFactorAtNaturalOrigin = new Unity(0.9996);
2234 18
        $falseEasting = new Metre(500000);
2235 18
        $falseNorthing = $hemisphere === UTMPoint::HEMISPHERE_NORTH ? new Metre(0) : new Metre(10000000);
2236 18
        $Z = (int) ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / 6) % (360 / 6) + 1;
2237 18
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * 6 - 3));
2238
2239 18
        $projectedCRS = new Projected(
2240 18
            'UTM/' . $this->crs->getSRID(),
2241 18
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2242 18
            $this->crs->getDatum(),
2243 18
            BoundingArea::createWorld() // this is a dummy CRS for the transform only, details don't matter
2244
        );
2245
2246 18
        $asProjected = $this->transverseMercator($projectedCRS, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
2247
2248 18
        return new UTMPoint($asProjected->getEasting(), $asProjected->getNorthing(), $Z, $hemisphere, $this->crs, $this->epoch);
2249
    }
2250
}
2251