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

Point::reversiblePolynomialUnitless()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 36
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 17
nc 9
nop 8
dl 0
loc 36
rs 9.3888
c 0
b 0
f 0

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\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