Passed
Push — master ( 4157e1...9ef2b1 )
by Doug
60:35
created

Point   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 103
dl 0
loc 228
rs 9.52
c 0
b 0
f 0
wmc 36

7 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 5
A reversiblePolynomialUnitless() 0 36 5
C resolveParamsByOperation() 0 31 14
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
    ];
67
68
    /**
69
     * @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...
70
     */
71
    protected static array $gridCache = [];
72
73
    /**
74
     * @internal
75
     * @param array{horizontalPoint?: Point} $additionalParams
76
     */
77
    public function performOperation(string $srid, Compound|Geocentric|Geographic2D|Geographic3D|Projected|Vertical $to, bool $inReverse, array $additionalParams = []): self
78
    {
79
        $operation = CoordinateOperations::getOperationData($srid);
80
81
        if ($operation['method'] === CoordinateOperationMethods::EPSG_ALIAS) {
82
            $point = clone $this;
83
            assert(property_exists($point, 'crs'));
84
            $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...
85
86
            return $point;
87
        } else {
88
            $method = CoordinateOperationMethods::getFunctionName($operation['method']);
89
            $params = self::resolveParamsByOperation($srid, $operation['method'], $inReverse);
90
91
            if (isset(self::METHODS_REQUIRING_HORIZONTAL_POINT[$operation['method']]) && isset($additionalParams['horizontalPoint'])) {
92
                $params['horizontalPoint'] = $additionalParams['horizontalPoint'];
93
            }
94
95
            return $this->$method($to, ...$params);
96
        }
97
    }
98
99
    /**
100
     * @return array<string, mixed>
101
     */
102
    protected static function resolveParamsByOperation(string $operationSrid, string $methodSrid, bool $inReverse): array
103
    {
104
        $methodData = CoordinateOperationMethods::getMethodData($methodSrid);
105
106
        $params = [];
107
        $powerCoefficients = [];
108
        foreach (CoordinateOperations::getParamData($operationSrid) as $paramName => $paramValue) {
109
            if (str_ends_with($paramName, 'File') && is_string($paramValue) && class_exists($paramValue) && new $paramValue() instanceof GridProvider) {
110
                $params[$paramName] = static::$gridCache[$paramValue] ??= (new $paramValue())->provideGrid();
111
            } else {
112
                if ($inReverse && isset($methodData['paramData'][$paramName]) && $methodData['paramData'][$paramName]['reverses']) {
113
                    $paramValue = $paramValue->multiply(-1);
114
                }
115
                if (str_starts_with($paramName, 'Au') || str_starts_with($paramName, 'Bu')) {
116
                    $powerCoefficients[$paramName] = $paramValue;
117
                } else {
118
                    $params[$paramName] = $paramValue;
119
                }
120
            }
121
        }
122
        if ($powerCoefficients) {
123
            $params['powerCoefficients'] = $powerCoefficients;
124
        }
125
        if (isset(self::METHODS_THAT_REQUIRE_DIRECTION[$methodSrid])) {
126
            if ($methodSrid === CoordinateOperationMethods::EPSG_VERTICAL_CHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN) {
127
                $inReverse = !$inReverse; // has a reversed grid
128
            }
129
            $params['inReverse'] = $inReverse;
130
        }
131
132
        return $params;
133
    }
134
135
    protected static function sign(float $number): int
136
    {
137
        if ($number < 0) {
138
            return -1;
139
        }
140
141
        return 1;
142
    }
143
144
    /**
145
     * General polynomial.
146
     * @param  array<string, Coefficient>  $powerCoefficients
147
     * @return array{xt: float, yt: float}
148
     */
149
    protected function generalPolynomialUnitless(
150
        float $xs,
151
        float $ys,
152
        UnitOfMeasure $ordinate1OfEvaluationPointInSourceCRS,
153
        UnitOfMeasure $ordinate2OfEvaluationPointInSourceCRS,
154
        UnitOfMeasure $ordinate1OfEvaluationPointInTargetCRS,
155
        UnitOfMeasure $ordinate2OfEvaluationPointInTargetCRS,
156
        Scale $scalingFactorForSourceCRSCoordDifferences,
157
        Scale $scalingFactorForTargetCRSCoordDifferences,
158
        Scale $A0,
159
        Scale $B0,
160
        array $powerCoefficients
161
    ): array {
162
        $xso = $ordinate1OfEvaluationPointInSourceCRS->getValue();
163
        $yso = $ordinate2OfEvaluationPointInSourceCRS->getValue();
164
        $xto = $ordinate1OfEvaluationPointInTargetCRS->getValue();
165
        $yto = $ordinate2OfEvaluationPointInTargetCRS->getValue();
166
167
        $U = $scalingFactorForSourceCRSCoordDifferences->asUnity()->getValue() * ($xs - $xso);
168
        $V = $scalingFactorForSourceCRSCoordDifferences->asUnity()->getValue() * ($ys - $yso);
169
170
        $mTdX = $A0->getValue();
171
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
172
            if ($coefficientName[0] === 'A') {
173
                sscanf($coefficientName, 'Au%dv%d', $uPower, $vPower);
174
                $mTdX += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
175
            }
176
        }
177
178
        $mTdY = $B0->getValue();
179
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
180
            if ($coefficientName[0] === 'B') {
181
                sscanf($coefficientName, 'Bu%dv%d', $uPower, $vPower);
182
                $mTdY += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
183
            }
184
        }
185
186
        $xt = $xs - $xso + $xto + $mTdX / $scalingFactorForTargetCRSCoordDifferences->asUnity()->getValue();
187
        $yt = $ys - $yso + $yto + $mTdY / $scalingFactorForTargetCRSCoordDifferences->asUnity()->getValue();
188
189
        return ['xt' => $xt, 'yt' => $yt];
190
    }
191
192
    /**
193
     * Reversible polynomial.
194
     * @param  array<string, Coefficient>  $powerCoefficients
195
     * @return array{xt: float, yt: float}
196
     */
197
    protected function reversiblePolynomialUnitless(
198
        float $xs,
199
        float $ys,
200
        Angle $ordinate1OfEvaluationPoint,
201
        Angle $ordinate2OfEvaluationPoint,
202
        Scale $scalingFactorForCoordDifferences,
203
        Scale $A0,
204
        Scale $B0,
205
        array $powerCoefficients
206
    ): array {
207
        $xo = $ordinate1OfEvaluationPoint->getValue();
208
        $yo = $ordinate2OfEvaluationPoint->getValue();
209
210
        $U = $scalingFactorForCoordDifferences->asUnity()->getValue() * ($xs - $xo);
211
        $V = $scalingFactorForCoordDifferences->asUnity()->getValue() * ($ys - $yo);
212
213
        $mTdX = $A0->getValue();
214
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
215
            if ($coefficientName[0] === 'A') {
216
                sscanf($coefficientName, 'Au%dv%d', $uPower, $vPower);
217
                $mTdX += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
218
            }
219
        }
220
221
        $mTdY = $B0->getValue();
222
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
223
            if ($coefficientName[0] === 'B') {
224
                sscanf($coefficientName, 'Bu%dv%d', $uPower, $vPower);
225
                $mTdY += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
226
            }
227
        }
228
229
        $xt = $xs + $mTdX * $scalingFactorForCoordDifferences->asUnity()->getValue();
230
        $yt = $ys + $mTdY * $scalingFactorForCoordDifferences->asUnity()->getValue();
231
232
        return ['xt' => $xt, 'yt' => $yt];
233
    }
234
235
    /**
236
     * Floating point vagaries mean that it's possible for inputs to be e.g. 1.00000000000001 which makes PHP give a
237
     * silent NaN as output so inputs need to be capped. atan/atan2 are not affected, they seem to cap internally.
238
     */
239
    protected static function acos(float $num): float
240
    {
241
        if ($num > 1.0) {
242
            $num = 1.0;
243
        } elseif ($num < -1) {
244
            $num = -1.0;
245
        }
246
247
        return acos($num);
248
    }
249
250
    /**
251
     * Floating point vagaries mean that it's possible for inputs to be e.g. 1.00000000000001 which makes PHP give a
252
     * silent NaN as output so inputs need to be capped. atan/atan2 are not affected, they seem to cap internally.
253
     */
254
    protected static function asin(float $num): float
255
    {
256
        if ($num > 1.0) {
257
            $num = 1.0;
258
        } elseif ($num < -1.0) {
259
            $num = -1.0;
260
        }
261
262
        return asin($num);
263
    }
264
265
    abstract public function getCRS(): CoordinateReferenceSystem;
266
267
    abstract public function getCoordinateEpoch(): ?DateTimeImmutable;
268
269
    abstract public function calculateDistance(self $to): Length;
270
}
271