Passed
Push — master ( 1c4ceb...50b2e6 )
by Doug
44:53
created

lambertConicConformal2SPMichigan()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 35
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 22
nc 1
nop 8
dl 0
loc 35
rs 9.568
c 0
b 0
f 0
ccs 23
cts 23
cp 1
crap 1

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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

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

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

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

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

1950
        $vertical = VerticalPoint::create(/** @scrutinizer ignore-type */ $toHeight, $to->getVertical(), $this->epoch);
Loading history...
1951
1952
        return CompoundPoint::create($horizontal, $vertical, $to, $this->epoch);
1953
    }
1954
1955
    /**
1956
     * General polynomial.
1957
     * @param Coefficient[] $powerCoefficients
1958
     */
1959 18
    public function generalPolynomial(
1960
        Geographic $to,
1961
        Angle $ordinate1OfEvaluationPointInSourceCRS,
1962
        Angle $ordinate2OfEvaluationPointInSourceCRS,
1963
        Angle $ordinate1OfEvaluationPointInTargetCRS,
1964
        Angle $ordinate2OfEvaluationPointInTargetCRS,
1965
        Scale $scalingFactorForSourceCRSCoordDifferences,
1966
        Scale $scalingFactorForTargetCRSCoordDifferences,
1967
        Scale $A0,
1968
        Scale $B0,
1969
        array $powerCoefficients
1970
    ): self {
1971 18
        $xs = $this->latitude->getValue();
1972 18
        $ys = $this->longitude->getValue();
1973
1974 18
        $t = $this->generalPolynomialUnitless(
1975 18
            $xs,
1976
            $ys,
1977
            $ordinate1OfEvaluationPointInSourceCRS,
1978
            $ordinate2OfEvaluationPointInSourceCRS,
1979
            $ordinate1OfEvaluationPointInTargetCRS,
1980
            $ordinate2OfEvaluationPointInTargetCRS,
1981
            $scalingFactorForSourceCRSCoordDifferences,
1982
            $scalingFactorForTargetCRSCoordDifferences,
1983
            $A0,
1984
            $B0,
1985
            $powerCoefficients
1986
        );
1987
1988 18
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
1989 18
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
1990 18
            $xtUnit = Angle::EPSG_DEGREE;
1991
        }
1992 18
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
1993 18
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
1994 18
            $ytUnit = Angle::EPSG_DEGREE;
1995
        }
1996
1997 18
        return static::create(
1998 18
            Angle::makeUnit($t['xt'], $xtUnit),
1999 18
            Angle::makeUnit($t['yt'], $ytUnit),
2000 18
            $this->height,
2001
            $to,
2002 18
            $this->epoch
2003
        );
2004
    }
2005
2006
    /**
2007
     * Reversible polynomial.
2008
     * @param Coefficient[] $powerCoefficients
2009
     */
2010 36
    public function reversiblePolynomial(
2011
        Geographic $to,
2012
        Angle $ordinate1OfEvaluationPoint,
2013
        Angle $ordinate2OfEvaluationPoint,
2014
        Scale $scalingFactorForCoordDifferences,
2015
        Scale $A0,
2016
        Scale $B0,
2017
        $powerCoefficients
2018
    ): self {
2019 36
        $xs = $this->latitude->getValue();
2020 36
        $ys = $this->longitude->getValue();
2021
2022 36
        $t = $this->reversiblePolynomialUnitless(
2023 36
            $xs,
2024
            $ys,
2025
            $ordinate1OfEvaluationPoint,
2026
            $ordinate2OfEvaluationPoint,
2027
            $scalingFactorForCoordDifferences,
2028
            $A0,
2029
            $B0,
2030
            $powerCoefficients
2031
        );
2032
2033 36
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2034 36
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2035 36
            $xtUnit = Angle::EPSG_DEGREE;
2036
        }
2037 36
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2038 36
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2039 36
            $ytUnit = Angle::EPSG_DEGREE;
2040
        }
2041
2042 36
        return static::create(
2043 36
            Angle::makeUnit($t['xt'], $xtUnit),
2044 36
            Angle::makeUnit($t['yt'], $ytUnit),
2045 36
            $this->height,
2046
            $to,
2047 36
            $this->epoch
2048
        );
2049
    }
2050
2051
    /**
2052
     * Axis Order Reversal.
2053
     */
2054
    public function axisReversal(
2055
        Geographic $to
2056
    ): self {
2057
        // axes are read in from the CRS, this is a book-keeping adjustment only
2058
        return static::create($this->latitude, $this->longitude, $this->height, $to, $this->epoch);
2059
    }
2060
2061
    /**
2062
     * Ordnance Survey National Transformation
2063
     * Geodetic transformation between ETRS89 (or WGS 84) and OSGB36 / National Grid.  Uses ETRS89 / National Grid as
2064
     * an intermediate coordinate system for bi-linear interpolation of gridded grid coordinate differences.
2065
     */
2066
    public function OSTN15(
2067
        Projected $to,
2068
        OSTNOSGM15Grid $eastingAndNorthingDifferenceFile
2069
    ): ProjectedPoint {
2070
        $etrs89NationalGrid = new Projected(
2071
            'ETRS89 / National Grid',
2072
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2073
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2074
            $to->getBoundingArea()
2075
        );
2076
2077
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2078
2079
        return $eastingAndNorthingDifferenceFile->applyForwardAdjustment($projected);
2080
    }
2081
2082
    /**
2083
     * NADCON5.
2084
     * Geodetic transformation operating on geographic coordinate differences by bi-quadratic interpolation.  Input
2085
     * expects longitudes to be positive east in range 0-360° (0° = Greenwich).
2086
     */
2087
    public function NADCON5(
2088
        Geographic $to,
2089
        NADCON5Grid $latitudeDifferenceFile,
2090
        NADCON5Grid $longitudeDifferenceFile,
2091
        ?NADCON5Grid $ellipsoidalHeightDifferenceFile = null,
2092
        bool $inReverse
2093
    ): self {
2094
        /*
2095
         * Ideally most of this logic (especially reverse case) would be in the NADCON5Grid class like the other grids,
2096
         * but NADCON5 uses different files for latitude/longitude/height that need to be combined at runtime so that
2097
         * isn't possible.
2098
         */
2099
        if (!$inReverse) {
2100
            $latitudeAdjustment = new ArcSecond($latitudeDifferenceFile->getForwardAdjustment($this));
2101
            $longitudeAdjustment = new ArcSecond($longitudeDifferenceFile->getForwardAdjustment($this));
2102
            $heightAdjustment = $this->getHeight() && $ellipsoidalHeightDifferenceFile ? new Metre($ellipsoidalHeightDifferenceFile->getForwardAdjustment($this)) : null;
2103
2104
            return self::create($this->latitude->add($latitudeAdjustment), $this->longitude->add($longitudeAdjustment), $heightAdjustment ? $this->height->add($heightAdjustment) : null, $to, $this->getCoordinateEpoch());
2105
        }
2106
2107
        $iteration = $this;
2108
2109
        do {
2110
            $prevIteration = $iteration;
2111
            $latitudeAdjustment = new ArcSecond($latitudeDifferenceFile->getForwardAdjustment($iteration));
2112
            $longitudeAdjustment = new ArcSecond($longitudeDifferenceFile->getForwardAdjustment($iteration));
2113
            $heightAdjustment = $this->getHeight() && $ellipsoidalHeightDifferenceFile ? new Metre($ellipsoidalHeightDifferenceFile->getForwardAdjustment($iteration)) : null;
2114
            $iteration = self::create($this->latitude->subtract($latitudeAdjustment), $this->longitude->subtract($longitudeAdjustment), $heightAdjustment ? $this->height->subtract($heightAdjustment) : null, $to, $this->getCoordinateEpoch());
2115
        } while (abs($iteration->latitude->subtract($prevIteration->latitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && abs($iteration->longitude->subtract($prevIteration->longitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && ($this->height === null || abs($iteration->height->subtract($prevIteration->height)->getValue()) > self::ITERATION_CONVERGENCE_GRID));
0 ignored issues
show
Bug introduced by
The method subtract() does not exist on null. ( Ignorable by Annotation )

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

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

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
It seems like $prevIteration->height can also be of type null; however, parameter $unit of PHPCoord\UnitOfMeasure\Length\Length::subtract() does only seem to accept PHPCoord\UnitOfMeasure\Length\Length, maybe add an additional type check? ( Ignorable by Annotation )

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

2115
        } while (abs($iteration->latitude->subtract($prevIteration->latitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && abs($iteration->longitude->subtract($prevIteration->longitude)->getValue()) > self::ITERATION_CONVERGENCE_GRID && ($this->height === null || abs($iteration->height->subtract(/** @scrutinizer ignore-type */ $prevIteration->height)->getValue()) > self::ITERATION_CONVERGENCE_GRID));
Loading history...
2116
2117
        return $iteration;
2118
    }
2119
2120 351
    public function asGeographicValue(): GeographicValue
2121
    {
2122 351
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2123
    }
2124
2125 18
    public function asUTMPoint(): UTMPoint
2126
    {
2127 18
        $hemisphere = $this->getLatitude()->asDegrees()->getValue() >= 0 ? UTMPoint::HEMISPHERE_NORTH : UTMPoint::HEMISPHERE_SOUTH;
2128 18
        $latitudeOfNaturalOrigin = new Degree(0);
2129 18
        $initialLongitude = new Degree(-180);
2130 18
        $scaleFactorAtNaturalOrigin = new Unity(0.9996);
2131 18
        $falseEasting = new Metre(500000);
2132 18
        $falseNorthing = $hemisphere === UTMPoint::HEMISPHERE_NORTH ? new Metre(0) : new Metre(10000000);
2133 18
        $Z = ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / 6) % (360 / 6) + 1;
2134 18
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * 6 - 3));
2135
2136 18
        $projectedCRS = new Projected(
2137 18
            'UTM/' . $this->crs->getSRID(),
2138 18
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2139 18
            $this->crs->getDatum(),
2140 18
            BoundingArea::createWorld() // this is a dummy CRS for the transform only, details don't matter
2141
        );
2142
2143 18
        $asProjected = $this->transverseMercator($projectedCRS, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
2144
2145 18
        return new UTMPoint($asProjected->getEasting(), $asProjected->getNorthing(), $Z, $hemisphere, $this->crs, $this->epoch);
2146
    }
2147
}
2148