Passed
Push — master ( fb3d41...e1b481 )
by Doug
25:45
created

GeographicPoint::krovakModified()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 47
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 20
c 0
b 0
f 0
dl 0
loc 47
ccs 21
cts 21
cp 1
rs 9.6
cc 1
nc 1
nop 20
crap 1

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

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

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

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

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

2069
        /** @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...
2070
        OSTNOSGM15Grid $eastingAndNorthingDifferenceFile
2071
    ): ProjectedPoint {
2072 2
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
2073 2
        $etrs89NationalGrid = new Projected(
2074 2
            'ETRS89 / National Grid',
2075 2
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2076 2
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2077 2
            $osgb36NationalGrid->getBoundingArea()
2078
        );
2079
2080 2
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2081
2082 2
        return $eastingAndNorthingDifferenceFile->applyForwardHorizontalAdjustment($projected);
2083
    }
2084
2085
    /**
2086
     * Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB).
2087
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
2088
     * coordinate differences.
2089
     */
2090 1
    public function geographic3DTo2DPlusGravityHeightOSGM15(
2091
        Compound $to,
2092
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile,
2093
        string $EPSGCodeForInterpolationCRS
0 ignored issues
show
Unused Code introduced by
The parameter $EPSGCodeForInterpolationCRS 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

2093
        /** @scrutinizer ignore-unused */ string $EPSGCodeForInterpolationCRS

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...
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 = null,
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
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...
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...
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
        } else {
2204 2
            return $latitudeAndLongitudeDifferenceFile->applyReverseAdjustment($this, $to);
2205
        }
2206
    }
2207
2208 360
    public function asGeographicValue(): GeographicValue
2209
    {
2210 360
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2211
    }
2212
2213 18
    public function asUTMPoint(): UTMPoint
2214
    {
2215 18
        $hemisphere = $this->getLatitude()->asDegrees()->getValue() >= 0 ? UTMPoint::HEMISPHERE_NORTH : UTMPoint::HEMISPHERE_SOUTH;
2216 18
        $latitudeOfNaturalOrigin = new Degree(0);
2217 18
        $initialLongitude = new Degree(-180);
2218 18
        $scaleFactorAtNaturalOrigin = new Unity(0.9996);
2219 18
        $falseEasting = new Metre(500000);
2220 18
        $falseNorthing = $hemisphere === UTMPoint::HEMISPHERE_NORTH ? new Metre(0) : new Metre(10000000);
2221 18
        $Z = ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / 6) % (360 / 6) + 1;
2222 18
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * 6 - 3));
2223
2224 18
        $projectedCRS = new Projected(
2225 18
            'UTM/' . $this->crs->getSRID(),
2226 18
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2227 18
            $this->crs->getDatum(),
2228 18
            BoundingArea::createWorld() // this is a dummy CRS for the transform only, details don't matter
2229
        );
2230
2231 18
        $asProjected = $this->transverseMercator($projectedCRS, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
2232
2233 18
        return new UTMPoint($asProjected->getEasting(), $asProjected->getNorthing(), $Z, $hemisphere, $this->crs, $this->epoch);
2234
    }
2235
}
2236