Passed
Push — master ( 1c87d2...a64886 )
by Doug
04:35
created

ProjectedPoint::complexPolynomial()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 44
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 22
c 0
b 0
f 0
dl 0
loc 44
ccs 22
cts 22
cp 1
rs 9.568
cc 3
nc 1
nop 15
crap 3

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use function abs;
12
use function asinh;
13
use function atan;
14
use function atan2;
15
use function atanh;
16
use function cos;
17
use function cosh;
18
use DateTime;
19
use DateTimeImmutable;
20
use DateTimeInterface;
21
use function implode;
22
use function is_nan;
23
use function log;
24
use function max;
25
use PHPCoord\CoordinateOperation\AutoConversion;
26
use PHPCoord\CoordinateOperation\ComplexNumber;
27
use PHPCoord\CoordinateOperation\GeographicValue;
28
use PHPCoord\CoordinateReferenceSystem\Geographic;
29
use PHPCoord\CoordinateReferenceSystem\Projected;
30
use PHPCoord\CoordinateSystem\Axis;
31
use PHPCoord\Exception\InvalidAxesException;
32
use PHPCoord\Exception\InvalidCoordinateReferenceSystemException;
33
use PHPCoord\Exception\UnknownAxisException;
34
use PHPCoord\UnitOfMeasure\Angle\Angle;
35
use PHPCoord\UnitOfMeasure\Angle\ArcSecond;
36
use PHPCoord\UnitOfMeasure\Angle\Degree;
37
use PHPCoord\UnitOfMeasure\Angle\Radian;
38
use PHPCoord\UnitOfMeasure\Length\Length;
39
use PHPCoord\UnitOfMeasure\Length\Metre;
40
use PHPCoord\UnitOfMeasure\Scale\Coefficient;
41
use PHPCoord\UnitOfMeasure\Scale\Scale;
42
use function sin;
43
use function sinh;
44
use function sqrt;
45
use function substr;
46
use function tan;
47
use function tanh;
48
49
/**
50
 * Coordinate representing a point on a map projection.
51
 */
52
class ProjectedPoint extends Point
53
{
54
    use AutoConversion;
55
56
    /**
57
     * Easting.
58
     */
59
    protected Length $easting;
60
61
    /**
62
     * Northing.
63
     */
64
    protected Length $northing;
65
66
    /**
67
     * Westing.
68
     */
69
    protected Length $westing;
70
71
    /**
72
     * Southing.
73
     */
74
    protected Length $southing;
75
76
    /**
77
     * Coordinate reference system.
78
     */
79
    protected Projected $crs;
80
81
    /**
82
     * Coordinate epoch (date for which the specified coordinates represented this point).
83
     */
84
    protected ?DateTimeImmutable $epoch;
85
86 135
    protected function __construct(?Length $easting, ?Length $northing, ?Length $westing, ?Length $southing, Projected $crs, ?DateTimeInterface $epoch = null)
87
    {
88 135
        $this->crs = $crs;
89
90 135
        $eastingAxis = $this->getAxisByName(Axis::EASTING);
91 135
        $westingAxis = $this->getAxisByName(Axis::WESTING);
92 135
        $northingAxis = $this->getAxisByName(Axis::NORTHING);
93 135
        $southingAxis = $this->getAxisByName(Axis::SOUTHING);
94
95 135
        if ($easting && $eastingAxis) {
96 121
            $this->easting = Length::convert($easting, $eastingAxis->getUnitOfMeasureId());
97 121
            $this->westing = $this->easting->multiply(-1);
98 16
        } elseif ($westing && $westingAxis) {
99 15
            $this->westing = Length::convert($westing, $westingAxis->getUnitOfMeasureId());
100 15
            $this->easting = $this->westing->multiply(-1);
101
        } else {
102 1
            throw new InvalidAxesException($crs->getCoordinateSystem()->getAxes());
103
        }
104
105 134
        if ($northing && $northingAxis) {
106 124
            $this->northing = Length::convert($northing, $northingAxis->getUnitOfMeasureId());
107 124
            $this->southing = $this->northing->multiply(-1);
108 11
        } elseif ($southing && $southingAxis) {
109 10
            $this->southing = Length::convert($southing, $southingAxis->getUnitOfMeasureId());
110 10
            $this->northing = $this->southing->multiply(-1);
111
        } else {
112 1
            throw new InvalidAxesException($crs->getCoordinateSystem()->getAxes());
113
        }
114
115 133
        if ($epoch instanceof DateTime) {
116 1
            $epoch = DateTimeImmutable::createFromMutable($epoch);
117
        }
118 133
        $this->epoch = $epoch;
119 133
    }
120
121 115
    public static function create(?Length $easting, ?Length $northing, ?Length $westing, ?Length $southing, Projected $crs, ?DateTimeInterface $epoch = null): self
122
    {
123 115
        if ($crs->getSRID() === Projected::EPSG_OSGB_1936_BRITISH_NATIONAL_GRID) {
124 8
            return new BritishNationalGridPoint($easting, $northing, $epoch);
0 ignored issues
show
Bug introduced by
It seems like $northing can also be of type null; however, parameter $northing of PHPCoord\BritishNationalGridPoint::__construct() 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

124
            return new BritishNationalGridPoint($easting, /** @scrutinizer ignore-type */ $northing, $epoch);
Loading history...
Bug introduced by
It seems like $easting can also be of type null; however, parameter $easting of PHPCoord\BritishNationalGridPoint::__construct() 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

124
            return new BritishNationalGridPoint(/** @scrutinizer ignore-type */ $easting, $northing, $epoch);
Loading history...
125
        }
126
127 110
        if ($crs->getSRID() === Projected::EPSG_TM75_IRISH_GRID) {
128 1
            return new IrishGridPoint($easting, $northing, $epoch);
0 ignored issues
show
Bug introduced by
It seems like $easting can also be of type null; however, parameter $easting of PHPCoord\IrishGridPoint::__construct() 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

128
            return new IrishGridPoint(/** @scrutinizer ignore-type */ $easting, $northing, $epoch);
Loading history...
Bug introduced by
It seems like $northing can also be of type null; however, parameter $northing of PHPCoord\IrishGridPoint::__construct() 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

128
            return new IrishGridPoint($easting, /** @scrutinizer ignore-type */ $northing, $epoch);
Loading history...
129
        }
130
131 109
        if ($crs->getSRID() === Projected::EPSG_IRENET95_IRISH_TRANSVERSE_MERCATOR) {
132 1
            return new IrishTransverseMercatorPoint($easting, $northing, $epoch);
0 ignored issues
show
Bug introduced by
It seems like $easting can also be of type null; however, parameter $easting of PHPCoord\IrishTransverse...torPoint::__construct() 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

132
            return new IrishTransverseMercatorPoint(/** @scrutinizer ignore-type */ $easting, $northing, $epoch);
Loading history...
Bug introduced by
It seems like $northing can also be of type null; however, parameter $northing of PHPCoord\IrishTransverse...torPoint::__construct() 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

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

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

2013
        return new GeographicValue($asGeographicPoint->getLatitude(), $asGeographicPoint->/** @scrutinizer ignore-call */ getLongitude(), null, $this->getCRS()->getDatum());
Loading history...
Bug introduced by
The method getLatitude() does not exist on PHPCoord\Point. It seems like you code against a sub-type of PHPCoord\Point such as PHPCoord\GeographicPoint. ( Ignorable by Annotation )

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

2013
        return new GeographicValue($asGeographicPoint->/** @scrutinizer ignore-call */ getLatitude(), $asGeographicPoint->getLongitude(), null, $this->getCRS()->getDatum());
Loading history...
2014
    }
2015
}
2016