Passed
Push — 4.0.x ( 414ad0...5c8c8d )
by Doug
05:04 queued 12s
created

ProjectedPoint::obliqueMercatorLaborde()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 59
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 4.0023

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 40
c 1
b 0
f 0
nc 2
nop 7
dl 0
loc 59
ccs 36
cts 38
cp 0.9474
crap 4.0023
rs 9.28

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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