Passed
Push — 4.x ( 2e43a7...18aaa8 )
by Doug
06:37
created

timeSpecificPositionVectorTransformation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 23
ccs 7
cts 7
cp 1
rs 10
cc 3
nc 3
nop 9
crap 3

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use function abs;
12
use DateTime;
13
use DateTimeImmutable;
14
use DateTimeInterface;
15
use PHPCoord\CoordinateOperation\AutoConversion;
16
use PHPCoord\CoordinateOperation\ConvertiblePoint;
17
use PHPCoord\CoordinateOperation\GeocentricValue;
18
use PHPCoord\CoordinateOperation\GeographicValue;
19
use PHPCoord\CoordinateReferenceSystem\Geocentric;
20
use PHPCoord\CoordinateReferenceSystem\Geographic;
21
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
22
use PHPCoord\CoordinateSystem\Axis;
23
use PHPCoord\Exception\InvalidCoordinateException;
24
use PHPCoord\Exception\InvalidCoordinateReferenceSystemException;
25
use PHPCoord\UnitOfMeasure\Angle\Angle;
26
use PHPCoord\UnitOfMeasure\Angle\Radian;
27
use PHPCoord\UnitOfMeasure\Length\Length;
28
use PHPCoord\UnitOfMeasure\Length\Metre;
29
use PHPCoord\UnitOfMeasure\Rate;
30
use PHPCoord\UnitOfMeasure\Scale\Scale;
31
use PHPCoord\UnitOfMeasure\Scale\Unity;
32
use PHPCoord\UnitOfMeasure\Time\Time;
33
use PHPCoord\UnitOfMeasure\Time\Year;
34
use function sprintf;
35
36
/**
37
 * Coordinate representing a point in ECEF geocentric form.
38
 */
39
class GeocentricPoint extends Point implements ConvertiblePoint
40
{
41
    use AutoConversion;
42
43
    /**
44
     * X co-ordinate.
45
     */
46
    protected Length $x;
47
48
    /**
49
     * Y co-ordinate.
50
     */
51
    protected Length $y;
52
53
    /**
54
     * Z co-ordinate.
55
     */
56
    protected Length $z;
57
58
    /**
59
     * Coordinate reference system.
60
     */
61
    protected Geocentric $crs;
62
63
    /**
64
     * Coordinate epoch (date for which the specified coordinates represented this point).
65
     */
66
    protected ?DateTimeImmutable $epoch;
67
68 270
    protected function __construct(Length $x, Length $y, Length $z, Geocentric $crs, ?DateTimeInterface $epoch = null)
69
    {
70 270
        $this->crs = $crs;
71 270
        $this->x = $x::convert($x, $this->crs->getCoordinateSystem()->getAxisByName(Axis::GEOCENTRIC_X)->getUnitOfMeasureId());
72 270
        $this->y = $y::convert($y, $this->crs->getCoordinateSystem()->getAxisByName(Axis::GEOCENTRIC_Y)->getUnitOfMeasureId());
73 270
        $this->z = $z::convert($z, $this->crs->getCoordinateSystem()->getAxisByName(Axis::GEOCENTRIC_Z)->getUnitOfMeasureId());
74
75 270
        if ($epoch instanceof DateTime) {
76 54
            $epoch = DateTimeImmutable::createFromMutable($epoch);
77
        }
78 270
        $this->epoch = $epoch;
79
    }
80
81
    /**
82
     * @param Length $x refer to CRS for preferred unit of measure, but any length unit accepted
83
     * @param Length $y refer to CRS for preferred unit of measure, but any length unit accepted
84
     * @param Length $z refer to CRS for preferred unit of measure, but any length unit accepted
85
     */
86 270
    public static function create(Length $x, Length $y, Length $z, Geocentric $crs, ?DateTimeInterface $epoch = null): self
87
    {
88 270
        return new static($x, $y, $z, $crs, $epoch);
89
    }
90
91 171
    public function getX(): Length
92
    {
93 171
        return $this->x;
94
    }
95
96 171
    public function getY(): Length
97
    {
98 171
        return $this->y;
99
    }
100
101 171
    public function getZ(): Length
102
    {
103 171
        return $this->z;
104
    }
105
106 63
    public function getCRS(): Geocentric
107
    {
108 63
        return $this->crs;
109
    }
110
111 45
    public function getCoordinateEpoch(): ?DateTimeImmutable
112
    {
113 45
        return $this->epoch;
114
    }
115
116
    /**
117
     * Calculate surface distance between two points.
118
     */
119 18
    public function calculateDistance(Point $to): Length
120
    {
121
        try {
122 18
            if ($to instanceof ConvertiblePoint) {
123 18
                $to = $to->convert($this->crs);
124
            }
125
        } finally {
126 18
            if ($to->getCRS()->getSRID() !== $this->crs->getSRID()) {
127 9
                throw new InvalidCoordinateReferenceSystemException('Can only calculate distances between two points in the same CRS');
128
            }
129
130
            /* @var GeocentricPoint $to */
131 9
            return static::vincenty($this->asGeographicValue(), $to->asGeographicValue(), $this->getCRS()->getDatum()->getEllipsoid());
132
        }
133
    }
134
135 27
    public function __toString(): string
136
    {
137 27
        return "({$this->x}, {$this->y}, {$this->z})";
138
    }
139
140
    /**
141
     * Geographic/geocentric conversions
142
     * In applications it is often concatenated with the 3- 7- or 10-parameter transformations 9603, 9606, 9607 or
143
     * 9636 to form a geographic to geographic transformation.
144
     */
145 36
    public function geographicGeocentric(
146
        Geographic $to
147
    ): GeographicPoint {
148 36
        $geocentricValue = new GeocentricValue($this->x, $this->y, $this->z, $to->getDatum());
149 36
        $asGeographic = $geocentricValue->asGeographicValue();
150
151 36
        return GeographicPoint::create($asGeographic->getLatitude(), $asGeographic->getLongitude(), $to instanceof Geographic3D ? $asGeographic->getHeight() : null, $to, $this->epoch);
152
    }
153
154
    /**
155
     * Coordinate Frame rotation (geocentric domain)
156
     * This method is a specific case of the Molodensky-Badekas (CF) method (code 1034) in which the evaluation point
157
     * is at the geocentre with coordinate values of zero. Note the analogy with the Position Vector method (code 1033)
158
     * but beware of the differences!
159
     */
160 54
    public function coordinateFrameRotation(
161
        Geocentric $to,
162
        Length $xAxisTranslation,
163
        Length $yAxisTranslation,
164
        Length $zAxisTranslation,
165
        Angle $xAxisRotation,
166
        Angle $yAxisRotation,
167
        Angle $zAxisRotation,
168
        Scale $scaleDifference
169
    ): self {
170 54
        return $this->coordinateFrameMolodenskyBadekas(
171 54
            $to,
172 54
            $xAxisTranslation,
173 54
            $yAxisTranslation,
174 54
            $zAxisTranslation,
175 54
            $xAxisRotation,
176 54
            $yAxisRotation,
177 54
            $zAxisRotation,
178 54
            $scaleDifference,
179 54
            new Metre(0),
180 54
            new Metre(0),
181 54
            new Metre(0)
182 54
        );
183
    }
184
185
    /**
186
     * Molodensky-Badekas (CF geocentric domain)
187
     * See method codes 1039 and 9636 for this operation in other coordinate domains and method code 1061 for opposite
188
     * rotation convention in geocentric domain.
189
     */
190 63
    public function coordinateFrameMolodenskyBadekas(
191
        Geocentric $to,
192
        Length $xAxisTranslation,
193
        Length $yAxisTranslation,
194
        Length $zAxisTranslation,
195
        Angle $xAxisRotation,
196
        Angle $yAxisRotation,
197
        Angle $zAxisRotation,
198
        Scale $scaleDifference,
199
        Length $ordinate1OfEvaluationPoint,
200
        Length $ordinate2OfEvaluationPoint,
201
        Length $ordinate3OfEvaluationPoint
202
    ): self {
203 63
        $xs = $this->x->asMetres()->getValue();
204 63
        $ys = $this->y->asMetres()->getValue();
205 63
        $zs = $this->z->asMetres()->getValue();
206 63
        $tx = $xAxisTranslation->asMetres()->getValue();
207 63
        $ty = $yAxisTranslation->asMetres()->getValue();
208 63
        $tz = $zAxisTranslation->asMetres()->getValue();
209 63
        $rx = $xAxisRotation->asRadians()->getValue();
210 63
        $ry = $yAxisRotation->asRadians()->getValue();
211 63
        $rz = $zAxisRotation->asRadians()->getValue();
212 63
        $M = 1 + $scaleDifference->asUnity()->getValue();
213 63
        $xp = $ordinate1OfEvaluationPoint->asMetres()->getValue();
214 63
        $yp = $ordinate2OfEvaluationPoint->asMetres()->getValue();
215 63
        $zp = $ordinate3OfEvaluationPoint->asMetres()->getValue();
216
217 63
        $xt = $M * ((($xs - $xp) * 1) + (($ys - $yp) * $rz) + (($zs - $zp) * -$ry)) + $tx + $xp;
218 63
        $yt = $M * ((($xs - $xp) * -$rz) + (($ys - $yp) * 1) + (($zs - $zp) * $rx)) + $ty + $yp;
219 63
        $zt = $M * ((($xs - $xp) * $ry) + (($ys - $yp) * -$rx) + (($zs - $zp) * 1)) + $tz + $zp;
220
221 63
        return static::create(new Metre($xt), new Metre($yt), new Metre($zt), $to, $this->epoch);
222
    }
223
224
    /**
225
     * Position Vector transformation (geocentric domain)
226
     * This method is a specific case of the Molodensky-Badekas (PV) method (code 1061) in which the evaluation point
227
     * is the geocentre with coordinate values of zero. Note the analogy with the Coordinate Frame method (code 1032)
228
     * but beware of the differences!
229
     */
230 54
    public function positionVectorTransformation(
231
        Geocentric $to,
232
        Length $xAxisTranslation,
233
        Length $yAxisTranslation,
234
        Length $zAxisTranslation,
235
        Angle $xAxisRotation,
236
        Angle $yAxisRotation,
237
        Angle $zAxisRotation,
238
        Scale $scaleDifference
239
    ): self {
240 54
        return $this->positionVectorMolodenskyBadekas(
241 54
            $to,
242 54
            $xAxisTranslation,
243 54
            $yAxisTranslation,
244 54
            $zAxisTranslation,
245 54
            $xAxisRotation,
246 54
            $yAxisRotation,
247 54
            $zAxisRotation,
248 54
            $scaleDifference,
249 54
            new Metre(0),
250 54
            new Metre(0),
251 54
            new Metre(0)
252 54
        );
253
    }
254
255
    /**
256
     * Molodensky-Badekas (PV geocentric domain)
257
     * See method codes 1062 and 1063 for this operation in other coordinate domains and method code 1034 for opposite
258
     * rotation convention in geocentric domain.
259
     */
260 63
    public function positionVectorMolodenskyBadekas(
261
        Geocentric $to,
262
        Length $xAxisTranslation,
263
        Length $yAxisTranslation,
264
        Length $zAxisTranslation,
265
        Angle $xAxisRotation,
266
        Angle $yAxisRotation,
267
        Angle $zAxisRotation,
268
        Scale $scaleDifference,
269
        Length $ordinate1OfEvaluationPoint,
270
        Length $ordinate2OfEvaluationPoint,
271
        Length $ordinate3OfEvaluationPoint
272
    ): self {
273 63
        $xs = $this->x->asMetres()->getValue();
274 63
        $ys = $this->y->asMetres()->getValue();
275 63
        $zs = $this->z->asMetres()->getValue();
276 63
        $tx = $xAxisTranslation->asMetres()->getValue();
277 63
        $ty = $yAxisTranslation->asMetres()->getValue();
278 63
        $tz = $zAxisTranslation->asMetres()->getValue();
279 63
        $rx = $xAxisRotation->asRadians()->getValue();
280 63
        $ry = $yAxisRotation->asRadians()->getValue();
281 63
        $rz = $zAxisRotation->asRadians()->getValue();
282 63
        $M = 1 + $scaleDifference->asUnity()->getValue();
283 63
        $xp = $ordinate1OfEvaluationPoint->asMetres()->getValue();
284 63
        $yp = $ordinate2OfEvaluationPoint->asMetres()->getValue();
285 63
        $zp = $ordinate3OfEvaluationPoint->asMetres()->getValue();
286
287 63
        $xt = $M * ((($xs - $xp) * 1) + (($ys - $yp) * -$rz) + (($zs - $zp) * $ry)) + $tx + $xp;
288 63
        $yt = $M * ((($xs - $xp) * $rz) + (($ys - $yp) * 1) + (($zs - $zp) * -$rx)) + $ty + $yp;
289 63
        $zt = $M * ((($xs - $xp) * -$ry) + (($ys - $yp) * $rx) + (($zs - $zp) * 1)) + $tz + $zp;
290
291 63
        return static::create(new Metre($xt), new Metre($yt), new Metre($zt), $to, $this->epoch);
292
    }
293
294
    /**
295
     * Geocentric translations
296
     * This method allows calculation of geocentric coords in the target system by adding the parameter values to the
297
     * corresponding coordinates of the point in the source system. See methods 1035 and 9603 for similar tfms
298
     * operating between other CRSs types.
299
     */
300 9
    public function geocentricTranslation(
301
        Geocentric $to,
302
        Length $xAxisTranslation,
303
        Length $yAxisTranslation,
304
        Length $zAxisTranslation
305
    ): self {
306 9
        return $this->positionVectorTransformation(
307 9
            $to,
308 9
            $xAxisTranslation,
309 9
            $yAxisTranslation,
310 9
            $zAxisTranslation,
311 9
            new Radian(0),
312 9
            new Radian(0),
313 9
            new Radian(0),
314 9
            new Unity(0)
315 9
        );
316
    }
317
318
    /**
319
     * Time-dependent Coordinate Frame rotation (geocen)
320
     * Note the analogy with the Time-dependent Position Vector transformation (code 1053) but beware of the
321
     * differences!  The Position Vector convention is used by IAG. See method codes 1057 and 1058 for similar methods
322
     * operating between other CRS types.
323
     */
324 18
    public function timeDependentCoordinateFrameRotation(
325
        Geocentric $to,
326
        Length $xAxisTranslation,
327
        Length $yAxisTranslation,
328
        Length $zAxisTranslation,
329
        Angle $xAxisRotation,
330
        Angle $yAxisRotation,
331
        Angle $zAxisRotation,
332
        Scale $scaleDifference,
333
        Rate $rateOfChangeOfXAxisTranslation,
334
        Rate $rateOfChangeOfYAxisTranslation,
335
        Rate $rateOfChangeOfZAxisTranslation,
336
        Rate $rateOfChangeOfXAxisRotation,
337
        Rate $rateOfChangeOfYAxisRotation,
338
        Rate $rateOfChangeOfZAxisRotation,
339
        Rate $rateOfChangeOfScaleDifference,
340
        Time $parameterReferenceEpoch
341
    ): self {
342 18
        if ($this->epoch === null) {
343 9
            throw new InvalidCoordinateException('This transformation requires an epoch, none given');
344
        }
345
346
        // Points use PHP DateTimes for ease of use, but transformations use decimal years...
347 9
        $pointEpoch = Year::fromDateTime($this->epoch);
348 9
        $yearsToAdjust = $pointEpoch->subtract($parameterReferenceEpoch)->getValue();
349 9
        $xAxisTranslation = $xAxisTranslation->add($rateOfChangeOfXAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
0 ignored issues
show
Bug introduced by
The method multiply() does not exist on PHPCoord\UnitOfMeasure\UnitOfMeasure. Since it exists in all sub-types, consider adding an abstract or default implementation to PHPCoord\UnitOfMeasure\UnitOfMeasure. ( Ignorable by Annotation )

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

349
        $xAxisTranslation = $xAxisTranslation->add($rateOfChangeOfXAxisTranslation->getChangePerYear()->/** @scrutinizer ignore-call */ multiply($yearsToAdjust));
Loading history...
350 9
        $yAxisTranslation = $yAxisTranslation->add($rateOfChangeOfYAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
351 9
        $zAxisTranslation = $zAxisTranslation->add($rateOfChangeOfZAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
352 9
        $xAxisRotation = $xAxisRotation->add($rateOfChangeOfXAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
353 9
        $yAxisRotation = $yAxisRotation->add($rateOfChangeOfYAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
354 9
        $zAxisRotation = $zAxisRotation->add($rateOfChangeOfZAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
355 9
        $scaleDifference = $scaleDifference->add($rateOfChangeOfScaleDifference->getChangePerYear()->multiply($yearsToAdjust));
356
357 9
        return $this->coordinateFrameRotation($to, $xAxisTranslation, $yAxisTranslation, $zAxisTranslation, $xAxisRotation, $yAxisRotation, $zAxisRotation, $scaleDifference);
358
    }
359
360
    /**
361
     * Time-dependent Position Vector tfm (geocentric)
362
     * Note the analogy with the Time-dependent Coordinate Frame rotation (code 1056) but beware of the differences!
363
     * The Position Vector convention is used by IAG. See method codes 1054 and 1055 for similar methods operating
364
     * between other CRS types.
365
     */
366 18
    public function timeDependentPositionVectorTransformation(
367
        Geocentric $to,
368
        Length $xAxisTranslation,
369
        Length $yAxisTranslation,
370
        Length $zAxisTranslation,
371
        Angle $xAxisRotation,
372
        Angle $yAxisRotation,
373
        Angle $zAxisRotation,
374
        Scale $scaleDifference,
375
        Rate $rateOfChangeOfXAxisTranslation,
376
        Rate $rateOfChangeOfYAxisTranslation,
377
        Rate $rateOfChangeOfZAxisTranslation,
378
        Rate $rateOfChangeOfXAxisRotation,
379
        Rate $rateOfChangeOfYAxisRotation,
380
        Rate $rateOfChangeOfZAxisRotation,
381
        Rate $rateOfChangeOfScaleDifference,
382
        Time $parameterReferenceEpoch
383
    ): self {
384 18
        if ($this->epoch === null) {
385 9
            throw new InvalidCoordinateException('This transformation requires an epoch, none given');
386
        }
387
388
        // Points use PHP DateTimes for ease of use, but transformations use decimal years...
389 9
        $pointEpoch = Year::fromDateTime($this->epoch);
390 9
        $yearsToAdjust = $pointEpoch->subtract($parameterReferenceEpoch)->getValue();
391 9
        $xAxisTranslation = $xAxisTranslation->add($rateOfChangeOfXAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
392 9
        $yAxisTranslation = $yAxisTranslation->add($rateOfChangeOfYAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
393 9
        $zAxisTranslation = $zAxisTranslation->add($rateOfChangeOfZAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
394 9
        $xAxisRotation = $xAxisRotation->add($rateOfChangeOfXAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
395 9
        $yAxisRotation = $yAxisRotation->add($rateOfChangeOfYAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
396 9
        $zAxisRotation = $zAxisRotation->add($rateOfChangeOfZAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
397 9
        $scaleDifference = $scaleDifference->add($rateOfChangeOfScaleDifference->getChangePerYear()->multiply($yearsToAdjust));
398
399 9
        return $this->positionVectorTransformation($to, $xAxisTranslation, $yAxisTranslation, $zAxisTranslation, $xAxisRotation, $yAxisRotation, $zAxisRotation, $scaleDifference);
400
    }
401
402
    /**
403
     * Time-specific Coordinate Frame rotation (geocen)
404
     * Note the analogy with the Time-specific Position Vector method (code 1065) but beware of the differences!
405
     */
406 36
    public function timeSpecificCoordinateFrameRotation(
407
        Geocentric $to,
408
        Length $xAxisTranslation,
409
        Length $yAxisTranslation,
410
        Length $zAxisTranslation,
411
        Angle $xAxisRotation,
412
        Angle $yAxisRotation,
413
        Angle $zAxisRotation,
414
        Scale $scaleDifference,
415
        Time $transformationReferenceEpoch
416
    ): self {
417 36
        if ($this->epoch === null) {
418 9
            throw new InvalidCoordinateException(sprintf('This transformation is only valid for epoch %s, none given', $transformationReferenceEpoch->getValue()));
419
        }
420
421
        // Points use PHP DateTimes for ease of use, but transformations use decimal years...
422 27
        $pointEpoch = Year::fromDateTime($this->epoch);
423
424 27
        if (abs($pointEpoch->getValue() - $transformationReferenceEpoch->getValue()) > 0.001) {
425 9
            throw new InvalidCoordinateException(sprintf('This transformation is only valid for epoch %s, got %s', $transformationReferenceEpoch, $pointEpoch));
426
        }
427
428 18
        return $this->coordinateFrameRotation($to, $xAxisTranslation, $yAxisTranslation, $zAxisTranslation, $xAxisRotation, $yAxisRotation, $zAxisRotation, $scaleDifference);
429
    }
430
431
    /**
432
     * Time-specific Position Vector transform (geocen)
433
     * Note the analogy with the Time-specifc Coordinate Frame method (code 1066) but beware of the differences!
434
     */
435 27
    public function timeSpecificPositionVectorTransformation(
436
        Geocentric $to,
437
        Length $xAxisTranslation,
438
        Length $yAxisTranslation,
439
        Length $zAxisTranslation,
440
        Angle $xAxisRotation,
441
        Angle $yAxisRotation,
442
        Angle $zAxisRotation,
443
        Scale $scaleDifference,
444
        Time $transformationReferenceEpoch
445
    ): self {
446 27
        if ($this->epoch === null) {
447 9
            throw new InvalidCoordinateException(sprintf('This transformation is only valid for epoch %s, none given', $transformationReferenceEpoch->getValue()));
448
        }
449
450
        // Points use PHP DateTimes for ease of use, but transformations use decimal years...
451 18
        $pointEpoch = Year::fromDateTime($this->epoch);
452
453 18
        if (abs($pointEpoch->getValue() - $transformationReferenceEpoch->getValue()) > 0.001) {
454 9
            throw new InvalidCoordinateException(sprintf('This transformation is only valid for epoch %s, got %s', $transformationReferenceEpoch, $pointEpoch));
455
        }
456
457 9
        return $this->positionVectorTransformation($to, $xAxisTranslation, $yAxisTranslation, $zAxisTranslation, $xAxisRotation, $yAxisRotation, $zAxisRotation, $scaleDifference);
458
    }
459
460 18
    public function asGeographicValue(): GeographicValue
461
    {
462 18
        return (new GeocentricValue($this->x, $this->y, $this->z, $this->getCRS()->getDatum()))->asGeographicValue();
463
    }
464
}
465