Passed
Push — master ( 2b8bad...6fb66b )
by Doug
25:16
created

GeographicPoint::madridToED50Polynomial()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 16
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

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

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use function abs;
12
use function asinh;
13
use function atan;
14
use function atan2;
15
use function atanh;
16
use function cos;
17
use function cosh;
18
use function count;
19
use DateTime;
20
use DateTimeImmutable;
21
use DateTimeInterface;
22
use function hypot;
23
use function implode;
24
use function is_nan;
25
use function log;
26
use const M_E;
27
use const M_PI;
28
use function max;
29
use PHPCoord\CoordinateOperation\AutoConversion;
30
use PHPCoord\CoordinateOperation\ComplexNumber;
31
use PHPCoord\CoordinateOperation\ConvertiblePoint;
32
use PHPCoord\CoordinateOperation\GeocentricValue;
33
use PHPCoord\CoordinateOperation\GeographicGeoidHeightGrid;
34
use PHPCoord\CoordinateOperation\GeographicGrid;
35
use PHPCoord\CoordinateOperation\GeographicValue;
36
use PHPCoord\CoordinateOperation\NADCON5Grid;
37
use PHPCoord\CoordinateOperation\NADCON5Grids;
38
use PHPCoord\CoordinateOperation\OSTNOSGM15Grid;
39
use PHPCoord\CoordinateReferenceSystem\Compound;
40
use PHPCoord\CoordinateReferenceSystem\Geocentric;
41
use PHPCoord\CoordinateReferenceSystem\Geographic;
42
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
43
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
44
use PHPCoord\CoordinateReferenceSystem\Projected;
0 ignored issues
show
Bug introduced by
The type PHPCoord\CoordinateReferenceSystem\Projected was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

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

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

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

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

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

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

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

1984
        /** @scrutinizer ignore-call */ 
1985
        $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...
1985
1986
        $horizontal = static::create($to->getHorizontal(), $toLatitude, $toLongitude, null, $this->epoch);
0 ignored issues
show
Bug introduced by
It seems like $to->getHorizontal() can also be of type PHPCoord\CoordinateReferenceSystem\Geocentric; however, parameter $crs of PHPCoord\GeographicPoint::create() does only seem to accept PHPCoord\CoordinateRefer...enceSystem\Geographic3D, maybe add an additional type check? ( Ignorable by Annotation )

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

1986
        $horizontal = static::create(/** @scrutinizer ignore-type */ $to->getHorizontal(), $toLatitude, $toLongitude, null, $this->epoch);
Loading history...
1987
        $vertical = VerticalPoint::create($to->getVertical(), $toHeight, $this->epoch);
0 ignored issues
show
Bug introduced by
It seems like $toHeight can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

2170
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
Loading history...
2171 1
            $this->getCoordinateEpoch()
2172
        );
2173
    }
2174
2175
    /**
2176
     * Geog3D to Geog2D+GravityRelatedHeight.
2177
     */
2178 12
    public function geographic3DTo2DPlusGravityHeightFromGrid(
2179
        Compound $to,
2180
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2181
    ): CompoundPoint {
2182 12
        $horizontalPoint = self::create(
2183 12
            $to->getHorizontal(),
0 ignored issues
show
Bug introduced by
It seems like $to->getHorizontal() can also be of type PHPCoord\CoordinateReferenceSystem\Geocentric; however, parameter $crs of PHPCoord\GeographicPoint::create() does only seem to accept PHPCoord\CoordinateRefer...enceSystem\Geographic3D, maybe add an additional type check? ( Ignorable by Annotation )

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

2183
            /** @scrutinizer ignore-type */ $to->getHorizontal(),
Loading history...
2184 12
            $this->latitude,
2185 12
            $this->longitude,
2186
            null,
2187 12
            $this->getCoordinateEpoch()
2188
        );
2189
2190 12
        $verticalPoint = VerticalPoint::create(
2191 12
            $to->getVertical(),
2192 12
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...eightAdjustment($this)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

2192
            /** @scrutinizer ignore-type */ $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
Loading history...
2193 12
            $this->getCoordinateEpoch()
2194
        );
2195
2196 12
        return CompoundPoint::create(
2197
            $to,
2198
            $horizontalPoint,
2199
            $verticalPoint,
2200 12
            $this->getCoordinateEpoch()
2201
        );
2202
    }
2203
2204
    /**
2205
     * Geographic3D to GravityRelatedHeight.
2206
     */
2207 7
    public function geographic3DToGravityHeightFromGrid(
2208
        Vertical $to,
2209
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
2210
    ): VerticalPoint {
2211 7
        return VerticalPoint::create(
2212
            $to,
2213 7
            $this->height->subtract($geoidHeightCorrectionModelFile->getHeightAdjustment($this)),
0 ignored issues
show
Bug introduced by
It seems like $this->height->subtract(...eightAdjustment($this)) can also be of type null; however, parameter $height of PHPCoord\VerticalPoint::create() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

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

2278
        return new UTMPoint($this->crs, $asProjected->getEasting(), $asProjected->/** @scrutinizer ignore-call */ getNorthing(), $zone, $hemisphere, $this->epoch);
Loading history...
2279
    }
2280
}
2281