Passed
Push — master ( 300b0d...3598bd )
by Doug
09:39
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 const M_E;
25
use const M_PI;
26
use const M_PI_2;
27
use function max;
28
use PHPCoord\CoordinateOperation\AutoConversion;
29
use PHPCoord\CoordinateOperation\ComplexNumber;
30
use PHPCoord\CoordinateOperation\GeographicValue;
31
use PHPCoord\CoordinateReferenceSystem\Geographic;
32
use PHPCoord\CoordinateReferenceSystem\Projected;
33
use PHPCoord\CoordinateSystem\Axis;
34
use PHPCoord\Exception\InvalidAxesException;
35
use PHPCoord\Exception\InvalidCoordinateReferenceSystemException;
36
use PHPCoord\Exception\UnknownAxisException;
37
use PHPCoord\UnitOfMeasure\Angle\Angle;
38
use PHPCoord\UnitOfMeasure\Angle\ArcSecond;
39
use PHPCoord\UnitOfMeasure\Angle\Degree;
40
use PHPCoord\UnitOfMeasure\Angle\Radian;
41
use PHPCoord\UnitOfMeasure\Length\Length;
42
use PHPCoord\UnitOfMeasure\Length\Metre;
43
use PHPCoord\UnitOfMeasure\Scale\Coefficient;
44
use PHPCoord\UnitOfMeasure\Scale\Scale;
45
use function sin;
46
use function sinh;
47
use function sqrt;
48
use function substr;
49
use function tan;
50
use function tanh;
51
52
/**
53
 * Coordinate representing a point on a map projection.
54
 */
55
class ProjectedPoint extends Point
56
{
57
    use AutoConversion;
58
59
    /**
60
     * Easting.
61
     */
62
    protected Length $easting;
63
64
    /**
65
     * Northing.
66
     */
67
    protected Length $northing;
68
69
    /**
70
     * Westing.
71
     */
72
    protected Length $westing;
73
74
    /**
75
     * Southing.
76
     */
77
    protected Length $southing;
78
79
    /**
80
     * Coordinate reference system.
81
     */
82
    protected Projected $crs;
83
84
    /**
85
     * Coordinate epoch (date for which the specified coordinates represented this point).
86
     */
87
    protected ?DateTimeImmutable $epoch;
88
89 135
    protected function __construct(?Length $easting, ?Length $northing, ?Length $westing, ?Length $southing, Projected $crs, ?DateTimeInterface $epoch = null)
90
    {
91 135
        $this->crs = $crs;
92
93 135
        $eastingAxis = $this->getAxisByName(Axis::EASTING);
94 135
        $westingAxis = $this->getAxisByName(Axis::WESTING);
95 135
        $northingAxis = $this->getAxisByName(Axis::NORTHING);
96 135
        $southingAxis = $this->getAxisByName(Axis::SOUTHING);
97
98 135
        if ($easting && $eastingAxis) {
99 121
            $this->easting = Length::convert($easting, $eastingAxis->getUnitOfMeasureId());
100 121
            $this->westing = $this->easting->multiply(-1);
101 16
        } elseif ($westing && $westingAxis) {
102 15
            $this->westing = Length::convert($westing, $westingAxis->getUnitOfMeasureId());
103 15
            $this->easting = $this->westing->multiply(-1);
104
        } else {
105 1
            throw new InvalidAxesException($crs->getCoordinateSystem()->getAxes());
106
        }
107
108 134
        if ($northing && $northingAxis) {
109 124
            $this->northing = Length::convert($northing, $northingAxis->getUnitOfMeasureId());
110 124
            $this->southing = $this->northing->multiply(-1);
111 11
        } elseif ($southing && $southingAxis) {
112 10
            $this->southing = Length::convert($southing, $southingAxis->getUnitOfMeasureId());
113 10
            $this->northing = $this->southing->multiply(-1);
114
        } else {
115 1
            throw new InvalidAxesException($crs->getCoordinateSystem()->getAxes());
116
        }
117
118 133
        if ($epoch instanceof DateTime) {
119 1
            $epoch = DateTimeImmutable::createFromMutable($epoch);
120
        }
121 133
        $this->epoch = $epoch;
122 133
    }
123
124 115
    public static function create(?Length $easting, ?Length $northing, ?Length $westing, ?Length $southing, Projected $crs, ?DateTimeInterface $epoch = null): self
125
    {
126 115
        if ($crs->getSRID() === Projected::EPSG_OSGB_1936_BRITISH_NATIONAL_GRID) {
127 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

127
            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

127
            return new BritishNationalGridPoint(/** @scrutinizer ignore-type */ $easting, $northing, $epoch);
Loading history...
128
        }
129
130 110
        if ($crs->getSRID() === Projected::EPSG_TM75_IRISH_GRID) {
131 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

131
            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

131
            return new IrishGridPoint($easting, /** @scrutinizer ignore-type */ $northing, $epoch);
Loading history...
132
        }
133
134 109
        if ($crs->getSRID() === Projected::EPSG_IRENET95_IRISH_TRANSVERSE_MERCATOR) {
135 1
            return new IrishTransverseMercatorPoint($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\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

135
            return new IrishTransverseMercatorPoint($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\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

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

2066
        return new GeographicValue($asGeographicPoint->/** @scrutinizer ignore-call */ getLatitude(), $asGeographicPoint->getLongitude(), null, $this->getCRS()->getDatum());
Loading history...
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

2066
        return new GeographicValue($asGeographicPoint->getLatitude(), $asGeographicPoint->/** @scrutinizer ignore-call */ getLongitude(), null, $this->getCRS()->getDatum());
Loading history...
2067
    }
2068
}
2069