Passed
Push — master ( e5f776...37af97 )
by Doug
63:28
created

Point   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 314
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 151
dl 0
loc 314
rs 9.2
c 0
b 0
f 0
wmc 40

9 Methods

Rating   Name   Duplication   Size   Complexity  
A acos() 0 9 3
A asin() 0 9 3
A performOperation() 0 19 4
A sign() 0 7 2
A generalPolynomialUnitless() 0 41 2
A generalPolynomialUnitlessReverse() 0 37 2
A reversiblePolynomialUnitless() 0 36 5
A generalPolynomialUnitlessForward() 0 41 5
C resolveParamsByOperation() 0 31 14

How to fix   Complexity   

Complex Class

Complex classes like Point often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Point, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord\Point;
10
11
use DateTimeImmutable;
12
use PHPCoord\CoordinateOperation\CoordinateOperationMethods;
13
use PHPCoord\CoordinateOperation\CoordinateOperations;
0 ignored issues
show
Bug introduced by
The type PHPCoord\CoordinateOperation\CoordinateOperations 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...
14
use PHPCoord\CoordinateOperation\Grid;
15
use PHPCoord\CoordinateOperation\GridProvider;
16
use PHPCoord\CoordinateReferenceSystem\Compound;
17
use PHPCoord\CoordinateReferenceSystem\CoordinateReferenceSystem;
18
use PHPCoord\CoordinateReferenceSystem\Geocentric;
19
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
20
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
21
use PHPCoord\CoordinateReferenceSystem\Projected;
22
use PHPCoord\CoordinateReferenceSystem\Vertical;
23
use PHPCoord\UnitOfMeasure\Angle\Angle;
24
use PHPCoord\UnitOfMeasure\Length\Length;
25
use PHPCoord\UnitOfMeasure\Scale\Coefficient;
26
use PHPCoord\UnitOfMeasure\Scale\Scale;
27
use PHPCoord\UnitOfMeasure\UnitOfMeasure;
28
use Stringable;
29
30
use function acos;
31
use function asin;
32
use function assert;
33
use function atan;
34
use function atan2;
35
use function class_exists;
36
use function is_string;
37
use function property_exists;
38
use function sscanf;
39
use function str_ends_with;
40
use function str_starts_with;
41
42
abstract class Point implements Stringable
43
{
44
    protected const ITERATION_CONVERGENCE_FORMULA = 1e-10;
45
    protected const ITERATION_CONVERGENCE_GRID = 0.0001;
46
    protected const METHODS_REQUIRING_HORIZONTAL_POINT = [
47
        CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_AND_SLOPE => CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_AND_SLOPE,
48
        CoordinateOperationMethods::EPSG_ZERO_TIDE_HEIGHT_TO_MEAN_TIDE_HEIGHT_EVRF2019 => CoordinateOperationMethods::EPSG_ZERO_TIDE_HEIGHT_TO_MEAN_TIDE_HEIGHT_EVRF2019,
49
        CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_GTX => CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_GTX,
50
        CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_PL_TXT => CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_PL_TXT,
51
        CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_BEV_AT => CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_BEV_AT,
52
        CoordinateOperationMethods::EPSG_VERTICAL_CHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN => CoordinateOperationMethods::EPSG_VERTICAL_CHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN,
53
    ];
54
    protected const METHODS_THAT_REQUIRE_DIRECTION = [
55
        CoordinateOperationMethods::EPSG_SIMILARITY_TRANSFORMATION => CoordinateOperationMethods::EPSG_SIMILARITY_TRANSFORMATION,
56
        CoordinateOperationMethods::EPSG_AFFINE_PARAMETRIC_TRANSFORMATION => CoordinateOperationMethods::EPSG_AFFINE_PARAMETRIC_TRANSFORMATION,
57
        CoordinateOperationMethods::EPSG_NADCON5_2D => CoordinateOperationMethods::EPSG_NADCON5_2D,
58
        CoordinateOperationMethods::EPSG_NADCON5_3D => CoordinateOperationMethods::EPSG_NADCON5_3D,
59
        CoordinateOperationMethods::EPSG_NTV2 => CoordinateOperationMethods::EPSG_NTV2,
60
        CoordinateOperationMethods::EPSG_ZERO_TIDE_HEIGHT_TO_MEAN_TIDE_HEIGHT_EVRF2019 => CoordinateOperationMethods::EPSG_ZERO_TIDE_HEIGHT_TO_MEAN_TIDE_HEIGHT_EVRF2019,
61
        CoordinateOperationMethods::EPSG_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN => CoordinateOperationMethods::EPSG_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN,
62
        CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_GTX => CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_GTX,
63
        CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_PL_TXT => CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_PL_TXT,
64
        CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_BEV_AT => CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_BEV_AT,
65
        CoordinateOperationMethods::EPSG_VERTICAL_CHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN => CoordinateOperationMethods::EPSG_VERTICAL_CHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN,
66
        CoordinateOperationMethods::EPSG_GENERAL_POLYNOMIAL_OF_DEGREE_2 => CoordinateOperationMethods::EPSG_GENERAL_POLYNOMIAL_OF_DEGREE_2,
67
        CoordinateOperationMethods::EPSG_GENERAL_POLYNOMIAL_OF_DEGREE_6 => CoordinateOperationMethods::EPSG_GENERAL_POLYNOMIAL_OF_DEGREE_6,
68
    ];
69
70
    /**
71
     * @var array<class-string, Grid>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string, Grid> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string, Grid>.
Loading history...
72
     */
73
    protected static array $gridCache = [];
74
75
    /**
76
     * @internal
77
     * @param array{horizontalPoint?: Point} $additionalParams
78
     */
79
    public function performOperation(string $srid, Compound|Geocentric|Geographic2D|Geographic3D|Projected|Vertical $to, bool $inReverse, array $additionalParams = []): self
80
    {
81
        $operation = CoordinateOperations::getOperationData($srid);
82
83
        if ($operation['method'] === CoordinateOperationMethods::EPSG_ALIAS) {
84
            $point = clone $this;
85
            assert(property_exists($point, 'crs'));
86
            $point->crs = $to;
0 ignored issues
show
Bug Best Practice introduced by
The property crs does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
87
88
            return $point;
89
        } else {
90
            $method = CoordinateOperationMethods::getFunctionName($operation['method']);
91
            $params = self::resolveParamsByOperation($srid, $operation['method'], $inReverse);
92
93
            if (isset(self::METHODS_REQUIRING_HORIZONTAL_POINT[$operation['method']]) && isset($additionalParams['horizontalPoint'])) {
94
                $params['horizontalPoint'] = $additionalParams['horizontalPoint'];
95
            }
96
97
            return $this->$method($to, ...$params);
98
        }
99
    }
100
101
    /**
102
     * @return array<string, mixed>
103
     */
104
    protected static function resolveParamsByOperation(string $operationSrid, string $methodSrid, bool $inReverse): array
105
    {
106
        $methodData = CoordinateOperationMethods::getMethodData($methodSrid);
107
108
        $params = [];
109
        $powerCoefficients = [];
110
        foreach (CoordinateOperations::getParamData($operationSrid) as $paramName => $paramValue) {
111
            if (str_ends_with($paramName, 'File') && is_string($paramValue) && class_exists($paramValue) && new $paramValue() instanceof GridProvider) {
112
                $params[$paramName] = static::$gridCache[$paramValue] ??= (new $paramValue())->provideGrid();
113
            } else {
114
                if ($inReverse && isset($methodData['paramData'][$paramName]) && $methodData['paramData'][$paramName]['reverses']) {
115
                    $paramValue = $paramValue->multiply(-1);
116
                }
117
                if (str_starts_with($paramName, 'Au') || str_starts_with($paramName, 'Bu')) {
118
                    $powerCoefficients[$paramName] = $paramValue;
119
                } else {
120
                    $params[$paramName] = $paramValue;
121
                }
122
            }
123
        }
124
        if ($powerCoefficients) {
125
            $params['powerCoefficients'] = $powerCoefficients;
126
        }
127
        if (isset(self::METHODS_THAT_REQUIRE_DIRECTION[$methodSrid])) {
128
            if ($methodSrid === CoordinateOperationMethods::EPSG_VERTICAL_CHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN) {
129
                $inReverse = !$inReverse; // has a reversed grid
130
            }
131
            $params['inReverse'] = $inReverse;
132
        }
133
134
        return $params;
135
    }
136
137
    protected static function sign(float $number): int
138
    {
139
        if ($number < 0) {
140
            return -1;
141
        }
142
143
        return 1;
144
    }
145
146
    /**
147
     * General polynomial.
148
     * @param  array<string, Coefficient>  $powerCoefficients
149
     * @return array{xt: float, yt: float}
150
     */
151
    protected function generalPolynomialUnitless(
152
        float $xs,
153
        float $ys,
154
        UnitOfMeasure $ordinate1OfEvaluationPointInSourceCRS,
155
        UnitOfMeasure $ordinate2OfEvaluationPointInSourceCRS,
156
        UnitOfMeasure $ordinate1OfEvaluationPointInTargetCRS,
157
        UnitOfMeasure $ordinate2OfEvaluationPointInTargetCRS,
158
        Scale $scalingFactorForSourceCRSCoordDifferences,
159
        Scale $scalingFactorForTargetCRSCoordDifferences,
160
        Scale $A0,
161
        Scale $B0,
162
        array $powerCoefficients,
163
        bool $inReverse
164
    ): array {
165
        if (!$inReverse) {
166
            return $this->generalPolynomialUnitlessForward(
167
                $xs,
168
                $ys,
169
                $ordinate1OfEvaluationPointInSourceCRS,
170
                $ordinate2OfEvaluationPointInSourceCRS,
171
                $ordinate1OfEvaluationPointInTargetCRS,
172
                $ordinate2OfEvaluationPointInTargetCRS,
173
                $scalingFactorForSourceCRSCoordDifferences,
174
                $scalingFactorForTargetCRSCoordDifferences,
175
                $A0,
176
                $B0,
177
                $powerCoefficients,
178
            );
179
        } else {
180
            return $this->generalPolynomialUnitlessReverse(
181
                $xs,
182
                $ys,
183
                $ordinate1OfEvaluationPointInSourceCRS,
184
                $ordinate2OfEvaluationPointInSourceCRS,
185
                $ordinate1OfEvaluationPointInTargetCRS,
186
                $ordinate2OfEvaluationPointInTargetCRS,
187
                $scalingFactorForSourceCRSCoordDifferences,
188
                $scalingFactorForTargetCRSCoordDifferences,
189
                $A0,
190
                $B0,
191
                $powerCoefficients,
192
            );
193
        }
194
    }
195
196
    protected function generalPolynomialUnitlessForward(
197
        float $xs,
198
        float $ys,
199
        UnitOfMeasure $ordinate1OfEvaluationPointInSourceCRS,
200
        UnitOfMeasure $ordinate2OfEvaluationPointInSourceCRS,
201
        UnitOfMeasure $ordinate1OfEvaluationPointInTargetCRS,
202
        UnitOfMeasure $ordinate2OfEvaluationPointInTargetCRS,
203
        Scale $scalingFactorForSourceCRSCoordDifferences,
204
        Scale $scalingFactorForTargetCRSCoordDifferences,
205
        Scale $A0,
206
        Scale $B0,
207
        array $powerCoefficients
208
    ): array {
209
        $xso = $ordinate1OfEvaluationPointInSourceCRS->getValue();
210
        $yso = $ordinate2OfEvaluationPointInSourceCRS->getValue();
211
        $xto = $ordinate1OfEvaluationPointInTargetCRS->getValue();
212
        $yto = $ordinate2OfEvaluationPointInTargetCRS->getValue();
213
214
        $U = $scalingFactorForSourceCRSCoordDifferences->asUnity()->getValue() * ($xs - $xso);
215
        $V = $scalingFactorForSourceCRSCoordDifferences->asUnity()->getValue() * ($ys - $yso);
216
217
        $mTdX = $A0->getValue();
218
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
219
            if ($coefficientName[0] === 'A') {
220
                sscanf($coefficientName, 'Au%dv%d', $uPower, $vPower);
221
                $mTdX += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
222
            }
223
        }
224
225
        $mTdY = $B0->getValue();
226
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
227
            if ($coefficientName[0] === 'B') {
228
                sscanf($coefficientName, 'Bu%dv%d', $uPower, $vPower);
229
                $mTdY += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
230
            }
231
        }
232
233
        $xt = $xs - $xso + $xto + $mTdX / $scalingFactorForTargetCRSCoordDifferences->asUnity()->getValue();
234
        $yt = $ys - $yso + $yto + $mTdY / $scalingFactorForTargetCRSCoordDifferences->asUnity()->getValue();
235
236
        return ['xt' => $xt, 'yt' => $yt];
237
    }
238
239
    protected function generalPolynomialUnitlessReverse(
240
        float $xs,
241
        float $ys,
242
        UnitOfMeasure $ordinate1OfEvaluationPointInSourceCRS,
243
        UnitOfMeasure $ordinate2OfEvaluationPointInSourceCRS,
244
        UnitOfMeasure $ordinate1OfEvaluationPointInTargetCRS,
245
        UnitOfMeasure $ordinate2OfEvaluationPointInTargetCRS,
246
        Scale $scalingFactorForSourceCRSCoordDifferences,
247
        Scale $scalingFactorForTargetCRSCoordDifferences,
248
        Scale $A0,
249
        Scale $B0,
250
        array $powerCoefficients
251
    ): array {
252
        $result = ['xt' => $xs, 'yt' => $ys];
253
        for ($i = 0; $i <= 15; ++$i) {
254
            $forwardShiftedCoordinates = $this->generalPolynomialUnitlessForward(
255
                $result['xt'],
256
                $result['yt'],
257
                $ordinate1OfEvaluationPointInSourceCRS,
258
                $ordinate2OfEvaluationPointInSourceCRS,
259
                $ordinate1OfEvaluationPointInTargetCRS,
260
                $ordinate2OfEvaluationPointInTargetCRS,
261
                $scalingFactorForSourceCRSCoordDifferences,
262
                $scalingFactorForTargetCRSCoordDifferences,
263
                $A0,
264
                $B0,
265
                $powerCoefficients
266
            );
267
            $deltaError = [
268
                'xt' => $forwardShiftedCoordinates['xt'] - $xs,
269
                'yt' => $forwardShiftedCoordinates['yt'] - $ys,
270
            ];
271
            $result['xt'] -= $deltaError['xt'];
272
            $result['yt'] -= $deltaError['yt'];
273
        }
274
275
        return $result;
276
    }
277
278
    /**
279
     * Reversible polynomial.
280
     * @param  array<string, Coefficient>  $powerCoefficients
281
     * @return array{xt: float, yt: float}
282
     */
283
    protected function reversiblePolynomialUnitless(
284
        float $xs,
285
        float $ys,
286
        Angle $ordinate1OfEvaluationPoint,
287
        Angle $ordinate2OfEvaluationPoint,
288
        Scale $scalingFactorForCoordDifferences,
289
        Scale $A0,
290
        Scale $B0,
291
        array $powerCoefficients
292
    ): array {
293
        $xo = $ordinate1OfEvaluationPoint->getValue();
294
        $yo = $ordinate2OfEvaluationPoint->getValue();
295
296
        $U = $scalingFactorForCoordDifferences->asUnity()->getValue() * ($xs - $xo);
297
        $V = $scalingFactorForCoordDifferences->asUnity()->getValue() * ($ys - $yo);
298
299
        $mTdX = $A0->getValue();
300
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
301
            if ($coefficientName[0] === 'A') {
302
                sscanf($coefficientName, 'Au%dv%d', $uPower, $vPower);
303
                $mTdX += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
304
            }
305
        }
306
307
        $mTdY = $B0->getValue();
308
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
309
            if ($coefficientName[0] === 'B') {
310
                sscanf($coefficientName, 'Bu%dv%d', $uPower, $vPower);
311
                $mTdY += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
312
            }
313
        }
314
315
        $xt = $xs + $mTdX * $scalingFactorForCoordDifferences->asUnity()->getValue();
316
        $yt = $ys + $mTdY * $scalingFactorForCoordDifferences->asUnity()->getValue();
317
318
        return ['xt' => $xt, 'yt' => $yt];
319
    }
320
321
    /**
322
     * Floating point vagaries mean that it's possible for inputs to be e.g. 1.00000000000001 which makes PHP give a
323
     * silent NaN as output so inputs need to be capped. atan/atan2 are not affected, they seem to cap internally.
324
     */
325
    protected static function acos(float $num): float
326
    {
327
        if ($num > 1.0) {
328
            $num = 1.0;
329
        } elseif ($num < -1) {
330
            $num = -1.0;
331
        }
332
333
        return acos($num);
334
    }
335
336
    /**
337
     * Floating point vagaries mean that it's possible for inputs to be e.g. 1.00000000000001 which makes PHP give a
338
     * silent NaN as output so inputs need to be capped. atan/atan2 are not affected, they seem to cap internally.
339
     */
340
    protected static function asin(float $num): float
341
    {
342
        if ($num > 1.0) {
343
            $num = 1.0;
344
        } elseif ($num < -1.0) {
345
            $num = -1.0;
346
        }
347
348
        return asin($num);
349
    }
350
351
    abstract public function getCRS(): CoordinateReferenceSystem;
352
353
    abstract public function getCoordinateEpoch(): ?DateTimeImmutable;
354
355
    abstract public function calculateDistance(self $to): Length;
356
}
357