Passed
Push — 4.x ( 1d49e7...4cde9a )
by Doug
06:44
created

GeographicPoint::krovakModified()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 47
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 1

Importance

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

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use function cos;
12
use DateTime;
13
use DateTimeImmutable;
14
use DateTimeInterface;
15
use function get_class;
16
use function implode;
17
use InvalidArgumentException;
18
use function log;
19
use PHPCoord\CoordinateOperation\AutoConversion;
20
use PHPCoord\CoordinateOperation\ComplexNumber;
21
use PHPCoord\CoordinateOperation\GeocentricValue;
22
use PHPCoord\CoordinateOperation\GeographicValue;
23
use PHPCoord\CoordinateReferenceSystem\Compound;
24
use PHPCoord\CoordinateReferenceSystem\Geocentric;
25
use PHPCoord\CoordinateReferenceSystem\Geographic;
26
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
27
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
28
use PHPCoord\CoordinateReferenceSystem\Projected;
29
use PHPCoord\CoordinateSystem\Axis;
30
use PHPCoord\Datum\Ellipsoid;
31
use PHPCoord\Exception\InvalidCoordinateReferenceSystemException;
32
use PHPCoord\Exception\UnknownAxisException;
33
use PHPCoord\UnitOfMeasure\Angle\Angle;
34
use PHPCoord\UnitOfMeasure\Angle\ArcSecond;
35
use PHPCoord\UnitOfMeasure\Angle\Degree;
36
use PHPCoord\UnitOfMeasure\Angle\Radian;
37
use PHPCoord\UnitOfMeasure\Length\Length;
38
use PHPCoord\UnitOfMeasure\Length\Metre;
39
use PHPCoord\UnitOfMeasure\Scale\Coefficient;
40
use PHPCoord\UnitOfMeasure\Scale\Scale;
41
use PHPCoord\UnitOfMeasure\Scale\Unity;
42
use function sin;
43
use function sprintf;
44
use function sqrt;
45
use function tan;
46
use TypeError;
47
48
/**
49
 * Coordinate representing a point on an ellipsoid.
50
 */
51
class GeographicPoint extends Point
52
{
53
    use AutoConversion;
54
55
    /**
56
     * Latitude.
57
     */
58
    protected Angle $latitude;
59
60
    /**
61
     * Longitude.
62
     */
63
    protected Angle $longitude;
64
65
    /**
66
     * Height above ellipsoid (N.B. *not* height above ground, sea-level or anything else tangible).
67
     */
68
    protected ?Length $height;
69
70
    /**
71
     * Coordinate reference system.
72
     */
73
    protected Geographic $crs;
74
75
    /**
76
     * Coordinate epoch (date for which the specified coordinates represented this point).
77
     */
78
    protected ?DateTimeImmutable $epoch;
79
80 123
    protected function __construct(Angle $latitude, Angle $longitude, ?Length $height, Geographic $crs, ?DateTimeInterface $epoch = null)
81
    {
82 123
        if (!$crs instanceof Geographic2D && !$crs instanceof Geographic3D) {
83
            throw new TypeError(sprintf("A geographic point must be associated with a geographic CRS, but a '%s' CRS was given", get_class($crs)));
84
        }
85
86 123
        if ($crs instanceof Geographic2D && $height !== null) {
87 1
            throw new InvalidCoordinateReferenceSystemException('A 2D geographic point must not include a height');
88
        }
89
90 122
        if ($crs instanceof Geographic3D && $height === null) {
91 1
            throw new InvalidCoordinateReferenceSystemException('A 3D geographic point must include a height, none given');
92
        }
93
94 121
        $this->crs = $crs;
95
96 121
        $this->latitude = Angle::convert($latitude, $this->getAxisByName(Axis::GEODETIC_LATITUDE)->getUnitOfMeasureId());
97 121
        $this->longitude = Angle::convert($longitude, $this->getAxisByName(Axis::GEODETIC_LONGITUDE)->getUnitOfMeasureId());
98
99 121
        if ($height) {
100 16
            $this->height = Length::convert($height, $this->getAxisByName(Axis::ELLIPSOIDAL_HEIGHT)->getUnitOfMeasureId());
101
        } else {
102 109
            $this->height = null;
103
        }
104
105 121
        if ($epoch instanceof DateTime) {
106 1
            $epoch = DateTimeImmutable::createFromMutable($epoch);
107
        }
108 121
        $this->epoch = $epoch;
109 121
    }
110
111
    /**
112
     * @param Angle   $latitude  refer to CRS for preferred unit of measure, but any angle unit accepted
113
     * @param Angle   $longitude refer to CRS for preferred unit of measure, but any angle unit accepted
114
     * @param ?Length $height    refer to CRS for preferred unit of measure, but any length unit accepted
115
     */
116 123
    public static function create(Angle $latitude, Angle $longitude, ?Length $height, Geographic $crs, ?DateTimeInterface $epoch = null): self
117
    {
118 123
        return new static($latitude, $longitude, $height, $crs, $epoch);
119
    }
120
121 72
    public function getLatitude(): Angle
122
    {
123 72
        return $this->latitude;
124
    }
125
126 72
    public function getLongitude(): Angle
127
    {
128 72
        return $this->longitude;
129
    }
130
131 35
    public function getHeight(): ?Length
132
    {
133 35
        return $this->height;
134
    }
135
136 121
    public function getCRS(): Geographic
137
    {
138 121
        return $this->crs;
139
    }
140
141 9
    public function getCoordinateEpoch(): ?DateTimeImmutable
142
    {
143 9
        return $this->epoch;
144
    }
145
146
    /**
147
     * Calculate surface distance between two points.
148
     */
149 1
    public function calculateDistance(Point $to): Length
150
    {
151 1
        if ($to->getCRS()->getSRID() !== $this->crs->getSRID()) {
152
            throw new InvalidArgumentException('Can only calculate distances between two points in the same CRS');
153
        }
154
155
        //Mean radius definition taken from Wikipedia
156
        /** @var Ellipsoid $ellipsoid */
157 1
        $ellipsoid = $this->getCRS()->getDatum()->getEllipsoid();
158 1
        $radius = ((2 * $ellipsoid->getSemiMajorAxis()->asMetres()->getValue()) + $ellipsoid->getSemiMinorAxis()->asMetres()->getValue()) / 3;
159
160 1
        return new Metre(self::acos(sin($this->latitude->asRadians()->getValue()) * sin($to->latitude->asRadians()->getValue()) + cos($this->latitude->asRadians()->getValue()) * cos($to->latitude->asRadians()->getValue()) * cos($to->longitude->asRadians()->getValue() - $this->longitude->asRadians()->getValue())) * $radius);
161
    }
162
163 4
    public function __toString(): string
164
    {
165 4
        $values = [];
166 4
        foreach ($this->getCRS()->getCoordinateSystem()->getAxes() as $axis) {
167 4
            if ($axis->getName() === Axis::GEODETIC_LATITUDE) {
168 4
                $values[] = $this->latitude;
169 4
            } elseif ($axis->getName() === Axis::GEODETIC_LONGITUDE) {
170 4
                $values[] = $this->longitude;
171 1
            } elseif ($axis->getName() === Axis::ELLIPSOIDAL_HEIGHT) {
172 1
                $values[] = $this->height;
173
            } else {
174
                throw new UnknownAxisException(); // @codeCoverageIgnore
175
            }
176
        }
177
178 4
        return '(' . implode(', ', $values) . ')';
179
    }
180
181
    /**
182
     * Geographic/geocentric conversions
183
     * In applications it is often concatenated with the 3- 7- or 10-parameter transformations 9603, 9606, 9607 or
184
     * 9636 to form a geographic to geographic transformation.
185
     */
186 1
    public function geographicGeocentric(
187
        Geocentric $to
188
    ): GeocentricPoint {
189 1
        $geographicValue = new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
190 1
        $asGeocentric = $geographicValue->asGeocentricValue();
191
192 1
        return GeocentricPoint::create($asGeocentric->getX(), $asGeocentric->getY(), $asGeocentric->getZ(), $to, $this->epoch);
193
    }
194
195
    /**
196
     * Coordinate Frame rotation (geog2D/geog3D domain)
197
     * Note the analogy with the Position Vector tfm (codes 9606/1037) but beware of the differences!  The Position Vector
198
     * convention is used by IAG and recommended by ISO 19111. See methods 1032/1038/9607 for similar tfms operating
199
     * between other CRS types.
200
     */
201 2
    public function coordinateFrameRotation(
202
        Geographic $to,
203
        Length $xAxisTranslation,
204
        Length $yAxisTranslation,
205
        Length $zAxisTranslation,
206
        Angle $xAxisRotation,
207
        Angle $yAxisRotation,
208
        Angle $zAxisRotation,
209
        Scale $scaleDifference
210
    ): self {
211 2
        return $this->coordinateFrameMolodenskyBadekas(
212 2
            $to,
213
            $xAxisTranslation,
214
            $yAxisTranslation,
215
            $zAxisTranslation,
216
            $xAxisRotation,
217
            $yAxisRotation,
218
            $zAxisRotation,
219
            $scaleDifference,
220 2
            new Metre(0),
221 2
            new Metre(0),
222 2
            new Metre(0)
223
        );
224
    }
225
226
    /**
227
     * Molodensky-Badekas (CF geog2D/geog3D domain)
228
     * See method codes 1034 and 1039/9636 for this operation in other coordinate domains and method code 1062/1063 for the
229
     * opposite rotation convention in geographic 2D domain.
230
     */
231 4
    public function coordinateFrameMolodenskyBadekas(
232
        Geographic $to,
233
        Length $xAxisTranslation,
234
        Length $yAxisTranslation,
235
        Length $zAxisTranslation,
236
        Angle $xAxisRotation,
237
        Angle $yAxisRotation,
238
        Angle $zAxisRotation,
239
        Scale $scaleDifference,
240
        Length $ordinate1OfEvaluationPoint,
241
        Length $ordinate2OfEvaluationPoint,
242
        Length $ordinate3OfEvaluationPoint
243
    ): self {
244 4
        $geographicValue = new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
245 4
        $asGeocentric = $geographicValue->asGeocentricValue();
246
247 4
        $xs = $asGeocentric->getX()->asMetres()->getValue();
248 4
        $ys = $asGeocentric->getY()->asMetres()->getValue();
249 4
        $zs = $asGeocentric->getZ()->asMetres()->getValue();
250 4
        $tx = $xAxisTranslation->asMetres()->getValue();
251 4
        $ty = $yAxisTranslation->asMetres()->getValue();
252 4
        $tz = $zAxisTranslation->asMetres()->getValue();
253 4
        $rx = $xAxisRotation->asRadians()->getValue();
254 4
        $ry = $yAxisRotation->asRadians()->getValue();
255 4
        $rz = $zAxisRotation->asRadians()->getValue();
256 4
        $M = 1 + $scaleDifference->asUnity()->getValue();
257 4
        $xp = $ordinate1OfEvaluationPoint->asMetres()->getValue();
258 4
        $yp = $ordinate2OfEvaluationPoint->asMetres()->getValue();
259 4
        $zp = $ordinate3OfEvaluationPoint->asMetres()->getValue();
260
261 4
        $xt = $M * ((($xs - $xp) * 1) + (($ys - $yp) * $rz) + (($zs - $zp) * -$ry)) + $tx + $xp;
262 4
        $yt = $M * ((($xs - $xp) * -$rz) + (($ys - $yp) * 1) + (($zs - $zp) * $rx)) + $ty + $yp;
263 4
        $zt = $M * ((($xs - $xp) * $ry) + (($ys - $yp) * -$rx) + (($zs - $zp) * 1)) + $tz + $zp;
264 4
        $newGeocentric = new GeocentricValue(new Metre($xt), new Metre($yt), new Metre($zt), $to->getDatum());
265 4
        $newGeographic = $newGeocentric->asGeographicValue();
266
267 4
        return static::create($newGeographic->getLatitude(), $newGeographic->getLongitude(), $to instanceof Geographic3D ? $newGeographic->getHeight() : null, $to, $this->epoch);
268
    }
269
270
    /**
271
     * Position Vector transformation (geog2D/geog3D domain)
272
     * Note the analogy with the Coordinate Frame rotation (code 9607/1038) but beware of the differences!  The Position
273
     * Vector convention is used by IAG and recommended by ISO 19111. See methods 1033/1037/9606 for similar tfms
274
     * operating between other CRS types.
275
     */
276 4
    public function positionVectorTransformation(
277
        Geographic $to,
278
        Length $xAxisTranslation,
279
        Length $yAxisTranslation,
280
        Length $zAxisTranslation,
281
        Angle $xAxisRotation,
282
        Angle $yAxisRotation,
283
        Angle $zAxisRotation,
284
        Scale $scaleDifference
285
    ): self {
286 4
        return $this->positionVectorMolodenskyBadekas(
287 4
            $to,
288
            $xAxisTranslation,
289
            $yAxisTranslation,
290
            $zAxisTranslation,
291
            $xAxisRotation,
292
            $yAxisRotation,
293
            $zAxisRotation,
294
            $scaleDifference,
295 4
            new Metre(0),
296 4
            new Metre(0),
297 4
            new Metre(0)
298
        );
299
    }
300
301
    /**
302
     * Molodensky-Badekas (PV geog2D/geog3D domain)
303
     * See method codes 1061 and 1062/1063 for this operation in other coordinate domains and method code 1039/9636 for opposite
304
     * rotation in geographic 2D/3D domain.
305
     */
306 6
    public function positionVectorMolodenskyBadekas(
307
        Geographic $to,
308
        Length $xAxisTranslation,
309
        Length $yAxisTranslation,
310
        Length $zAxisTranslation,
311
        Angle $xAxisRotation,
312
        Angle $yAxisRotation,
313
        Angle $zAxisRotation,
314
        Scale $scaleDifference,
315
        Length $ordinate1OfEvaluationPoint,
316
        Length $ordinate2OfEvaluationPoint,
317
        Length $ordinate3OfEvaluationPoint
318
    ): self {
319 6
        $geographicValue = new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
320 6
        $asGeocentric = $geographicValue->asGeocentricValue();
321
322 6
        $xs = $asGeocentric->getX()->asMetres()->getValue();
323 6
        $ys = $asGeocentric->getY()->asMetres()->getValue();
324 6
        $zs = $asGeocentric->getZ()->asMetres()->getValue();
325 6
        $tx = $xAxisTranslation->asMetres()->getValue();
326 6
        $ty = $yAxisTranslation->asMetres()->getValue();
327 6
        $tz = $zAxisTranslation->asMetres()->getValue();
328 6
        $rx = $xAxisRotation->asRadians()->getValue();
329 6
        $ry = $yAxisRotation->asRadians()->getValue();
330 6
        $rz = $zAxisRotation->asRadians()->getValue();
331 6
        $M = 1 + $scaleDifference->asUnity()->getValue();
332 6
        $xp = $ordinate1OfEvaluationPoint->asMetres()->getValue();
333 6
        $yp = $ordinate2OfEvaluationPoint->asMetres()->getValue();
334 6
        $zp = $ordinate3OfEvaluationPoint->asMetres()->getValue();
335
336 6
        $xt = $M * ((($xs - $xp) * 1) + (($ys - $yp) * -$rz) + (($zs - $zp) * $ry)) + $tx + $xp;
337 6
        $yt = $M * ((($xs - $xp) * $rz) + (($ys - $yp) * 1) + (($zs - $zp) * -$rx)) + $ty + $yp;
338 6
        $zt = $M * ((($xs - $xp) * -$ry) + (($ys - $yp) * $rx) + (($zs - $zp) * 1)) + $tz + $zp;
339 6
        $newGeocentric = new GeocentricValue(new Metre($xt), new Metre($yt), new Metre($zt), $to->getDatum());
340 6
        $newGeographic = $newGeocentric->asGeographicValue();
341
342 6
        return static::create($newGeographic->getLatitude(), $newGeographic->getLongitude(), $to instanceof Geographic3D ? $newGeographic->getHeight() : null, $to, $this->epoch);
343
    }
344
345
    /**
346
     * Geocentric translations
347
     * This method allows calculation of geocentric coords in the target system by adding the parameter values to the
348
     * corresponding coordinates of the point in the source system. See methods 1031 and 1035 for similar tfms
349
     * operating between other CRSs types.
350
     */
351 1
    public function geocentricTranslation(
352
        Geographic $to,
353
        Length $xAxisTranslation,
354
        Length $yAxisTranslation,
355
        Length $zAxisTranslation
356
    ): self {
357 1
        return $this->positionVectorTransformation(
358 1
            $to,
359
            $xAxisTranslation,
360
            $yAxisTranslation,
361
            $zAxisTranslation,
362 1
            new Radian(0),
363 1
            new Radian(0),
364 1
            new Radian(0),
365 1
            new Unity(0)
366
        );
367
    }
368
369
    /**
370
     * Abridged Molodensky
371
     * This transformation is a truncated Taylor series expansion of a transformation between two geographic coordinate
372
     * systems, modelled as a set of geocentric translations.
373
     */
374 2
    public function abridgedMolodensky(
375
        Geographic $to,
376
        Length $xAxisTranslation,
377
        Length $yAxisTranslation,
378
        Length $zAxisTranslation,
379
        Length $differenceInSemiMajorAxis,
380
        Scale $differenceInFlattening
381
    ): self {
382 2
        $latitude = $this->latitude->asRadians()->getValue();
383 2
        $longitude = $this->longitude->asRadians()->getValue();
384 2
        $fromHeight = $this->height ? $this->height->asMetres()->getValue() : 0;
385 2
        $tx = $xAxisTranslation->asMetres()->getValue();
386 2
        $ty = $yAxisTranslation->asMetres()->getValue();
387 2
        $tz = $zAxisTranslation->asMetres()->getValue();
388 2
        $da = $differenceInSemiMajorAxis->asMetres()->getValue();
389 2
        $df = $differenceInFlattening->asUnity()->getValue();
390
391 2
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
392 2
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
393
394 2
        $rho = $a * (1 - $e2) / (1 - $e2 * sin($latitude) ** 2) ** (3 / 2);
395 2
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
396
397 2
        $f = $this->crs->getDatum()->getEllipsoid()->getInverseFlattening();
398
399 2
        $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()));
400 2
        $dLongitude = (-$tx * sin($longitude) + $ty * cos($longitude)) / (($nu * cos($latitude)) * sin((new ArcSecond(1))->asRadians()->getValue()));
401 2
        $dHeight = ($tx * cos($latitude) * cos($longitude)) + ($ty * cos($latitude) * sin($longitude)) + ($tz * sin($latitude)) + (($a * $df + $f * $da) * (sin($latitude) ** 2)) - $da;
402
403 2
        $toLatitude = $latitude + (new ArcSecond($dLatitude))->asRadians()->getValue();
404 2
        $toLongitude = $longitude + (new ArcSecond($dLongitude))->asRadians()->getValue();
405 2
        $toHeight = $fromHeight + $dHeight;
406
407 2
        return static::create(new Radian($toLatitude), new Radian($toLongitude), $to instanceof Geographic3D ? new Metre($toHeight) : null, $to, $this->epoch);
408
    }
409
410
    /**
411
     * Molodensky
412
     * See Abridged Molodensky.
413
     */
414 2
    public function molodensky(
415
        Geographic $to,
416
        Length $xAxisTranslation,
417
        Length $yAxisTranslation,
418
        Length $zAxisTranslation,
419
        Length $differenceInSemiMajorAxis,
420
        Scale $differenceInFlattening
421
    ): self {
422 2
        $latitude = $this->latitude->asRadians()->getValue();
423 2
        $longitude = $this->longitude->asRadians()->getValue();
424 2
        $fromHeight = $this->height ? $this->height->asMetres()->getValue() : 0;
425 2
        $tx = $xAxisTranslation->asMetres()->getValue();
426 2
        $ty = $yAxisTranslation->asMetres()->getValue();
427 2
        $tz = $zAxisTranslation->asMetres()->getValue();
428 2
        $da = $differenceInSemiMajorAxis->asMetres()->getValue();
429 2
        $df = $differenceInFlattening->asUnity()->getValue();
430
431 2
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
432 2
        $b = $this->crs->getDatum()->getEllipsoid()->getSemiMinorAxis()->asMetres()->getValue();
433 2
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
434
435 2
        $rho = $a * (1 - $e2) / (1 - $e2 * sin($latitude) ** 2) ** (3 / 2);
436 2
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
437
438 2
        $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...
439
440 2
        $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()));
441 2
        $dLongitude = (-$tx * sin($longitude) + $ty * cos($longitude)) / ((($nu + $fromHeight) * cos($latitude)) * sin((new ArcSecond(1))->asRadians()->getValue()));
442 2
        $dHeight = ($tx * cos($latitude) * cos($longitude)) + ($ty * cos($latitude) * sin($longitude)) + ($tz * sin($latitude)) - $da * $a / $nu + $df * $b / $a * $nu * sin($latitude) ** 2;
443
444 2
        $toLatitude = $latitude + (new ArcSecond($dLatitude))->asRadians()->getValue();
445 2
        $toLongitude = $longitude + (new ArcSecond($dLongitude))->asRadians()->getValue();
446 2
        $toHeight = $fromHeight + $dHeight;
447
448 2
        return static::create(new Radian($toLatitude), new Radian($toLongitude), $to instanceof Geographic3D ? new Metre($toHeight) : null, $to, $this->epoch);
449
    }
450
451
    /**
452
     * Albers Equal Area.
453
     */
454 2
    public function albersEqualArea(
455
        Projected $to,
456
        Angle $latitudeOfFalseOrigin,
457
        Angle $longitudeOfFalseOrigin,
458
        Angle $latitudeOf1stStandardParallel,
459
        Angle $latitudeOf2ndStandardParallel,
460
        Length $eastingAtFalseOrigin,
461
        Length $northingAtFalseOrigin
462
    ): ProjectedPoint {
463 2
        $latitude = $this->latitude->asRadians()->getValue();
464 2
        $longitude = $this->longitude->asRadians()->getValue();
465 2
        $phiOrigin = $latitudeOfFalseOrigin->asRadians()->getValue();
466 2
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
467 2
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
468 2
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
469 2
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
470 2
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
471
472 2
        $centralMeridianFirstParallel = cos($phi1) / sqrt(1 - ($e2 * sin($phi1) ** 2));
473 2
        $centralMeridianSecondParallel = cos($phi2) / sqrt(1 - ($e2 * sin($phi2) ** 2));
474
475 2
        $alpha = (1 - $e2) * (sin($latitude) / (1 - $e2 * sin($latitude) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))));
476 2
        $alphaOrigin = (1 - $e2) * (sin($phiOrigin) / (1 - $e2 * sin($phiOrigin) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($phiOrigin)) / (1 + $e * sin($phiOrigin))));
477 2
        $alphaFirstParallel = (1 - $e2) * (sin($phi1) / (1 - $e2 * sin($phi1) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))));
478 2
        $alphaSecondParallel = (1 - $e2) * (sin($phi2) / (1 - $e2 * sin($phi2) ** 2) - (1 / 2 / $e) * log((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))));
479
480 2
        $n = ($centralMeridianFirstParallel ** 2 - $centralMeridianSecondParallel ** 2) / ($alphaSecondParallel - $alphaFirstParallel);
481 2
        $C = $centralMeridianFirstParallel ** 2 + $n * $alphaFirstParallel;
482 2
        $theta = $n * ($longitude - $longitudeOfFalseOrigin->asRadians()->getValue());
483 2
        $rho = $a * sqrt($C - $n * $alpha) / $n;
484 2
        $rhoOrigin = ($a * sqrt($C - $n * $alphaOrigin)) / $n;
485
486 2
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + ($rho * sin($theta));
487 2
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rhoOrigin - ($rho * cos($theta));
488
489 2
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
490
    }
491
492
    /**
493
     * American Polyconic.
494
     */
495 1
    public function americanPolyconic(
496
        Projected $to,
497
        Angle $latitudeOfNaturalOrigin,
498
        Angle $longitudeOfNaturalOrigin,
499
        Length $falseEasting,
500
        Length $falseNorthing
501
    ): ProjectedPoint {
502 1
        $latitude = $this->latitude->asRadians()->getValue();
503 1
        $longitude = $this->longitude->asRadians()->getValue();
504 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
505 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
506 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
507 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
508 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
509 1
        $e4 = $e ** 4;
510 1
        $e6 = $e ** 6;
511
512 1
        $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));
513 1
        $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));
514
515 1
        if ($latitude === 0.0) {
0 ignored issues
show
introduced by
The condition $latitude === 0.0 is always false.
Loading history...
516
            $easting = $falseEasting->asMetres()->getValue() + $a * ($longitude - $longitudeOrigin);
517
            $northing = $falseNorthing->asMetres()->getValue() - $MO;
518
        } else {
519 1
            $L = ($longitude - $longitudeOrigin) * sin($latitude);
520 1
            $nu = $a / sqrt(1 - $e2 * sin($latitude) ** 2);
521
522 1
            $easting = $falseEasting->asMetres()->getValue() + $nu * 1 / tan($latitude) * sin($L);
523 1
            $northing = $falseNorthing->asMetres()->getValue() + $M - $MO + $nu * 1 / tan($latitude) * (1 - cos($L));
524
        }
525
526 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
527
    }
528
529
    /**
530
     * Bonne.
531
     */
532 1
    public function bonne(
533
        Projected $to,
534
        Angle $latitudeOfNaturalOrigin,
535
        Angle $longitudeOfNaturalOrigin,
536
        Length $falseEasting,
537
        Length $falseNorthing
538
    ): ProjectedPoint {
539 1
        $latitude = $this->latitude->asRadians()->getValue();
540 1
        $longitude = $this->longitude->asRadians()->getValue();
541 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
542 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
543 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
544 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
545 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
546 1
        $e4 = $e ** 4;
547 1
        $e6 = $e ** 6;
548
549 1
        $m = cos($latitude) / sqrt(1 - $e2 * sin($latitude) ** 2);
550 1
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
551
552 1
        $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));
553 1
        $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));
554
555 1
        $rho = $a * $mO / sin($latitudeOrigin) + $MO - $M;
556 1
        $tau = $a * $m * ($longitude - $longitudeOrigin) / $rho;
557
558 1
        $easting = $falseEasting->asMetres()->getValue() + ($rho * sin($tau));
559 1
        $northing = $falseNorthing->asMetres()->getValue() + (($a * $mO / sin($latitudeOrigin) - $rho * cos($tau)));
560
561 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
562
    }
563
564
    /**
565
     * Bonne South Orientated.
566
     */
567 1
    public function bonneSouthOrientated(
568
        Projected $to,
569
        Angle $latitudeOfNaturalOrigin,
570
        Angle $longitudeOfNaturalOrigin,
571
        Length $falseEasting,
572
        Length $falseNorthing
573
    ): ProjectedPoint {
574 1
        $latitude = $this->latitude->asRadians()->getValue();
575 1
        $longitude = $this->longitude->asRadians()->getValue();
576 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
577 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
578 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
579 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
580 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
581 1
        $e4 = $e ** 4;
582 1
        $e6 = $e ** 6;
583
584 1
        $m = cos($latitude) / sqrt(1 - $e2 * sin($latitude) ** 2);
585 1
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
586
587 1
        $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));
588 1
        $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));
589
590 1
        $rho = $a * $mO / sin($latitudeOrigin) + $MO - $M;
591 1
        $tau = $a * $m * ($longitude - $longitudeOrigin) / $rho;
592
593 1
        $westing = $falseEasting->asMetres()->getValue() - ($rho * sin($tau));
594 1
        $southing = $falseNorthing->asMetres()->getValue() - (($a * $mO / sin($latitudeOrigin) - $rho * cos($tau)));
595
596 1
        return ProjectedPoint::create(new Metre(-$westing), new Metre(-$southing), new Metre($westing), new Metre($southing), $to, $this->epoch);
597
    }
598
599
    /**
600
     * Cassini-Soldner.
601
     */
602 1
    public function cassiniSoldner(
603
        Projected $to,
604
        Angle $latitudeOfNaturalOrigin,
605
        Angle $longitudeOfNaturalOrigin,
606
        Length $falseEasting,
607
        Length $falseNorthing
608
    ): ProjectedPoint {
609 1
        $latitude = $this->latitude->asRadians()->getValue();
610 1
        $longitude = $this->longitude->asRadians()->getValue();
611 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
612 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
613 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
614 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
615 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
616 1
        $e4 = $e ** 4;
617 1
        $e6 = $e ** 6;
618
619 1
        $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));
620 1
        $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));
621
622 1
        $A = ($longitude - $longitudeOrigin) * cos($latitude);
623 1
        $T = tan($latitude) ** 2;
624 1
        $C = $e2 * cos($latitude) ** 2 / (1 - $e2);
625 1
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
626 1
        $X = $M - $MO + $nu * tan($latitude) * ($A ** 2 / 2 + (5 - $T + 6 * $C) * $A ** 4 / 24);
627
628 1
        $easting = $falseEasting->asMetres()->getValue() + $nu * ($A - $T * $A ** 3 / 6 - (8 - $T + 8 * $C) * $T * $A ** 5 / 120);
629 1
        $northing = $falseNorthing->asMetres()->getValue() + $X;
630
631 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
632
    }
633
634
    /**
635
     * Hyperbolic Cassini-Soldner.
636
     */
637 2
    public function hyperbolicCassiniSoldner(
638
        Projected $to,
639
        Angle $latitudeOfNaturalOrigin,
640
        Angle $longitudeOfNaturalOrigin,
641
        Length $falseEasting,
642
        Length $falseNorthing
643
    ): ProjectedPoint {
644 2
        $latitude = $this->latitude->asRadians()->getValue();
645 2
        $longitude = $this->longitude->asRadians()->getValue();
646 2
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
647 2
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
648 2
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
649 2
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
650 2
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
651 2
        $e4 = $e ** 4;
652 2
        $e6 = $e ** 6;
653
654 2
        $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));
655 2
        $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));
656
657 2
        $A = ($longitude - $longitudeOrigin) * cos($latitude);
658 2
        $T = tan($latitude) ** 2;
659 2
        $C = $e2 * cos($latitude) ** 2 / (1 - $e2);
660 2
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
661 2
        $rho = $a * (1 - $e2) / (1 - $e2 * sin($latitude) ** 2) ** (3 / 2);
662 2
        $X = $M - $MO + $nu * tan($latitude) * ($A ** 2 / 2 + (5 - $T + 6 * $C) * $A ** 4 / 24);
663
664 2
        $easting = $falseEasting->asMetres()->getValue() + $nu * ($A - $T * $A ** 3 / 6 - (8 - $T + 8 * $C) * $T * $A ** 5 / 120);
665 2
        $northing = $falseNorthing->asMetres()->getValue() + $X - ($X ** 3 / (6 * $rho * $nu));
666
667 2
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
668
    }
669
670
    /**
671
     * Colombia Urban.
672
     */
673 1
    public function columbiaUrban(
674
        Projected $to,
675
        Angle $latitudeOfNaturalOrigin,
676
        Angle $longitudeOfNaturalOrigin,
677
        Length $falseEasting,
678
        Length $falseNorthing,
679
        Length $projectionPlaneOriginHeight
680
    ): ProjectedPoint {
681 1
        $latitude = $this->latitude->asRadians()->getValue();
682 1
        $longitude = $this->longitude->asRadians()->getValue();
683 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
684 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
685 1
        $heightOrigin = $projectionPlaneOriginHeight->asMetres()->getValue();
686 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
687 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
688
689 1
        $rho = $a * (1 - $e2) / (1 - $e2 * sin($latitude) ** 2) ** (3 / 2);
0 ignored issues
show
Unused Code introduced by
The assignment to $rho is dead and can be removed.
Loading history...
690 1
        $rhoOrigin = $a * (1 - $e2) / (1 - $e2 * sin($latitudeOrigin) ** 2) ** (3 / 2);
691 1
        $rhoMid = $a * (1 - $e2) / (1 - $e2 * sin(($latitude + $latitudeOrigin) / 2) ** 2) ** (3 / 2);
692
693 1
        $nu = $a / sqrt(1 - $e2 * (sin($latitude) ** 2));
694 1
        $nuOrigin = $a / sqrt(1 - $e2 * (sin($latitudeOrigin) ** 2));
695
696 1
        $A = 1 + $heightOrigin / $nuOrigin;
697 1
        $B = tan($latitudeOrigin) / (2 * $rhoOrigin * $nuOrigin);
698 1
        $G = 1 + $heightOrigin / $rhoMid;
699
700 1
        $easting = $falseEasting->asMetres()->getValue() + $A * $nu * cos($latitude) * ($longitude - $longitudeOrigin);
701 1
        $northing = $falseNorthing->asMetres()->getValue() + $G * $rhoOrigin * (($latitude - $latitudeOrigin) + ($B * ($longitude - $longitudeOrigin) ** 2 * $nu ** 2 * cos($latitude) ** 2));
702
703 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
704
    }
705
706
    /**
707
     * Equal Earth.
708
     */
709 1
    public function equalEarth(
710
        Projected $to,
711
        Angle $longitudeOfNaturalOrigin,
712
        Length $falseEasting,
713
        Length $falseNorthing
714
    ): ProjectedPoint {
715 1
        $latitude = $this->latitude->asRadians()->getValue();
716 1
        $longitude = $this->longitude->asRadians()->getValue();
717 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
718 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
719 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
720 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
721
722 1
        $q = (1 - $e2) * ((sin($latitude) / (1 - $e2 * sin($latitude) ** 2)) - (1 / (2 * $e) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude)))));
723 1
        $qP = (1 - $e2) * ((1 / (1 - $e2)) - (1 / (2 * $e) * log((1 - $e) / (1 + $e))));
724 1
        $beta = self::asin($q / $qP);
725 1
        $theta = self::asin(sin($beta) * sqrt(3) / 2);
726 1
        $Rq = $a * sqrt($qP / 2);
727
728 1
        $easting = $falseEasting->asMetres()->getValue() + ($Rq * 2 * ($longitude - $longitudeOrigin) * cos($theta)) / (sqrt(3) * (1.340264 - 0.243318 * $theta ** 2 + $theta ** 6 * (0.006251 + 0.034164 * $theta ** 2)));
729 1
        $northing = $falseNorthing->asMetres()->getValue() + $Rq * $theta * (1.340264 - 0.081106 * $theta ** 2 + $theta ** 6 * (0.000893 + 0.003796 * $theta ** 2));
730
731 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
732
    }
733
734
    /**
735
     * Equidistant Cylindrical
736
     * See method code 1029 for spherical development. See also Pseudo Plate Carree, method code 9825.
737
     */
738 1
    public function equidistantCylindrical(
739
        Projected $to,
740
        Angle $latitudeOf1stStandardParallel,
741
        Angle $longitudeOfNaturalOrigin,
742
        Length $falseEasting,
743
        Length $falseNorthing
744
    ): ProjectedPoint {
745 1
        $latitude = $this->latitude->asRadians()->getValue();
746 1
        $longitude = $this->longitude->asRadians()->getValue();
747 1
        $latitudeFirstParallel = $latitudeOf1stStandardParallel->asRadians()->getValue();
748 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
749 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
750 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
751 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
752 1
        $e4 = $e ** 4;
753 1
        $e6 = $e ** 6;
754 1
        $e8 = $e ** 8;
755 1
        $e10 = $e ** 10;
756 1
        $e12 = $e ** 12;
757 1
        $e14 = $e ** 14;
758
759 1
        $nu1 = $a / sqrt(1 - $e2 * sin($latitudeFirstParallel) ** 2);
760
761
        $M = $a * (
762 1
            (1 - 1 / 4 * $e2 - 3 / 64 * $e4 - 5 / 256 * $e6 - 175 / 16384 * $e8 - 441 / 65536 * $e10 - 4851 / 1048576 * $e12 - 14157 / 4194304 * $e14) * $latitude +
763 1
            (-3 / 8 * $e2 - 3 / 32 * $e4 - 45 / 1024 * $e6 - 105 / 4096 * $e8 - 2205 / 131072 * $e10 - 6237 / 524288 * $e12 - 297297 / 33554432 * $e14) * sin(2 * $latitude) +
764 1
            (15 / 256 * $e4 + 45 / 1024 * $e ** 6 + 525 / 16384 * $e ** 8 + 1575 / 65536 * $e10 + 155925 / 8388608 * $e12 + 495495 / 33554432 * $e14) * sin(4 * $latitude) +
765 1
            (-35 / 3072 * $e6 - 175 / 12288 * $e8 - 3675 / 262144 * $e10 - 13475 / 1048576 * $e12 - 385385 / 33554432 * $e14) * sin(6 * $latitude) +
766 1
            (315 / 131072 * $e8 + 2205 / 524288 * $e10 + 43659 / 8388608 * $e12 + 189189 / 33554432 * $e14) * sin(8 * $latitude) +
767 1
            (-693 / 1310720 * $e10 - 6537 / 5242880 * $e12 - 297297 / 167772160 * $e14) * sin(10 * $latitude) +
768 1
            (1001 / 8388608 * $e12 + 11011 / 33554432 * $e14) * sin(12 * $latitude) +
769 1
            (-6435 / 234881024 * $e ** 14) * sin(14 * $latitude)
770
        );
771
772 1
        $easting = $falseEasting->asMetres()->getValue() + $nu1 * cos($latitudeFirstParallel) * ($longitude - $longitudeOrigin);
773 1
        $northing = $falseNorthing->asMetres()->getValue() + $M;
774
775 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
776
    }
777
778
    /**
779
     * Guam Projection
780
     * Simplified form of Oblique Azimuthal Equidistant projection method.
781
     */
782 1
    public function guamProjection(
783
        Projected $to,
784
        Angle $latitudeOfNaturalOrigin,
785
        Angle $longitudeOfNaturalOrigin,
786
        Length $falseEasting,
787
        Length $falseNorthing
788
    ): ProjectedPoint {
789 1
        $latitude = $this->latitude->asRadians()->getValue();
790 1
        $longitude = $this->longitude->asRadians()->getValue();
791 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
792 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
793 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
794 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
795 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
796 1
        $e4 = $e ** 4;
797 1
        $e6 = $e ** 6;
798
799 1
        $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));
800 1
        $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));
801 1
        $x = ($a * ($longitude - $longitudeOrigin) * cos($latitude)) / sqrt(1 - $e2 * sin($latitude) ** 2);
802
803 1
        $easting = $falseEasting->asMetres()->getValue() + $x;
804 1
        $northing = $falseNorthing->asMetres()->getValue() + $M - $MO + ($x ** 2 * tan($latitude) * sqrt(1 - $e2 * sin($latitude) ** 2) / (2 * $a));
805
806 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
807
    }
808
809
    /**
810
     * Krovak.
811
     */
812 4
    public function krovak(
813
        Projected $to,
814
        Angle $latitudeOfProjectionCentre,
815
        Angle $longitudeOfOrigin,
816
        Angle $coLatitudeOfConeAxis,
817
        Angle $latitudeOfPseudoStandardParallel,
818
        Scale $scaleFactorOnPseudoStandardParallel,
819
        Length $falseEasting,
820
        Length $falseNorthing
821
    ): ProjectedPoint {
822 4
        $longitudeOffset = $to->getDatum()->getPrimeMeridian()->getGreenwichLongitude()->asRadians()->getValue() - $this->getCRS()->getDatum()->getPrimeMeridian()->getGreenwichLongitude()->asRadians()->getValue();
823 4
        $latitude = $this->latitude->asRadians()->getValue();
824 4
        $longitude = $this->longitude->asRadians()->getValue() - $longitudeOffset;
825 4
        $latitudeC = $latitudeOfProjectionCentre->asRadians()->getValue();
826 4
        $longitudeO = $longitudeOfOrigin->asRadians()->getValue();
827 4
        $alphaC = $coLatitudeOfConeAxis->asRadians()->getValue();
828 4
        $latitudeP = $latitudeOfPseudoStandardParallel->asRadians()->getValue();
829 4
        $kP = $scaleFactorOnPseudoStandardParallel->asUnity()->getValue();
830 4
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
831 4
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
832 4
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
833
834 4
        $A = $a * sqrt(1 - $e2) / (1 - $e2 * sin($latitudeC) ** 2);
835 4
        $B = sqrt(1 + $e2 * cos($latitudeC) ** 4 / (1 - $e2));
836 4
        $upsilonO = self::asin(sin($latitudeC) / $B);
837 4
        $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);
838 4
        $n = sin($latitudeP);
839 4
        $rO = $kP * $A / tan($latitudeP);
840
841 4
        $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);
842 4
        $V = $B * ($longitudeO - $longitude);
843 4
        $T = self::asin(cos($alphaC) * sin($U) + sin($alphaC) * cos($U) * cos($V));
844 4
        $D = atan2(cos($U) * sin($V) / cos($T), ((cos($alphaC) * sin($T) - sin($U)) / (sin($alphaC) * cos($T))));
845 4
        $theta = $n * $D;
846 4
        $r = $rO * tan(M_PI / 4 + $latitudeP / 2) ** $n / tan($T / 2 + M_PI / 4) ** $n;
847 4
        $X = $r * cos($theta);
848 4
        $Y = $r * sin($theta);
849
850 4
        $westing = $Y + $falseEasting->asMetres()->getValue();
851 4
        $southing = $X + $falseNorthing->asMetres()->getValue();
852
853 4
        return ProjectedPoint::create(new Metre(-$westing), new Metre(-$southing), new Metre($westing), new Metre($southing), $to, $this->epoch);
854
    }
855
856
    /**
857
     * Krovak Modified
858
     * Incorporates a polynomial transformation which is defined to be exact and for practical purposes is considered
859
     * to be a map projection.
860
     */
861 2
    public function krovakModified(
862
        Projected $to,
863
        Angle $latitudeOfProjectionCentre,
864
        Angle $longitudeOfOrigin,
865
        Angle $coLatitudeOfConeAxis,
866
        Angle $latitudeOfPseudoStandardParallel,
867
        Scale $scaleFactorOnPseudoStandardParallel,
868
        Length $falseEasting,
869
        Length $falseNorthing,
870
        Length $ordinate1OfEvaluationPoint,
871
        Length $ordinate2OfEvaluationPoint,
872
        Coefficient $C1,
873
        Coefficient $C2,
874
        Coefficient $C3,
875
        Coefficient $C4,
876
        Coefficient $C5,
877
        Coefficient $C6,
878
        Coefficient $C7,
879
        Coefficient $C8,
880
        Coefficient $C9,
881
        Coefficient $C10
882
    ): ProjectedPoint {
883 2
        $asKrovak = $this->krovak($to, $latitudeOfProjectionCentre, $longitudeOfOrigin, $coLatitudeOfConeAxis, $latitudeOfPseudoStandardParallel, $scaleFactorOnPseudoStandardParallel, new Metre(0), new Metre(0));
884
885 2
        $westing = $asKrovak->getWesting()->asMetres()->getValue();
886 2
        $southing = $asKrovak->getSouthing()->asMetres()->getValue();
887 2
        $c1 = $C1->asUnity()->getValue();
888 2
        $c2 = $C2->asUnity()->getValue();
889 2
        $c3 = $C3->asUnity()->getValue();
890 2
        $c4 = $C4->asUnity()->getValue();
891 2
        $c5 = $C5->asUnity()->getValue();
892 2
        $c6 = $C6->asUnity()->getValue();
893 2
        $c7 = $C7->asUnity()->getValue();
894 2
        $c8 = $C8->asUnity()->getValue();
895 2
        $c9 = $C9->asUnity()->getValue();
896 2
        $c10 = $C10->asUnity()->getValue();
897
898 2
        $Xr = $southing - $ordinate1OfEvaluationPoint->asMetres()->getValue();
899 2
        $Yr = $westing - $ordinate2OfEvaluationPoint->asMetres()->getValue();
900
901 2
        $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);
902 2
        $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);
903
904 2
        $westing += $falseEasting->asMetres()->getValue() - $dY;
905 2
        $southing += $falseNorthing->asMetres()->getValue() - $dX;
906
907 2
        return ProjectedPoint::create(new Metre(-$westing), new Metre(-$southing), new Metre($westing), new Metre($southing), $to, $this->epoch);
908
    }
909
910
    /**
911
     * Lambert Azimuthal Equal Area
912
     * This is the ellipsoidal form of the projection.
913
     */
914 1
    public function lambertAzimuthalEqualArea(
915
        Projected $to,
916
        Angle $latitudeOfNaturalOrigin,
917
        Angle $longitudeOfNaturalOrigin,
918
        Length $falseEasting,
919
        Length $falseNorthing
920
    ): ProjectedPoint {
921 1
        $latitude = $this->latitude->asRadians()->getValue();
922 1
        $longitude = $this->longitude->asRadians()->getValue();
923 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
924 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
925 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
926 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
927 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
928
929 1
        $q = (1 - $e2) * ((sin($latitude) / (1 - $e2 * sin($latitude) ** 2)) - ((1 / (2 * $e)) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude)))));
930 1
        $qO = (1 - $e2) * ((sin($latitudeOrigin) / (1 - $e2 * sin($latitudeOrigin) ** 2)) - ((1 / (2 * $e)) * log((1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin)))));
931 1
        $qP = (1 - $e2) * ((1 / (1 - $e2)) - ((1 / (2 * $e)) * log((1 - $e) / (1 + $e))));
932 1
        $beta = self::asin($q / $qP);
933 1
        $betaO = self::asin($qO / $qP);
934 1
        $Rq = $a * sqrt($qP / 2);
935 1
        $B = $Rq * sqrt(2 / (1 + sin($betaO) * sin($beta) + (cos($betaO) * cos($beta) * cos($longitude - $longitudeOrigin))));
936 1
        $D = $a * (cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2)) / ($Rq * cos($betaO));
937
938 1
        $easting = $falseEasting->asMetres()->getValue() + (($B * $D) * (cos($beta) * sin($longitude - $longitudeOrigin)));
939 1
        $northing = $falseNorthing->asMetres()->getValue() + ($B / $D) * ((cos($betaO) * sin($beta)) - (sin($betaO) * cos($beta) * cos($longitude - $longitudeOrigin)));
940
941 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
942
    }
943
944
    /**
945
     * Lambert Azimuthal Equal Area (Spherical)
946
     * This is the spherical form of the projection.  See coordinate operation method Lambert Azimuthal Equal Area
947
     * (code 9820) for ellipsoidal form.  Differences of several tens of metres result from comparison of the two
948
     * methods.
949
     */
950 1
    public function lambertAzimuthalEqualAreaSpherical(
951
        Projected $to,
952
        Angle $latitudeOfNaturalOrigin,
953
        Angle $longitudeOfNaturalOrigin,
954
        Length $falseEasting,
955
        Length $falseNorthing
956
    ): ProjectedPoint {
957 1
        $latitude = $this->latitude->asRadians()->getValue();
958 1
        $longitude = $this->longitude->asRadians()->getValue();
959 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
960 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
961 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
962
963 1
        $k = sqrt(2 / (1 + sin($latitudeOrigin) * sin($latitude) + cos($latitudeOrigin) * cos($latitude) * cos($longitude - $longitudeOrigin)));
964
965 1
        $easting = $falseEasting->asMetres()->getValue() + ($a * $k * cos($latitude) * sin($longitude - $longitudeOrigin));
966 1
        $northing = $falseNorthing->asMetres()->getValue() + ($a * $k * (cos($latitudeOrigin) * sin($latitude) - sin($latitudeOrigin) * cos($latitude) * cos($longitude - $longitudeOrigin)));
967
968 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
969
    }
970
971
    /**
972
     * Lambert Conic Conformal (1SP).
973
     */
974 1
    public function lambertConicConformal1SP(
975
        Projected $to,
976
        Angle $latitudeOfNaturalOrigin,
977
        Angle $longitudeOfNaturalOrigin,
978
        Scale $scaleFactorAtNaturalOrigin,
979
        Length $falseEasting,
980
        Length $falseNorthing
981
    ): ProjectedPoint {
982 1
        $latitude = $this->latitude->asRadians()->getValue();
983 1
        $longitude = $this->longitude->asRadians()->getValue();
984 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
985 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
986 1
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
987 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
988 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
989 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
990
991 1
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
992 1
        $tO = tan(M_PI / 4 - $latitudeOrigin / 2) / ((1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin))) ** ($e / 2);
993 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
994 1
        $n = sin($latitudeOrigin);
995 1
        $F = $mO / ($n * $tO ** $n);
996 1
        $rO = $a * $F * $tO ** $n * $kO;
997 1
        $r = $a * $F * $t ** $n * $kO;
998 1
        $theta = $n * ($longitude - $longitudeOrigin);
999
1000 1
        $easting = $falseEasting->asMetres()->getValue() + $r * sin($theta);
1001 1
        $northing = $falseNorthing->asMetres()->getValue() + $rO - $r * cos($theta);
1002
1003 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1004
    }
1005
1006
    /**
1007
     * Lambert Conic Conformal (2SP Belgium)
1008
     * In 2000 this modification was replaced through use of the regular Lambert Conic Conformal (2SP) method [9802]
1009
     * with appropriately modified parameter values.
1010
     */
1011 1
    public function lambertConicConformal2SPBelgium(
1012
        Projected $to,
1013
        Angle $latitudeOfFalseOrigin,
1014
        Angle $longitudeOfFalseOrigin,
1015
        Angle $latitudeOf1stStandardParallel,
1016
        Angle $latitudeOf2ndStandardParallel,
1017
        Length $eastingAtFalseOrigin,
1018
        Length $northingAtFalseOrigin
1019
    ): ProjectedPoint {
1020 1
        $latitude = $this->latitude->asRadians()->getValue();
1021 1
        $longitude = $this->longitude->asRadians()->getValue();
1022 1
        $lambdaF = $longitudeOfFalseOrigin->asRadians()->getValue();
1023 1
        $phiF = $latitudeOfFalseOrigin->asRadians()->getValue();
1024 1
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
1025 1
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
1026 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1027 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1028 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1029
1030 1
        $m1 = cos($phi1) / sqrt(1 - $e2 * sin($phi1) ** 2);
1031 1
        $m2 = cos($phi2) / sqrt(1 - $e2 * sin($phi2) ** 2);
1032 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1033 1
        $t1 = tan(M_PI / 4 - $phi1 / 2) / ((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))) ** ($e / 2);
1034 1
        $t2 = tan(M_PI / 4 - $phi2 / 2) / ((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))) ** ($e / 2);
1035 1
        $tF = tan(M_PI / 4 - $phiF / 2) / ((1 - $e * sin($phiF)) / (1 + $e * sin($phiF))) ** ($e / 2);
1036 1
        $n = (log($m1) - log($m2)) / (log($t1) - log($t2));
1037 1
        $F = $m1 / ($n * $t1 ** $n);
1038 1
        $r = $a * $F * $t ** $n;
1039 1
        $rF = $a * $F * $tF ** $n;
1040 1
        if (is_nan($rF)) {
1041 1
            $rF = 0;
1042
        }
1043 1
        $theta = ($n * ($longitude - $lambdaF)) - (new ArcSecond(29.2985))->asRadians()->getValue();
1044
1045 1
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $r * sin($theta);
1046 1
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rF - $r * cos($theta);
1047
1048 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1049
    }
1050
1051
    /**
1052
     * Lambert Conic Conformal (2SP Michigan).
1053
     */
1054 1
    public function lambertConicConformal2SPMichigan(
1055
        Projected $to,
1056
        Angle $latitudeOfFalseOrigin,
1057
        Angle $longitudeOfFalseOrigin,
1058
        Angle $latitudeOf1stStandardParallel,
1059
        Angle $latitudeOf2ndStandardParallel,
1060
        Length $eastingAtFalseOrigin,
1061
        Length $northingAtFalseOrigin,
1062
        Scale $ellipsoidScalingFactor
1063
    ): ProjectedPoint {
1064 1
        $latitude = $this->latitude->asRadians()->getValue();
1065 1
        $longitude = $this->longitude->asRadians()->getValue();
1066 1
        $lambdaF = $longitudeOfFalseOrigin->asRadians()->getValue();
1067 1
        $phiF = $latitudeOfFalseOrigin->asRadians()->getValue();
1068 1
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
1069 1
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
1070 1
        $K = $ellipsoidScalingFactor->asUnity()->getValue();
1071 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1072 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1073 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1074
1075 1
        $m1 = cos($phi1) / sqrt(1 - $e2 * sin($phi1) ** 2);
1076 1
        $m2 = cos($phi2) / sqrt(1 - $e2 * sin($phi2) ** 2);
1077 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1078 1
        $t1 = tan(M_PI / 4 - $phi1 / 2) / ((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))) ** ($e / 2);
1079 1
        $t2 = tan(M_PI / 4 - $phi2 / 2) / ((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))) ** ($e / 2);
1080 1
        $tF = tan(M_PI / 4 - $phiF / 2) / ((1 - $e * sin($phiF)) / (1 + $e * sin($phiF))) ** ($e / 2);
1081 1
        $n = (log($m1) - log($m2)) / (log($t1) - log($t2));
1082 1
        $F = $m1 / ($n * $t1 ** $n);
1083 1
        $r = $a * $K * $F * $t ** $n;
1084 1
        $rF = $a * $K * $F * $tF ** $n;
1085 1
        $theta = $n * ($longitude - $lambdaF);
1086
1087 1
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $r * sin($theta);
1088 1
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rF - $r * cos($theta);
1089
1090 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1091
    }
1092
1093
    /**
1094
     * Lambert Conic Conformal (2SP).
1095
     */
1096 1
    public function lambertConicConformal2SP(
1097
        Projected $to,
1098
        Angle $latitudeOfFalseOrigin,
1099
        Angle $longitudeOfFalseOrigin,
1100
        Angle $latitudeOf1stStandardParallel,
1101
        Angle $latitudeOf2ndStandardParallel,
1102
        Length $eastingAtFalseOrigin,
1103
        Length $northingAtFalseOrigin
1104
    ): ProjectedPoint {
1105 1
        $latitude = $this->latitude->asRadians()->getValue();
1106 1
        $longitude = $this->longitude->asRadians()->getValue();
1107 1
        $lambdaF = $longitudeOfFalseOrigin->asRadians()->getValue();
1108 1
        $phiF = $latitudeOfFalseOrigin->asRadians()->getValue();
1109 1
        $phi1 = $latitudeOf1stStandardParallel->asRadians()->getValue();
1110 1
        $phi2 = $latitudeOf2ndStandardParallel->asRadians()->getValue();
1111 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1112 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1113 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1114
1115 1
        $m1 = cos($phi1) / sqrt(1 - $e2 * sin($phi1) ** 2);
1116 1
        $m2 = cos($phi2) / sqrt(1 - $e2 * sin($phi2) ** 2);
1117 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1118 1
        $t1 = tan(M_PI / 4 - $phi1 / 2) / ((1 - $e * sin($phi1)) / (1 + $e * sin($phi1))) ** ($e / 2);
1119 1
        $t2 = tan(M_PI / 4 - $phi2 / 2) / ((1 - $e * sin($phi2)) / (1 + $e * sin($phi2))) ** ($e / 2);
1120 1
        $tF = tan(M_PI / 4 - $phiF / 2) / ((1 - $e * sin($phiF)) / (1 + $e * sin($phiF))) ** ($e / 2);
1121 1
        $n = (log($m1) - log($m2)) / (log($t1) - log($t2));
1122 1
        $F = $m1 / ($n * $t1 ** $n);
1123 1
        $r = $a * $F * $t ** $n;
1124 1
        $rF = $a * $F * $tF ** $n;
1125 1
        $theta = $n * ($longitude - $lambdaF);
1126
1127 1
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $r * sin($theta);
1128 1
        $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rF - $r * cos($theta);
1129
1130 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1131
    }
1132
1133
    /**
1134
     * Lambert Conic Conformal (West Orientated).
1135
     */
1136
    public function lambertConicConformalWestOrientated(
1137
        Projected $to,
1138
        Angle $latitudeOfNaturalOrigin,
1139
        Angle $longitudeOfNaturalOrigin,
1140
        Scale $scaleFactorAtNaturalOrigin,
1141
        Length $falseEasting,
1142
        Length $falseNorthing
1143
    ): ProjectedPoint {
1144
        $latitude = $this->latitude->asRadians()->getValue();
1145
        $longitude = $this->longitude->asRadians()->getValue();
1146
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1147
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1148
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1149
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1150
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1151
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1152
1153
        $mO = cos($latitudeOrigin) / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
1154
        $tO = tan(M_PI / 4 - $latitudeOrigin / 2) / ((1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin))) ** ($e / 2);
1155
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1156
        $n = sin($latitudeOrigin);
1157
        $F = $mO / ($n * $tO ** $n);
1158
        $rO = $a * $F * $tO ** $n ** $kO;
1159
        $r = $a * $F * $t ** $n ** $kO;
1160
        $theta = $n * ($longitude - $longitudeOrigin);
1161
1162
        $westing = $falseEasting->asMetres()->getValue() - $r * sin($theta);
1163
        $northing = $falseNorthing->asMetres()->getValue() + $rO - $r * cos($theta);
1164
1165
        return ProjectedPoint::create(new Metre(-$westing), new Metre($northing), new Metre($westing), new Metre(-$northing), $to, $this->epoch);
1166
    }
1167
1168
    /**
1169
     * Lambert Conic Near-Conformal
1170
     * The Lambert Near-Conformal projection is derived from the Lambert Conformal Conic projection by truncating the
1171
     * series expansion of the projection formulae.
1172
     */
1173 1
    public function lambertConicNearConformal(
1174
        Projected $to,
1175
        Angle $latitudeOfNaturalOrigin,
1176
        Angle $longitudeOfNaturalOrigin,
1177
        Scale $scaleFactorAtNaturalOrigin,
1178
        Length $falseEasting,
1179
        Length $falseNorthing
1180
    ): ProjectedPoint {
1181 1
        $latitude = $this->latitude->asRadians()->getValue();
1182 1
        $longitude = $this->longitude->asRadians()->getValue();
1183 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1184 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1185 1
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1186 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1187 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1188 1
        $f = $this->crs->getDatum()->getEllipsoid()->getInverseFlattening();
1189
1190 1
        $n = $f / (2 - $f);
1191 1
        $rhoO = $a * (1 - $e2) / (1 - $e2 * sin($latitudeOrigin) ** 2) ** (3 / 2);
1192 1
        $nuO = $a / sqrt(1 - $e2 * (sin($latitudeOrigin) ** 2));
1193 1
        $A = 1 / (6 * $rhoO * $nuO);
1194 1
        $APrime = $a * (1 - $n + 5 * ($n ** 2 - $n ** 3) / 4 + 81 * ($n ** 4 - $n ** 5) / 64);
1195 1
        $BPrime = 3 * $a * ($n - $n ** 2 + 7 * ($n ** 3 - $n ** 4) / 8 + 55 * $n ** 5 / 64) / 2;
1196 1
        $CPrime = 15 * $a * ($n ** 2 - $n ** 3 + 3 * ($n ** 4 - $n ** 5) / 4) / 16;
1197 1
        $DPrime = 35 * $a * ($n ** 3 - $n ** 4 + 11 * $n ** 5 / 16) / 48;
1198 1
        $EPrime = 315 * $a * ($n ** 4 - $n ** 5) / 512;
1199 1
        $rO = $kO * $nuO / tan($latitudeOrigin);
1200 1
        $sO = $APrime * $latitudeOrigin - $BPrime * sin(2 * $latitudeOrigin) + $CPrime * sin(4 * $latitudeOrigin) - $DPrime * sin(6 * $latitudeOrigin) + $EPrime * sin(8 * $latitudeOrigin);
1201 1
        $s = $APrime * $latitude - $BPrime * sin(2 * $latitude) + $CPrime * sin(4 * $latitude) - $DPrime * sin(6 * $latitude) + $EPrime * sin(8 * $latitude);
1202 1
        $m = $s - $sO;
1203 1
        $M = $kO * ($m + $A * $m ** 3);
1204 1
        $r = $rO - $M;
1205 1
        $theta = ($longitude - $longitudeOrigin) * sin($latitudeOrigin);
1206
1207 1
        $easting = $falseEasting->asMetres()->getValue() + $r * sin($theta);
1208 1
        $northing = $falseNorthing->asMetres()->getValue() + $M + $r * sin($theta) * tan($theta / 2);
1209
1210 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1211
    }
1212
1213
    /**
1214
     * Lambert Cylindrical Equal Area
1215
     * This is the ellipsoidal form of the projection.
1216
     */
1217 1
    public function lambertCylindricalEqualArea(
1218
        Projected $to,
1219
        Angle $latitudeOf1stStandardParallel,
1220
        Angle $longitudeOfNaturalOrigin,
1221
        Length $falseEasting,
1222
        Length $falseNorthing
1223
    ): ProjectedPoint {
1224 1
        $latitude = $this->latitude->asRadians()->getValue();
1225 1
        $longitude = $this->longitude->asRadians()->getValue();
1226 1
        $latitudeFirstParallel = $latitudeOf1stStandardParallel->asRadians()->getValue();
1227 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1228 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1229 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1230 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1231
1232 1
        $k = cos($latitudeFirstParallel) / sqrt(1 - $e2 * sin($latitudeFirstParallel) ** 2);
1233 1
        $q = (1 - $e2) * ((sin($latitude) / (1 - $e2 * sin($latitude) ** 2)) - (1 / (2 * $e)) * log((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))));
1234
1235 1
        $x = $a * $k * ($longitude - $longitudeOrigin);
1236 1
        $y = $a * $q / (2 * $k);
1237
1238 1
        $easting = $falseEasting->asMetres()->getValue() + $x;
1239 1
        $northing = $falseNorthing->asMetres()->getValue() + $y;
1240
1241 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1242
    }
1243
1244
    /**
1245
     * Modified Azimuthal Equidistant
1246
     * Modified form of Oblique Azimuthal Equidistant projection method developed for Polynesian islands. For the
1247
     * distances over which these projections are used (under 800km) this modification introduces no significant error.
1248
     */
1249 1
    public function modifiedAzimuthalEquidistant(
1250
        Projected $to,
1251
        Angle $latitudeOfNaturalOrigin,
1252
        Angle $longitudeOfNaturalOrigin,
1253
        Length $falseEasting,
1254
        Length $falseNorthing
1255
    ): ProjectedPoint {
1256 1
        $latitude = $this->latitude->asRadians()->getValue();
1257 1
        $longitude = $this->longitude->asRadians()->getValue();
1258 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1259 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1260 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1261 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1262 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1263
1264 1
        $nuO = $a / sqrt(1 - $e2 * sin($latitudeOrigin) ** 2);
1265 1
        $nu = $a / sqrt(1 - $e2 * sin($latitude) ** 2);
1266 1
        $psi = atan((1 - $e2) * tan($latitude) + ($e2 * $nuO * sin($latitudeOrigin)) / ($nu * cos($latitude)));
1267 1
        $alpha = atan2(sin($longitude - $longitudeOrigin), (cos($latitudeOrigin) * tan($psi) - sin($latitudeOrigin) * cos($longitude - $longitudeOrigin)));
1268 1
        $G = $e * sin($latitudeOrigin) / sqrt(1 - $e2);
1269 1
        $H = $e * cos($latitudeOrigin) * cos($alpha) / sqrt(1 - $e2);
1270
1271 1
        if (sin($alpha) === 0.0) {
1272
            $s = self::asin(cos($latitudeOrigin) * sin($psi) - sin($latitudeOrigin) * cos($alpha)) * cos($alpha) / abs(cos($alpha));
1273
        } else {
1274 1
            $s = self::asin(sin($longitude - $longitudeOrigin) * cos($psi) / sin($alpha));
1275
        }
1276
1277 1
        $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));
1278
1279 1
        $easting = $falseEasting->asMetres()->getValue() + $c * sin($alpha);
1280 1
        $northing = $falseNorthing->asMetres()->getValue() + $c * cos($alpha);
1281
1282 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1283
    }
1284
1285
    /**
1286
     * Oblique Stereographic
1287
     * This is not the same as the projection method of the same name in USGS Professional Paper no. 1395, "Map
1288
     * Projections - A Working Manual" by John P. Snyder.
1289
     */
1290 1
    public function obliqueStereographic(
1291
        Projected $to,
1292
        Angle $latitudeOfNaturalOrigin,
1293
        Angle $longitudeOfNaturalOrigin,
1294
        Scale $scaleFactorAtNaturalOrigin,
1295
        Length $falseEasting,
1296
        Length $falseNorthing
1297
    ): ProjectedPoint {
1298 1
        $latitude = $this->latitude->asRadians()->getValue();
1299 1
        $longitude = $this->longitude->asRadians()->getValue();
1300 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1301 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1302 1
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1303 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1304 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1305 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1306
1307 1
        $rhoOrigin = $a * (1 - $e2) / (1 - $e2 * sin($latitudeOrigin) ** 2) ** (3 / 2);
1308 1
        $nuOrigin = $a / sqrt(1 - $e2 * (sin($latitudeOrigin) ** 2));
1309 1
        $R = sqrt($rhoOrigin * $nuOrigin);
1310
1311 1
        $n = sqrt(1 + ($e2 * cos($latitudeOrigin) ** 4 / (1 - $e2)));
1312 1
        $S1 = (1 + sin($latitudeOrigin)) / (1 - sin($latitudeOrigin));
1313 1
        $S2 = (1 - $e * sin($latitudeOrigin)) / (1 + $e * sin($latitudeOrigin));
1314 1
        $w1 = ($S1 * ($S2 ** $e)) ** $n;
1315 1
        $c = (($n + sin($latitudeOrigin)) * (1 - ($w1 - 1) / ($w1 + 1))) / (($n - sin($latitudeOrigin)) * (1 + ($w1 - 1) / ($w1 + 1)));
1316 1
        $w2 = $c * $w1;
1317 1
        $chiOrigin = self::asin(($w2 - 1) / ($w2 + 1));
1318
1319 1
        $lambda = $n * ($longitude - $longitudeOrigin) + $longitudeOrigin;
1320
1321 1
        $Sa = (1 + sin($latitude)) / (1 - sin($latitude));
1322 1
        $Sb = (1 - $e * sin($latitude)) / (1 + $e * sin($latitude));
1323 1
        $w = $c * ($Sa * ($Sb ** $e)) ** $n;
1324 1
        $chi = self::asin(($w - 1) / ($w + 1));
1325
1326 1
        $B = (1 + sin($chi) * sin($chiOrigin) + cos($chi) * cos($chiOrigin) * cos($lambda - $longitudeOrigin));
1327
1328 1
        $easting = $falseEasting->asMetres()->getValue() + 2 * $R * $kO * cos($chi) * sin($lambda - $longitudeOrigin) / $B;
1329 1
        $northing = $falseNorthing->asMetres()->getValue() + 2 * $R * $kO * (sin($chi) * cos($chiOrigin) - cos($chi) * sin($chiOrigin) * cos($lambda - $longitudeOrigin)) / $B;
1330
1331 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1332
    }
1333
1334
    /**
1335
     * Polar Stereographic (variant A)
1336
     * Latitude of natural origin must be either 90 degrees or -90 degrees (or equivalent in alternative angle unit).
1337
     */
1338 1
    public function polarStereographicVariantA(
1339
        Projected $to,
1340
        Angle $latitudeOfNaturalOrigin,
1341
        Angle $longitudeOfNaturalOrigin,
1342
        Scale $scaleFactorAtNaturalOrigin,
1343
        Length $falseEasting,
1344
        Length $falseNorthing
1345
    ): ProjectedPoint {
1346 1
        $latitude = $this->latitude->asRadians()->getValue();
1347 1
        $longitude = $this->longitude->asRadians()->getValue();
1348 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1349 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1350 1
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1351 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1352 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1353
1354 1
        if ($latitudeOrigin < 0) {
1355
            $t = tan(M_PI / 4 + $latitude / 2) / (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1356
        } else {
1357 1
            $t = tan(M_PI / 4 - $latitude / 2) * (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1358
        }
1359 1
        $rho = 2 * $a * $kO * $t / sqrt((1 + $e) ** (1 + $e) * (1 - $e) ** (1 - $e));
1360
1361 1
        $theta = $longitude - $longitudeOrigin;
1362 1
        $dE = $rho * sin($theta);
1363 1
        $dN = $rho * cos($theta);
1364
1365 1
        $easting = $falseEasting->asMetres()->getValue() + $dE;
1366 1
        if ($latitudeOrigin < 0) {
1367
            $northing = $falseNorthing->asMetres()->getValue() + $dN;
1368
        } else {
1369 1
            $northing = $falseNorthing->asMetres()->getValue() - $dN;
1370
        }
1371
1372 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1373
    }
1374
1375
    /**
1376
     * Polar Stereographic (variant B).
1377
     */
1378 1
    public function polarStereographicVariantB(
1379
        Projected $to,
1380
        Angle $latitudeOfStandardParallel,
1381
        Angle $longitudeOfOrigin,
1382
        Length $falseEasting,
1383
        Length $falseNorthing
1384
    ): ProjectedPoint {
1385 1
        $latitude = $this->latitude->asRadians()->getValue();
1386 1
        $longitude = $this->longitude->asRadians()->getValue();
1387 1
        $firstStandardParallel = $latitudeOfStandardParallel->asRadians()->getValue();
1388 1
        $longitudeOrigin = $longitudeOfOrigin->asRadians()->getValue();
1389 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1390 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1391 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1392
1393 1
        if ($firstStandardParallel < 0) {
1394 1
            $tF = tan(M_PI / 4 + $firstStandardParallel / 2) / (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1395 1
            $t = tan(M_PI / 4 + $latitude / 2) / (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1396
        } else {
1397
            $tF = tan(M_PI / 4 - $firstStandardParallel / 2) * (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1398
            $t = tan(M_PI / 4 - $latitude / 2) * (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1399
        }
1400 1
        $mF = cos($firstStandardParallel) / sqrt(1 - $e2 * sin($firstStandardParallel) ** 2);
1401 1
        $kO = $mF * sqrt((1 + $e) ** (1 + $e) * (1 - $e) ** (1 - $e)) / (2 * $tF);
1402
1403 1
        $rho = 2 * $a * $kO * $t / sqrt((1 + $e) ** (1 + $e) * (1 - $e) ** (1 - $e));
1404
1405 1
        $theta = $longitude - $longitudeOrigin;
1406 1
        $dE = $rho * sin($theta);
1407 1
        $dN = $rho * cos($theta);
1408
1409 1
        $easting = $falseEasting->asMetres()->getValue() + $dE;
1410 1
        if ($firstStandardParallel < 0) {
1411 1
            $northing = $falseNorthing->asMetres()->getValue() + $dN;
1412
        } else {
1413
            $northing = $falseNorthing->asMetres()->getValue() - $dN;
1414
        }
1415
1416 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1417
    }
1418
1419
    /**
1420
     * Polar Stereographic (variant C).
1421
     */
1422 1
    public function polarStereographicVariantC(
1423
        Projected $to,
1424
        Angle $latitudeOfStandardParallel,
1425
        Angle $longitudeOfOrigin,
1426
        Length $eastingAtFalseOrigin,
1427
        Length $northingAtFalseOrigin
1428
    ): ProjectedPoint {
1429 1
        $latitude = $this->latitude->asRadians()->getValue();
1430 1
        $longitude = $this->longitude->asRadians()->getValue();
1431 1
        $firstStandardParallel = $latitudeOfStandardParallel->asRadians()->getValue();
1432 1
        $longitudeOrigin = $longitudeOfOrigin->asRadians()->getValue();
1433 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1434 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1435 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1436
1437 1
        if ($firstStandardParallel < 0) {
1438 1
            $tF = tan(M_PI / 4 + $firstStandardParallel / 2) / (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1439 1
            $t = tan(M_PI / 4 + $latitude / 2) / (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1440
        } else {
1441
            $tF = tan(M_PI / 4 - $firstStandardParallel / 2) * (((1 + $e * sin($firstStandardParallel)) / (1 - $e * sin($firstStandardParallel))) ** ($e / 2));
1442
            $t = tan(M_PI / 4 - $latitude / 2) * (((1 + $e * sin($latitude)) / (1 - $e * sin($latitude))) ** ($e / 2));
1443
        }
1444 1
        $mF = cos($firstStandardParallel) / sqrt(1 - $e2 * sin($firstStandardParallel) ** 2);
1445
1446 1
        $rhoF = $a * $mF;
1447 1
        $rho = $rhoF * $t / $tF;
1448
1449 1
        $theta = $longitude - $longitudeOrigin;
1450 1
        $dE = $rho * sin($theta);
1451 1
        $dN = $rho * cos($theta);
1452
1453 1
        $easting = $eastingAtFalseOrigin->asMetres()->getValue() + $dE;
1454 1
        if ($firstStandardParallel < 0) {
1455 1
            $northing = $northingAtFalseOrigin->asMetres()->getValue() - $rhoF + $dN;
1456
        } else {
1457
            $northing = $northingAtFalseOrigin->asMetres()->getValue() + $rhoF - $dN;
1458
        }
1459
1460 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1461
    }
1462
1463
    /**
1464
     * Popular Visualisation Pseudo Mercator
1465
     * Applies spherical formulas to the ellipsoid. As such does not have the properties of a true Mercator projection.
1466
     */
1467 1
    public function popularVisualisationPseudoMercator(
1468
        Projected $to,
1469
        Angle $latitudeOfNaturalOrigin,
1470
        Angle $longitudeOfNaturalOrigin,
1471
        Length $falseEasting,
1472
        Length $falseNorthing
1473
    ): ProjectedPoint {
1474 1
        $latitude = $this->latitude->asRadians()->getValue();
1475 1
        $longitude = $this->longitude->asRadians()->getValue();
1476 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
0 ignored issues
show
Unused Code introduced by
The assignment to $latitudeOrigin is dead and can be removed.
Loading history...
1477 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1478 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1479
1480 1
        $easting = $falseEasting->asMetres()->getValue() + $a * ($longitude - $longitudeOrigin);
1481 1
        $northing = $falseNorthing->asMetres()->getValue() + $a * log(tan(M_PI / 4 + $latitude / 2));
1482
1483 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1484
    }
1485
1486
    /**
1487
     * Mercator (variant A)
1488
     * Note that in these formulas the parameter latitude of natural origin (latO) is not used. However for this
1489
     * Mercator (variant A) method the EPSG dataset includes this parameter, which must have a value of zero, for
1490
     * completeness in CRS labelling.
1491
     */
1492 1
    public function mercatorVariantA(
1493
        Projected $to,
1494
        Angle $latitudeOfNaturalOrigin,
1495
        Angle $longitudeOfNaturalOrigin,
1496
        Scale $scaleFactorAtNaturalOrigin,
1497
        Length $falseEasting,
1498
        Length $falseNorthing
1499
    ): ProjectedPoint {
1500 1
        $latitude = $this->latitude->asRadians()->getValue();
1501 1
        $longitude = $this->longitude->asRadians()->getValue();
1502
1503 1
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
0 ignored issues
show
Unused Code introduced by
The assignment to $latitudeOrigin is dead and can be removed.
Loading history...
1504 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1505 1
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1506
1507 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1508 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1509
1510 1
        $easting = $falseEasting->asMetres()->getValue() + $a * $kO * ($longitude - $longitudeOrigin);
1511 1
        $northing = $falseNorthing->asMetres()->getValue() + $a * $kO * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1512
1513 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1514
    }
1515
1516
    /**
1517
     * Mercator (variant B)
1518
     * Used for most nautical charts.
1519
     */
1520 1
    public function mercatorVariantB(
1521
        Projected $to,
1522
        Angle $latitudeOf1stStandardParallel,
1523
        Angle $longitudeOfNaturalOrigin,
1524
        Length $falseEasting,
1525
        Length $falseNorthing
1526
    ): ProjectedPoint {
1527 1
        $latitude = $this->latitude->asRadians()->getValue();
1528 1
        $longitude = $this->longitude->asRadians()->getValue();
1529 1
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1530 1
        $firstStandardParallel = $latitudeOf1stStandardParallel->asRadians()->getValue();
1531 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1532 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1533 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1534
1535 1
        $kO = cos($firstStandardParallel) / sqrt(1 - $e2 * sin($firstStandardParallel) ** 2);
1536
1537 1
        $easting = $falseEasting->asMetres()->getValue() + $a * $kO * ($longitude - $longitudeOrigin);
1538 1
        $northing = $falseNorthing->asMetres()->getValue() + $a * $kO * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1539
1540 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1541
    }
1542
1543
    /**
1544
     * Longitude rotation
1545
     * This transformation allows calculation of the longitude of a point in the target system by adding the parameter
1546
     * value to the longitude value of the point in the source system.
1547
     */
1548 1
    public function longitudeRotation(
1549
        Geographic $to,
1550
        Angle $longitudeOffset
1551
    ): self {
1552 1
        $newLongitude = $this->longitude->add($longitudeOffset);
1553 1
        if ($newLongitude->asDegrees()->getValue() < -180) {
1554
            $newLongitude = $newLongitude->add(new Degree(360));
1555
        }
1556
1557 1
        return static::create($this->latitude, $newLongitude, $this->height, $to, $this->epoch);
1558
    }
1559
1560
    /**
1561
     * Hotine Oblique Mercator (variant A).
1562
     */
1563 1
    public function obliqueMercatorHotineVariantA(
1564
        Projected $to,
1565
        Angle $latitudeOfProjectionCentre,
1566
        Angle $longitudeOfProjectionCentre,
1567
        Angle $azimuthOfInitialLine,
1568
        Angle $angleFromRectifiedToSkewGrid,
1569
        Scale $scaleFactorOnInitialLine,
1570
        Length $falseEasting,
1571
        Length $falseNorthing
1572
    ): ProjectedPoint {
1573 1
        $latitude = $this->latitude->asRadians()->getValue();
1574 1
        $longitude = $this->longitude->asRadians()->getValue();
1575 1
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1576 1
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1577 1
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1578 1
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1579 1
        $gammaC = $angleFromRectifiedToSkewGrid->asRadians()->getValue();
1580 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1581 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1582 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1583
1584 1
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1585 1
        $A = $a * $B * $kC * sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2);
1586 1
        $tO = tan(M_PI / 4 - $latC / 2) / ((1 - $e * sin($latC)) / (1 + $e * sin($latC))) ** ($e / 2);
1587 1
        $D = $B * sqrt((1 - $e2)) / (cos($latC) * sqrt(1 - $e2 * sin($latC) ** 2));
1588 1
        $DD = max(1, $D ** 2);
1589 1
        $F = $D + sqrt($DD - 1) * static::sign($latC);
1590 1
        $H = $F * ($tO) ** $B;
1591 1
        $G = ($F - 1 / $F) / 2;
1592 1
        $gammaO = self::asin(sin($alphaC) / $D);
1593 1
        $lonO = $lonC - (self::asin($G * tan($gammaO))) / $B;
1594
1595 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1596 1
        $Q = $H / $t ** $B;
1597 1
        $S = ($Q - 1 / $Q) / 2;
1598 1
        $T = ($Q + 1 / $Q) / 2;
1599 1
        $V = sin($B * ($longitude - $lonO));
1600 1
        $U = (-$V * cos($gammaO) + $S * sin($gammaO)) / $T;
1601 1
        $v = $A * log((1 - $U) / (1 + $U)) / (2 * $B);
1602 1
        $u = $A * atan2(($S * cos($gammaO) + $V * sin($gammaO)), cos($B * ($longitude - $lonO))) / $B;
1603
1604 1
        $easting = $v * cos($gammaC) + $u * sin($gammaC) + $falseEasting->asMetres()->getValue();
1605 1
        $northing = $u * cos($gammaC) - $v * sin($gammaC) + $falseNorthing->asMetres()->getValue();
1606
1607 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1608
    }
1609
1610
    /**
1611
     * Hotine Oblique Mercator (variant B).
1612
     */
1613 1
    public function obliqueMercatorHotineVariantB(
1614
        Projected $to,
1615
        Angle $latitudeOfProjectionCentre,
1616
        Angle $longitudeOfProjectionCentre,
1617
        Angle $azimuthOfInitialLine,
1618
        Angle $angleFromRectifiedToSkewGrid,
1619
        Scale $scaleFactorOnInitialLine,
1620
        Length $eastingAtProjectionCentre,
1621
        Length $northingAtProjectionCentre
1622
    ): ProjectedPoint {
1623 1
        $latitude = $this->latitude->asRadians()->getValue();
1624 1
        $longitude = $this->longitude->asRadians()->getValue();
1625 1
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1626 1
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1627 1
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1628 1
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1629 1
        $gammaC = $angleFromRectifiedToSkewGrid->asRadians()->getValue();
1630 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1631 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1632 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1633
1634 1
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1635 1
        $A = $a * $B * $kC * sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2);
1636 1
        $tO = tan(M_PI / 4 - $latC / 2) / ((1 - $e * sin($latC)) / (1 + $e * sin($latC))) ** ($e / 2);
1637 1
        $D = $B * sqrt((1 - $e2)) / (cos($latC) * sqrt(1 - $e2 * sin($latC) ** 2));
1638 1
        $DD = max(1, $D ** 2);
1639 1
        $F = $D + sqrt($DD - 1) * static::sign($latC);
1640 1
        $H = $F * ($tO) ** $B;
1641 1
        $G = ($F - 1 / $F) / 2;
1642 1
        $gammaO = self::asin(sin($alphaC) / $D);
1643 1
        $lonO = $lonC - (self::asin($G * tan($gammaO))) / $B;
1644 1
        $vC = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $vC is dead and can be removed.
Loading history...
1645 1
        if ($alphaC === M_PI / 2) {
1646
            $uC = $A * ($lonC - $lonO);
1647
        } else {
1648 1
            $uC = ($A / $B) * atan2(sqrt($DD - 1), cos($alphaC)) * static::sign($latC);
1649
        }
1650
1651 1
        $t = tan(M_PI / 4 - $latitude / 2) / ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2);
1652 1
        $Q = $H / $t ** $B;
1653 1
        $S = ($Q - 1 / $Q) / 2;
1654 1
        $T = ($Q + 1 / $Q) / 2;
1655 1
        $V = sin($B * ($longitude - $lonO));
1656 1
        $U = (-$V * cos($gammaO) + $S * sin($gammaO)) / $T;
1657 1
        $v = $A * log((1 - $U) / (1 + $U)) / (2 * $B);
1658
1659 1
        if ($alphaC === M_PI / 2) {
1660
            if ($longitude === $lonC) {
1661
                $u = 0;
1662
            } else {
1663
                $u = ($A * atan(($S * cos($gammaO) + $V * sin($gammaO)) / cos($B * ($longitude - $lonO))) / $B) - (abs($uC) * static::sign($latC) * static::sign($lonC - $longitude));
1664
            }
1665
        } else {
1666 1
            $u = ($A * atan2(($S * cos($gammaO) + $V * sin($gammaO)), cos($B * ($longitude - $lonO))) / $B) - (abs($uC) * static::sign($latC));
1667
        }
1668
1669 1
        $easting = $v * cos($gammaC) + $u * sin($gammaC) + $eastingAtProjectionCentre->asMetres()->getValue();
1670 1
        $northing = $u * cos($gammaC) - $v * sin($gammaC) + $northingAtProjectionCentre->asMetres()->getValue();
1671
1672 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1673
    }
1674
1675
    /**
1676
     * Laborde Oblique Mercator.
1677
     */
1678 1
    public function obliqueMercatorLaborde(
1679
        Projected $to,
1680
        Angle $latitudeOfProjectionCentre,
1681
        Angle $longitudeOfProjectionCentre,
1682
        Angle $azimuthOfInitialLine,
1683
        Scale $scaleFactorOnInitialLine,
1684
        Length $falseEasting,
1685
        Length $falseNorthing
1686
    ): ProjectedPoint {
1687 1
        $latitude = $this->latitude->asRadians()->getValue();
1688 1
        $longitude = $this->longitude->asRadians()->getValue();
1689 1
        $latC = $latitudeOfProjectionCentre->asRadians()->getValue();
1690 1
        $lonC = $longitudeOfProjectionCentre->asRadians()->getValue();
1691 1
        $alphaC = $azimuthOfInitialLine->asRadians()->getValue();
1692 1
        $kC = $scaleFactorOnInitialLine->asUnity()->getValue();
1693 1
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1694 1
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1695 1
        $e2 = $this->crs->getDatum()->getEllipsoid()->getEccentricitySquared();
1696
1697 1
        $B = sqrt(1 + ($e2 * cos($latC) ** 4 / (1 - $e2)));
1698 1
        $latS = self::asin(sin($latC) / $B);
1699 1
        $R = $a * $kC * (sqrt(1 - $e2) / (1 - $e2 * sin($latC) ** 2));
1700 1
        $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));
1701
1702 1
        $L = $B * ($longitude - $lonC);
1703 1
        $q = $C + $B * log(tan(M_PI / 4 + $latitude / 2) * ((1 - $e * sin($latitude)) / (1 + $e * sin($latitude))) ** ($e / 2));
1704 1
        $P = 2 * atan(M_E ** $q) - M_PI / 2;
1705 1
        $U = cos($P) * cos($L) * cos($latS) + sin($P) * sin($latS);
1706 1
        $V = cos($P) * cos($L) * sin($latS) - sin($P) * cos($latS);
1707 1
        $W = cos($P) * sin($L);
1708 1
        $d = sqrt($U ** 2 + $V ** 2);
1709 1
        if ($d === 0.0) {
1710
            $LPrime = 0;
1711
            $PPrime = static::sign($W) * M_PI / 2;
1712
        } else {
1713 1
            $LPrime = 2 * atan($V / ($U + $d));
1714 1
            $PPrime = atan($W / $d);
1715
        }
1716 1
        $H = new ComplexNumber(-$LPrime, log(tan(M_PI / 4 + $PPrime / 2)));
1717 1
        $G = (new ComplexNumber(1 - cos(2 * $alphaC), sin(2 * $alphaC)))->divide(new ComplexNumber(12, 0));
1718
1719 1
        $easting = $falseEasting->asMetres()->getValue() + $R * $H->pow(3)->multiply($G)->add($H)->getImaginary();
1720 1
        $northing = $falseNorthing->asMetres()->getValue() + $R * $H->pow(3)->multiply($G)->add($H)->getReal();
1721
1722 1
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1723
    }
1724
1725
    /**
1726
     * Transverse Mercator.
1727
     */
1728 7
    public function transverseMercator(
1729
        Projected $to,
1730
        Angle $latitudeOfNaturalOrigin,
1731
        Angle $longitudeOfNaturalOrigin,
1732
        Scale $scaleFactorAtNaturalOrigin,
1733
        Length $falseEasting,
1734
        Length $falseNorthing
1735
    ): ProjectedPoint {
1736 7
        $latitude = $this->latitude->asRadians()->getValue();
1737 7
        $longitude = $this->longitude->asRadians()->getValue();
1738 7
        $latitudeOrigin = $latitudeOfNaturalOrigin->asRadians()->getValue();
1739 7
        $longitudeOrigin = $longitudeOfNaturalOrigin->asRadians()->getValue();
1740 7
        $kO = $scaleFactorAtNaturalOrigin->asUnity()->getValue();
1741 7
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1742 7
        $e = $this->crs->getDatum()->getEllipsoid()->getEccentricity();
1743 7
        $f = $this->crs->getDatum()->getEllipsoid()->getInverseFlattening();
1744
1745 7
        $n = $f / (2 - $f);
1746 7
        $B = ($a / (1 + $n)) * (1 + $n ** 2 / 4 + $n ** 4 / 64);
1747
1748 7
        $h1 = $n / 2 - (2 / 3) * $n ** 2 + (5 / 16) * $n ** 3 + (41 / 180) * $n ** 4;
1749 7
        $h2 = (13 / 48) * $n ** 2 - (3 / 5) * $n ** 3 + (557 / 1440) * $n ** 4;
1750 7
        $h3 = (61 / 240) * $n ** 3 - (103 / 140) * $n ** 4;
1751 7
        $h4 = (49561 / 161280) * $n ** 4;
1752
1753 7
        if ($latitudeOrigin === 0.0) {
0 ignored issues
show
introduced by
The condition $latitudeOrigin === 0.0 is always false.
Loading history...
1754 5
            $mO = 0;
1755 2
        } elseif ($latitudeOrigin === M_PI / 2) {
1756
            $mO = $B * M_PI / 2;
1757 2
        } elseif ($latitudeOrigin === -M_PI / 2) {
1758
            $mO = $B * -M_PI / 2;
1759
        } else {
1760 2
            $qO = asinh(tan($latitudeOrigin)) - ($e * atanh($e * sin($latitudeOrigin)));
1761 2
            $betaO = atan(sinh($qO));
1762 2
            $xiO0 = self::asin(sin($betaO));
1763 2
            $xiO1 = $h1 * sin(2 * $xiO0);
1764 2
            $xiO2 = $h2 * sin(4 * $xiO0);
1765 2
            $xiO3 = $h3 * sin(6 * $xiO0);
1766 2
            $xiO4 = $h4 * sin(8 * $xiO0);
1767 2
            $xiO = $xiO0 + $xiO1 + $xiO2 + $xiO3 + $xiO4;
1768 2
            $mO = $B * $xiO;
1769
        }
1770
1771 7
        $Q = asinh(tan($latitude)) - ($e * atanh($e * sin($latitude)));
1772 7
        $beta = atan(sinh($Q));
1773 7
        $eta0 = atanh(cos($beta) * sin($longitude - $longitudeOrigin));
1774 7
        $xi0 = self::asin(sin($beta) * cosh($eta0));
1775 7
        $xi1 = $h1 * sin(2 * $xi0) * cosh(2 * $eta0);
1776 7
        $eta1 = $h1 * cos(2 * $xi0) * sinh(2 * $eta0);
1777 7
        $xi2 = $h2 * sin(4 * $xi0) * cosh(4 * $eta0);
1778 7
        $eta2 = $h2 * cos(4 * $xi0) * sinh(4 * $eta0);
1779 7
        $xi3 = $h3 * sin(6 * $xi0) * cosh(6 * $eta0);
1780 7
        $eta3 = $h3 * cos(6 * $xi0) * sinh(6 * $eta0);
1781 7
        $xi4 = $h4 * sin(8 * $xi0) * cosh(8 * $eta0);
1782 7
        $eta4 = $h4 * cos(8 * $xi0) * sinh(8 * $eta0);
1783 7
        $xi = $xi0 + $xi1 + $xi2 + $xi3 + $xi4;
1784 7
        $eta = $eta0 + $eta1 + $eta2 + $eta3 + $eta4;
1785
1786 7
        $easting = $falseEasting->asMetres()->getValue() + $kO * $B * $eta;
1787 7
        $northing = $falseNorthing->asMetres()->getValue() + $kO * ($B * $xi - $mO);
1788
1789 7
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1790
    }
1791
1792
    /**
1793
     * Transverse Mercator Zoned Grid System
1794
     * If locations fall outwith the fixed zones the general Transverse Mercator method (code 9807) must be used for
1795
     * each zone.
1796
     */
1797 3
    public function transverseMercatorZonedGrid(
1798
        Projected $to,
1799
        Angle $latitudeOfNaturalOrigin,
1800
        Angle $initialLongitude,
1801
        Angle $zoneWidth,
1802
        Scale $scaleFactorAtNaturalOrigin,
1803
        Length $falseEasting,
1804
        Length $falseNorthing
1805
    ): ProjectedPoint {
1806 3
        $W = $zoneWidth->asDegrees()->getValue();
1807 3
        $Z = ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / $W) % (360 / $W) + 1;
1808
1809 3
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * $W - $W / 2));
1810 3
        $falseEasting = $falseEasting->add(new Metre($Z * 1000000));
1811
1812 3
        return $this->transverseMercator($to, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
1813
    }
1814
1815
    /**
1816
     * New Zealand Map Grid.
1817
     */
1818 3
    public function newZealandMapGrid(
1819
        Projected $to,
1820
        Angle $latitudeOfNaturalOrigin,
1821
        Angle $longitudeOfNaturalOrigin,
1822
        Length $falseEasting,
1823
        Length $falseNorthing
1824
    ): ProjectedPoint {
1825 3
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1826
1827 3
        $deltaLatitudeToOrigin = Angle::convert($this->latitude->subtract($latitudeOfNaturalOrigin), Angle::EPSG_ARC_SECOND)->getValue();
1828 3
        $deltaLongitudeToOrigin = $this->longitude->subtract($longitudeOfNaturalOrigin)->asRadians();
1829
1830 3
        $deltaPsi = 0;
1831 3
        $deltaPsi += 0.6399175073 * ($deltaLatitudeToOrigin * 0.00001) ** 1;
1832 3
        $deltaPsi += -0.1358797613 * ($deltaLatitudeToOrigin * 0.00001) ** 2;
1833 3
        $deltaPsi += 0.063294409 * ($deltaLatitudeToOrigin * 0.00001) ** 3;
1834 3
        $deltaPsi += -0.02526853 * ($deltaLatitudeToOrigin * 0.00001) ** 4;
1835 3
        $deltaPsi += 0.0117879 * ($deltaLatitudeToOrigin * 0.00001) ** 5;
1836 3
        $deltaPsi += -0.0055161 * ($deltaLatitudeToOrigin * 0.00001) ** 6;
1837 3
        $deltaPsi += 0.0026906 * ($deltaLatitudeToOrigin * 0.00001) ** 7;
1838 3
        $deltaPsi += -0.001333 * ($deltaLatitudeToOrigin * 0.00001) ** 8;
1839 3
        $deltaPsi += 0.00067 * ($deltaLatitudeToOrigin * 0.00001) ** 9;
1840 3
        $deltaPsi += -0.00034 * ($deltaLatitudeToOrigin * 0.00001) ** 10;
1841
1842 3
        $zeta = new ComplexNumber($deltaPsi, $deltaLongitudeToOrigin->getValue());
1843
1844 3
        $B1 = new ComplexNumber(0.7557853228, 0.0);
1845 3
        $B2 = new ComplexNumber(0.249204646, 0.003371507);
1846 3
        $B3 = new ComplexNumber(-0.001541739, 0.041058560);
1847 3
        $B4 = new ComplexNumber(-0.10162907, 0.01727609);
1848 3
        $B5 = new ComplexNumber(-0.26623489, -0.36249218);
1849 3
        $B6 = new ComplexNumber(-0.6870983, -1.1651967);
1850 3
        $z = new ComplexNumber(0, 0);
1851 3
        $z = $z->add($B1->multiply($zeta->pow(1)));
1852 3
        $z = $z->add($B2->multiply($zeta->pow(2)));
1853 3
        $z = $z->add($B3->multiply($zeta->pow(3)));
1854 3
        $z = $z->add($B4->multiply($zeta->pow(4)));
1855 3
        $z = $z->add($B5->multiply($zeta->pow(5)));
1856 3
        $z = $z->add($B6->multiply($zeta->pow(6)));
1857
1858 3
        $easting = $falseEasting->asMetres()->getValue() + $z->getImaginary() * $a;
1859 3
        $northing = $falseNorthing->asMetres()->getValue() + $z->getReal() * $a;
1860
1861 3
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1862
    }
1863
1864
    /**
1865
     * Madrid to ED50 polynomial.
1866
     */
1867 1
    public function madridToED50Polynomial(
1868
        Geographic2D $to,
1869
        Scale $A0,
1870
        Scale $A1,
1871
        Scale $A2,
1872
        Scale $A3,
1873
        Angle $B00,
1874
        Scale $B0,
1875
        Scale $B1,
1876
        Scale $B2,
1877
        Scale $B3
1878
    ): self {
1879 1
        $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());
1880 1
        $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()));
1881
1882 1
        return self::create($this->latitude->add($dLatitude), $this->longitude->add($dLongitude), null, $to, $this->epoch);
1883
    }
1884
1885
    /**
1886
     * Geographic3D to 2D conversion.
1887
     */
1888 1
    public function threeDToTwoD(
1889
        Geographic $to
1890
    ): self {
1891 1
        if ($to instanceof Geographic2D) {
1892 1
            return static::create($this->latitude, $this->longitude, null, $to, $this->epoch);
1893
        }
1894
1895
        return static::create($this->latitude, $this->longitude, new Metre(0), $to, $this->epoch);
1896
    }
1897
1898
    /**
1899
     * Geographic2D offsets.
1900
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
1901
     * coordinate values of the point in the source system.
1902
     */
1903 1
    public function geographic2DOffsets(
1904
        Geographic $to,
1905
        Angle $latitudeOffset,
1906
        Angle $longitudeOffset
1907
    ): self {
1908 1
        $toLatitude = $this->latitude->add($latitudeOffset);
1909 1
        $toLongitude = $this->longitude->add($longitudeOffset);
1910
1911 1
        return static::create($toLatitude, $toLongitude, null, $to, $this->epoch);
1912
    }
1913
1914
    /*
1915
     * Geographic2D with Height Offsets.
1916
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
1917
     * coordinate values of the point in the source system.
1918
     */
1919
    public function geographic2DWithHeightOffsets(
1920
        Compound $to,
1921
        Angle $latitudeOffset,
1922
        Angle $longitudeOffset,
1923
        Length $geoidUndulation
1924
    ): CompoundPoint {
1925
        $toLatitude = $this->latitude->add($latitudeOffset);
1926
        $toLongitude = $this->longitude->add($longitudeOffset);
1927
        $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

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

1930
        $vertical = VerticalPoint::create(/** @scrutinizer ignore-type */ $toHeight, $to->getVertical(), $this->epoch);
Loading history...
1931
1932
        return CompoundPoint::create($horizontal, $vertical, $to, $this->epoch);
1933
    }
1934
1935
    /**
1936
     * General polynomial of degree.
1937
     * @param Coefficient[] $powerCoefficients
1938
     */
1939 1
    public function generalPolynomial(
1940
        Geographic $to,
1941
        Angle $ordinate1OfEvaluationPointInSourceCRS,
1942
        Angle $ordinate2OfEvaluationPointInSourceCRS,
1943
        Angle $ordinate1OfEvaluationPointInTargetCRS,
1944
        Angle $ordinate2OfEvaluationPointInTargetCRS,
1945
        Scale $scalingFactorForSourceCRSCoordDifferences,
1946
        Scale $scalingFactorForTargetCRSCoordDifferences,
1947
        Scale $A0,
1948
        Scale $B0,
1949
        array $powerCoefficients
1950
    ): self {
1951 1
        $xs = $this->latitude->getValue();
1952 1
        $ys = $this->longitude->getValue();
1953
1954 1
        $t = $this->generalPolynomialUnitless(
1955 1
            $xs,
1956
            $ys,
1957
            $ordinate1OfEvaluationPointInSourceCRS,
1958
            $ordinate2OfEvaluationPointInSourceCRS,
1959
            $ordinate1OfEvaluationPointInTargetCRS,
1960
            $ordinate2OfEvaluationPointInTargetCRS,
1961
            $scalingFactorForSourceCRSCoordDifferences,
1962
            $scalingFactorForTargetCRSCoordDifferences,
1963
            $A0,
1964
            $B0,
1965
            $powerCoefficients
1966
        );
1967
1968 1
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
1969 1
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
1970 1
            $xtUnit = Angle::EPSG_DEGREE;
1971
        }
1972 1
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
1973 1
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
1974 1
            $ytUnit = Angle::EPSG_DEGREE;
1975
        }
1976
1977 1
        return static::create(
1978 1
            Angle::makeUnit($t['xt'], $xtUnit),
1979 1
            Angle::makeUnit($t['yt'], $ytUnit),
1980 1
            $this->height,
1981
            $to,
1982 1
            $this->epoch
1983
        );
1984
    }
1985
1986
    /**
1987
     * Reversible polynomial.
1988
     * @param Coefficient[] $powerCoefficients
1989
     */
1990 2
    public function reversiblePolynomial(
1991
        Geographic $to,
1992
        Angle $ordinate1OfEvaluationPoint,
1993
        Angle $ordinate2OfEvaluationPoint,
1994
        Scale $scalingFactorForCoordDifferences,
1995
        Scale $A0,
1996
        Scale $B0,
1997
        $powerCoefficients
1998
    ): self {
1999 2
        $xs = $this->latitude->getValue();
2000 2
        $ys = $this->longitude->getValue();
2001
2002 2
        $t = $this->reversiblePolynomialUnitless(
2003 2
            $xs,
2004
            $ys,
2005
            $ordinate1OfEvaluationPoint,
2006
            $ordinate2OfEvaluationPoint,
2007
            $scalingFactorForCoordDifferences,
2008
            $A0,
2009
            $B0,
2010
            $powerCoefficients
2011
        );
2012
2013 2
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2014 2
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2015 2
            $xtUnit = Angle::EPSG_DEGREE;
2016
        }
2017 2
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2018 2
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2019 2
            $ytUnit = Angle::EPSG_DEGREE;
2020
        }
2021
2022 2
        return static::create(
2023 2
            Angle::makeUnit($t['xt'], $xtUnit),
2024 2
            Angle::makeUnit($t['yt'], $ytUnit),
2025 2
            $this->height,
2026
            $to,
2027 2
            $this->epoch
2028
        );
2029
    }
2030
2031
    /**
2032
     * Axis Order Reversal.
2033
     */
2034
    public function axisReversal(
2035
        Geographic $to
2036
    ) {
2037
        // axes are read in from the CRS, this is a book-keeping adjustment only
2038
        return static::create($this->latitude, $this->longitude, $this->height, $to, $this->epoch);
2039
    }
2040
2041 7
    public function asGeographicValue(): GeographicValue
2042
    {
2043 7
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2044
    }
2045
}
2046