Issues (86)

src/Point/GeocentricPoint.php (6 issues)

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

351
        $xAxisTranslation = $xAxisTranslation->add(/** @scrutinizer ignore-type */ $rateOfChangeOfXAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
Loading history...
352 30
        $yAxisTranslation = $yAxisTranslation->add($rateOfChangeOfYAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
353 30
        $zAxisTranslation = $zAxisTranslation->add($rateOfChangeOfZAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
354 30
        $xAxisRotation = $xAxisRotation->add($rateOfChangeOfXAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
0 ignored issues
show
It seems like $rateOfChangeOfXAxisRota...ultiply($yearsToAdjust) can also be of type PHPCoord\UnitOfMeasure\Length\Length and PHPCoord\UnitOfMeasure\Scale\Scale; however, parameter $unit of PHPCoord\UnitOfMeasure\Angle\Angle::add() does only seem to accept PHPCoord\UnitOfMeasure\Angle\Angle, 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

354
        $xAxisRotation = $xAxisRotation->add(/** @scrutinizer ignore-type */ $rateOfChangeOfXAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
Loading history...
355 30
        $yAxisRotation = $yAxisRotation->add($rateOfChangeOfYAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
356 30
        $zAxisRotation = $zAxisRotation->add($rateOfChangeOfZAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
357
        $scaleDifference = $scaleDifference->add($rateOfChangeOfScaleDifference->getChangePerYear()->multiply($yearsToAdjust));
0 ignored issues
show
It seems like $rateOfChangeOfScaleDiff...ultiply($yearsToAdjust) can also be of type PHPCoord\UnitOfMeasure\Angle\Angle and PHPCoord\UnitOfMeasure\Length\Length; however, parameter $unit of PHPCoord\UnitOfMeasure\Scale\Scale::add() does only seem to accept PHPCoord\UnitOfMeasure\Scale\Scale, 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

357
        $scaleDifference = $scaleDifference->add(/** @scrutinizer ignore-type */ $rateOfChangeOfScaleDifference->getChangePerYear()->multiply($yearsToAdjust));
Loading history...
358 30
359
        return $this->coordinateFrameRotation($to, $xAxisTranslation, $yAxisTranslation, $zAxisTranslation, $xAxisRotation, $yAxisRotation, $zAxisRotation, $scaleDifference);
360
    }
361
362
    /**
363
     * Time-dependent Position Vector tfm (geocentric)
364
     * Note the analogy with the Time-dependent Coordinate Frame rotation (code 1056) but beware of the differences!
365
     * The Position Vector convention is used by IAG. See method codes 1054 and 1055 for similar methods operating
366
     * between other CRS types.
367 126
     */
368
    public function timeDependentPositionVectorTransformation(
369
        Geocentric $to,
370
        Length $xAxisTranslation,
371
        Length $yAxisTranslation,
372
        Length $zAxisTranslation,
373
        Angle $xAxisRotation,
374
        Angle $yAxisRotation,
375
        Angle $zAxisRotation,
376
        Scale $scaleDifference,
377
        Rate $rateOfChangeOfXAxisTranslation,
378
        Rate $rateOfChangeOfYAxisTranslation,
379
        Rate $rateOfChangeOfZAxisTranslation,
380
        Rate $rateOfChangeOfXAxisRotation,
381
        Rate $rateOfChangeOfYAxisRotation,
382
        Rate $rateOfChangeOfZAxisRotation,
383
        Rate $rateOfChangeOfScaleDifference,
384
        Time $parameterReferenceEpoch
385
    ): self {
386 126
        // Points use PHP DateTimes for ease of use, but transformations use decimal years...
387 126
        $pointEpoch = Year::fromDateTime($this->epoch ?? new DateTime());
388 126
        $yearsToAdjust = $pointEpoch->subtract($parameterReferenceEpoch)->getValue();
389 126
        $xAxisTranslation = $xAxisTranslation->add($rateOfChangeOfXAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
0 ignored issues
show
It seems like $rateOfChangeOfXAxisTran...ultiply($yearsToAdjust) can also be of type PHPCoord\UnitOfMeasure\Angle\Angle and PHPCoord\UnitOfMeasure\Scale\Scale; however, parameter $unit of PHPCoord\UnitOfMeasure\Length\Length::add() 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

389
        $xAxisTranslation = $xAxisTranslation->add(/** @scrutinizer ignore-type */ $rateOfChangeOfXAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
Loading history...
390 126
        $yAxisTranslation = $yAxisTranslation->add($rateOfChangeOfYAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
391 126
        $zAxisTranslation = $zAxisTranslation->add($rateOfChangeOfZAxisTranslation->getChangePerYear()->multiply($yearsToAdjust));
392 126
        $xAxisRotation = $xAxisRotation->add($rateOfChangeOfXAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
0 ignored issues
show
It seems like $rateOfChangeOfXAxisRota...ultiply($yearsToAdjust) can also be of type PHPCoord\UnitOfMeasure\Length\Length and PHPCoord\UnitOfMeasure\Scale\Scale; however, parameter $unit of PHPCoord\UnitOfMeasure\Angle\Angle::add() does only seem to accept PHPCoord\UnitOfMeasure\Angle\Angle, 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

392
        $xAxisRotation = $xAxisRotation->add(/** @scrutinizer ignore-type */ $rateOfChangeOfXAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
Loading history...
393 126
        $yAxisRotation = $yAxisRotation->add($rateOfChangeOfYAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
394 126
        $zAxisRotation = $zAxisRotation->add($rateOfChangeOfZAxisRotation->getChangePerYear()->multiply($yearsToAdjust));
395
        $scaleDifference = $scaleDifference->add($rateOfChangeOfScaleDifference->getChangePerYear()->multiply($yearsToAdjust));
0 ignored issues
show
It seems like $rateOfChangeOfScaleDiff...ultiply($yearsToAdjust) can also be of type PHPCoord\UnitOfMeasure\Angle\Angle and PHPCoord\UnitOfMeasure\Length\Length; however, parameter $unit of PHPCoord\UnitOfMeasure\Scale\Scale::add() does only seem to accept PHPCoord\UnitOfMeasure\Scale\Scale, 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

395
        $scaleDifference = $scaleDifference->add(/** @scrutinizer ignore-type */ $rateOfChangeOfScaleDifference->getChangePerYear()->multiply($yearsToAdjust));
Loading history...
396 126
397
        return $this->positionVectorTransformation($to, $xAxisTranslation, $yAxisTranslation, $zAxisTranslation, $xAxisRotation, $yAxisRotation, $zAxisRotation, $scaleDifference);
398
    }
399
400
    /**
401
     * Time-specific Coordinate Frame rotation (geocen)
402
     * Note the analogy with the Time-specific Position Vector method (code 1065) but beware of the differences!
403 36
     */
404
    public function timeSpecificCoordinateFrameRotation(
405
        Geocentric $to,
406
        Length $xAxisTranslation,
407
        Length $yAxisTranslation,
408
        Length $zAxisTranslation,
409
        Angle $xAxisRotation,
410
        Angle $yAxisRotation,
411
        Angle $zAxisRotation,
412
        Scale $scaleDifference,
413
        Time $transformationReferenceEpoch
414 36
    ): self {
415 9
        if ($this->epoch === null) {
416
            throw new InvalidCoordinateException(sprintf('This transformation is only valid for epoch %s, none given', $transformationReferenceEpoch->getValue()));
417
        }
418
419 27
        // Points use PHP DateTimes for ease of use, but transformations use decimal years...
420
        $pointEpoch = Year::fromDateTime($this->epoch ?? new DateTime());
421 27
422 9
        if (abs($pointEpoch->getValue() - $transformationReferenceEpoch->getValue()) > 0.001) {
423
            throw new InvalidCoordinateException(sprintf('This transformation is only valid for epoch %s, got %s', $transformationReferenceEpoch, $pointEpoch));
424
        }
425 18
426
        return $this->coordinateFrameRotation($to, $xAxisTranslation, $yAxisTranslation, $zAxisTranslation, $xAxisRotation, $yAxisRotation, $zAxisRotation, $scaleDifference);
427
    }
428
429
    /**
430
     * Time-specific Position Vector transform (geocen)
431
     * Note the analogy with the Time-specifc Coordinate Frame method (code 1066) but beware of the differences!
432 27
     */
433
    public function timeSpecificPositionVectorTransformation(
434
        Geocentric $to,
435
        Length $xAxisTranslation,
436
        Length $yAxisTranslation,
437
        Length $zAxisTranslation,
438
        Angle $xAxisRotation,
439
        Angle $yAxisRotation,
440
        Angle $zAxisRotation,
441
        Scale $scaleDifference,
442
        Time $transformationReferenceEpoch
443 27
    ): self {
444 9
        if ($this->epoch === null) {
445
            throw new InvalidCoordinateException(sprintf('This transformation is only valid for epoch %s, none given', $transformationReferenceEpoch->getValue()));
446
        }
447
448 18
        // Points use PHP DateTimes for ease of use, but transformations use decimal years...
449
        $pointEpoch = Year::fromDateTime($this->epoch);
450 18
451 9
        if (abs($pointEpoch->getValue() - $transformationReferenceEpoch->getValue()) > 0.001) {
452
            throw new InvalidCoordinateException(sprintf('This transformation is only valid for epoch %s, got %s', $transformationReferenceEpoch, $pointEpoch));
453
        }
454 9
455
        return $this->positionVectorTransformation($to, $xAxisTranslation, $yAxisTranslation, $zAxisTranslation, $xAxisRotation, $yAxisRotation, $zAxisRotation, $scaleDifference);
456
    }
457 18
458
    public function asGeographicValue(): GeographicValue
459 18
    {
460
        return (new GeocentricValue($this->x, $this->y, $this->z, $this->getCRS()->getDatum()))->asGeographicValue();
461
    }
462
}
463