Passed
Push — test_feature_branch_build ( 3e9d7e...36e42b )
by Doug
61:58 queued 19s
created

GeographicPoint::positionVectorTransformation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 12
c 0
b 0
f 0
nc 1
nop 8
dl 0
loc 22
rs 9.8666

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use function abs;
12
use function asinh;
13
use function atan;
14
use function atan2;
15
use function atanh;
16
use function cos;
17
use function cosh;
18
use DateTime;
19
use DateTimeImmutable;
20
use DateTimeInterface;
21
use function get_class;
22
use function hypot;
23
use function implode;
24
use function is_nan;
25
use function log;
26
use const M_E;
27
use const M_PI;
28
use function max;
29
use PHPCoord\CoordinateOperation\AutoConversion;
30
use PHPCoord\CoordinateOperation\ComplexNumber;
31
use PHPCoord\CoordinateOperation\ConvertiblePoint;
32
use PHPCoord\CoordinateOperation\GeocentricValue;
33
use PHPCoord\CoordinateOperation\GeographicValue;
34
use PHPCoord\CoordinateOperation\OSTNOSGM15Grid;
35
use PHPCoord\CoordinateReferenceSystem\Compound;
36
use PHPCoord\CoordinateReferenceSystem\Geocentric;
37
use PHPCoord\CoordinateReferenceSystem\Geographic;
38
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
39
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
40
use PHPCoord\CoordinateReferenceSystem\Projected;
0 ignored issues
show
Bug introduced by
The type PHPCoord\CoordinateReferenceSystem\Projected was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

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

1504
        /** @scrutinizer ignore-unused */ Angle $latitudeOfNaturalOrigin,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1505
        Angle $longitudeOfNaturalOrigin,
1506
        Length $falseEasting,
1507
        Length $falseNorthing
1508
    ): ProjectedPoint {
1509
        $latitude = $this->latitude->asRadians()->getValue();
1510
        $a = $this->crs->getDatum()->getEllipsoid()->getSemiMajorAxis()->asMetres()->getValue();
1511
1512
        $easting = $falseEasting->asMetres()->getValue() + $a * ($this->normaliseLongitude($this->longitude->subtract($longitudeOfNaturalOrigin))->asRadians()->getValue());
1513
        $northing = $falseNorthing->asMetres()->getValue() + $a * log(tan(M_PI / 4 + $latitude / 2));
1514
1515
        return ProjectedPoint::create(new Metre($easting), new Metre($northing), new Metre(-$easting), new Metre(-$northing), $to, $this->epoch);
1516
    }
1517
1518
    /**
1519
     * Mercator (variant A)
1520
     * Note that in these formulas the parameter latitude of natural origin (latO) is not used. However for this
1521
     * Mercator (variant A) method the EPSG dataset includes this parameter, which must have a value of zero, for
1522
     * completeness in CRS labelling.
1523
     */
1524
    public function mercatorVariantA(
1525
        Projected $to,
1526
        Angle $latitudeOfNaturalOrigin,
0 ignored issues
show
Unused Code introduced by
The parameter $latitudeOfNaturalOrigin is not used and could be removed. ( Ignorable by Annotation )

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

1526
        /** @scrutinizer ignore-unused */ Angle $latitudeOfNaturalOrigin,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

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

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

1946
        /** @scrutinizer ignore-call */ 
1947
        $toHeight = $this->height->add($geoidUndulation);

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

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

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

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

1949
        $vertical = VerticalPoint::create(/** @scrutinizer ignore-type */ $toHeight, $to->getVertical(), $this->epoch);
Loading history...
1950
1951
        return CompoundPoint::create($horizontal, $vertical, $to, $this->epoch);
1952
    }
1953
1954
    /**
1955
     * General polynomial.
1956
     * @param Coefficient[] $powerCoefficients
1957
     */
1958
    public function generalPolynomial(
1959
        Geographic $to,
1960
        Angle $ordinate1OfEvaluationPointInSourceCRS,
1961
        Angle $ordinate2OfEvaluationPointInSourceCRS,
1962
        Angle $ordinate1OfEvaluationPointInTargetCRS,
1963
        Angle $ordinate2OfEvaluationPointInTargetCRS,
1964
        Scale $scalingFactorForSourceCRSCoordDifferences,
1965
        Scale $scalingFactorForTargetCRSCoordDifferences,
1966
        Scale $A0,
1967
        Scale $B0,
1968
        array $powerCoefficients
1969
    ): self {
1970
        $xs = $this->latitude->getValue();
1971
        $ys = $this->longitude->getValue();
1972
1973
        $t = $this->generalPolynomialUnitless(
1974
            $xs,
1975
            $ys,
1976
            $ordinate1OfEvaluationPointInSourceCRS,
1977
            $ordinate2OfEvaluationPointInSourceCRS,
1978
            $ordinate1OfEvaluationPointInTargetCRS,
1979
            $ordinate2OfEvaluationPointInTargetCRS,
1980
            $scalingFactorForSourceCRSCoordDifferences,
1981
            $scalingFactorForTargetCRSCoordDifferences,
1982
            $A0,
1983
            $B0,
1984
            $powerCoefficients
1985
        );
1986
1987
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
1988
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
1989
            $xtUnit = Angle::EPSG_DEGREE;
1990
        }
1991
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
1992
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
1993
            $ytUnit = Angle::EPSG_DEGREE;
1994
        }
1995
1996
        return static::create(
1997
            Angle::makeUnit($t['xt'], $xtUnit),
1998
            Angle::makeUnit($t['yt'], $ytUnit),
1999
            $this->height,
2000
            $to,
2001
            $this->epoch
2002
        );
2003
    }
2004
2005
    /**
2006
     * Reversible polynomial.
2007
     * @param Coefficient[] $powerCoefficients
2008
     */
2009
    public function reversiblePolynomial(
2010
        Geographic $to,
2011
        Angle $ordinate1OfEvaluationPoint,
2012
        Angle $ordinate2OfEvaluationPoint,
2013
        Scale $scalingFactorForCoordDifferences,
2014
        Scale $A0,
2015
        Scale $B0,
2016
        $powerCoefficients
2017
    ): self {
2018
        $xs = $this->latitude->getValue();
2019
        $ys = $this->longitude->getValue();
2020
2021
        $t = $this->reversiblePolynomialUnitless(
2022
            $xs,
2023
            $ys,
2024
            $ordinate1OfEvaluationPoint,
2025
            $ordinate2OfEvaluationPoint,
2026
            $scalingFactorForCoordDifferences,
2027
            $A0,
2028
            $B0,
2029
            $powerCoefficients
2030
        );
2031
2032
        $xtUnit = $to->getCoordinateSystem()->getAxes()[0]->getUnitOfMeasureId();
2033
        if ($xtUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2034
            $xtUnit = Angle::EPSG_DEGREE;
2035
        }
2036
        $ytUnit = $to->getCoordinateSystem()->getAxes()[1]->getUnitOfMeasureId();
2037
        if ($ytUnit === Angle::EPSG_DEGREE_SUPPLIER_TO_DEFINE_REPRESENTATION) {
2038
            $ytUnit = Angle::EPSG_DEGREE;
2039
        }
2040
2041
        return static::create(
2042
            Angle::makeUnit($t['xt'], $xtUnit),
2043
            Angle::makeUnit($t['yt'], $ytUnit),
2044
            $this->height,
2045
            $to,
2046
            $this->epoch
2047
        );
2048
    }
2049
2050
    /**
2051
     * Axis Order Reversal.
2052
     */
2053
    public function axisReversal(
2054
        Geographic $to
2055
    ): self {
2056
        // axes are read in from the CRS, this is a book-keeping adjustment only
2057
        return static::create($this->latitude, $this->longitude, $this->height, $to, $this->epoch);
2058
    }
2059
2060
    /**
2061
     * Ordnance Survey National Transformation
2062
     * Geodetic transformation between ETRS89 (or WGS 84) and OSGB36 / National Grid.  Uses ETRS89 / National Grid as
2063
     * an intermediate coordinate system for bi-linear interpolation of gridded grid coordinate differences.
2064
     */
2065
    public function OSTN15(
2066
        Projected $to,
2067
        OSTNOSGM15Grid $eastingAndNorthingDifferenceFile
2068
    ): ProjectedPoint {
2069
        $etrs89NationalGrid = new Projected(
2070
            'ETRS89 / National Grid',
2071
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2072
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
2073
            $to->getBoundingArea()
2074
        );
2075
2076
        $projected = $this->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
2077
2078
        return $eastingAndNorthingDifferenceFile->applyForwardAdjustment($projected);
2079
    }
2080
2081
    public function asGeographicValue(): GeographicValue
2082
    {
2083
        return new GeographicValue($this->latitude, $this->longitude, $this->height, $this->crs->getDatum());
2084
    }
2085
2086
    public function asUTMPoint(): UTMPoint
2087
    {
2088
        $hemisphere = $this->getLatitude()->asDegrees()->getValue() >= 0 ? UTMPoint::HEMISPHERE_NORTH : UTMPoint::HEMISPHERE_SOUTH;
2089
        $latitudeOfNaturalOrigin = new Degree(0);
2090
        $initialLongitude = new Degree(-180);
2091
        $scaleFactorAtNaturalOrigin = new Unity(0.9996);
2092
        $falseEasting = new Metre(500000);
2093
        $falseNorthing = $hemisphere === UTMPoint::HEMISPHERE_NORTH ? new Metre(0) : new Metre(10000000);
2094
        $Z = ($this->longitude->subtract($initialLongitude)->asDegrees()->getValue() / 6) % (360 / 6) + 1;
2095
        $longitudeOrigin = $initialLongitude->add(new Degree($Z * 6 - 3));
2096
2097
        $projectedCRS = new Projected(
2098
            'UTM/' . $this->crs->getSRID(),
2099
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
2100
            $this->crs->getDatum(),
2101
            BoundingArea::createWorld() // this is a dummy CRS for the transform only, details don't matter
2102
        );
2103
2104
        $asProjected = $this->transverseMercator($projectedCRS, $latitudeOfNaturalOrigin, $longitudeOrigin, $scaleFactorAtNaturalOrigin, $falseEasting, $falseNorthing);
2105
2106
        return new UTMPoint($asProjected->getEasting(), $asProjected->getNorthing(), $Z, $hemisphere, $this->crs, $this->epoch);
2107
    }
2108
}
2109