Passed
Push — master ( f00c65...31a7ea )
by Doug
61:30
created

Point::reversiblePolynomialUnitless()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 36
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 5

Importance

Changes 0
Metric Value
eloc 17
c 0
b 0
f 0
dl 0
loc 36
ccs 18
cts 18
cp 1
rs 9.3888
cc 5
nc 9
nop 8
crap 5

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 function acos;
13
use function asin;
14
use function atan;
15
use function atan2;
16
use function cos;
17
use DateTimeImmutable;
18
use const M_PI;
19
use PHPCoord\CoordinateOperation\CoordinateOperationMethods;
20
use PHPCoord\CoordinateOperation\CoordinateOperationParams;
0 ignored issues
show
Bug introduced by
The type PHPCoord\CoordinateOpera...ordinateOperationParams 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...
21
use PHPCoord\CoordinateOperation\CoordinateOperations;
22
use PHPCoord\CoordinateOperation\GeographicValue;
23
use PHPCoord\CoordinateReferenceSystem\CoordinateReferenceSystem;
24
use PHPCoord\Datum\Ellipsoid;
25
use PHPCoord\UnitOfMeasure\Angle\Angle;
26
use PHPCoord\UnitOfMeasure\Length\Length;
27
use PHPCoord\UnitOfMeasure\Length\Metre;
28
use PHPCoord\UnitOfMeasure\Scale\Coefficient;
29
use PHPCoord\UnitOfMeasure\Scale\Scale;
30
use PHPCoord\UnitOfMeasure\UnitOfMeasure;
31
use PHPCoord\UnitOfMeasure\UnitOfMeasureFactory;
32
use function sin;
33
use function sqrt;
34
use function sscanf;
35
use function str_starts_with;
36
use Stringable;
37
use function tan;
38
39
abstract class Point implements Stringable
40
{
41
    protected const ITERATION_CONVERGENCE_FORMULA = 1e-10;
42
    protected const ITERATION_CONVERGENCE_GRID = 0.0001;
43
    protected const METHODS_REQUIRING_HORIZONTAL_POINT = [
44
        CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_AND_SLOPE => CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_AND_SLOPE,
45
        CoordinateOperationMethods::EPSG_ZERO_TIDE_HEIGHT_TO_MEAN_TIDE_HEIGHT_EVRF2019 => CoordinateOperationMethods::EPSG_ZERO_TIDE_HEIGHT_TO_MEAN_TIDE_HEIGHT_EVRF2019,
46
        CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_GTX => CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_GTX,
47
    ];
48
    protected const METHODS_THAT_REQUIRE_DIRECTION = [
49
        CoordinateOperationMethods::EPSG_SIMILARITY_TRANSFORMATION => CoordinateOperationMethods::EPSG_SIMILARITY_TRANSFORMATION,
50
        CoordinateOperationMethods::EPSG_AFFINE_PARAMETRIC_TRANSFORMATION => CoordinateOperationMethods::EPSG_AFFINE_PARAMETRIC_TRANSFORMATION,
51
        CoordinateOperationMethods::EPSG_NADCON5_2D => CoordinateOperationMethods::EPSG_NADCON5_2D,
52
        CoordinateOperationMethods::EPSG_NADCON5_3D => CoordinateOperationMethods::EPSG_NADCON5_3D,
53
        CoordinateOperationMethods::EPSG_NTV2 => CoordinateOperationMethods::EPSG_NTV2,
54
        CoordinateOperationMethods::EPSG_ZERO_TIDE_HEIGHT_TO_MEAN_TIDE_HEIGHT_EVRF2019 => CoordinateOperationMethods::EPSG_ZERO_TIDE_HEIGHT_TO_MEAN_TIDE_HEIGHT_EVRF2019,
55
        CoordinateOperationMethods::EPSG_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN => CoordinateOperationMethods::EPSG_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN,
56
        CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_GTX => CoordinateOperationMethods::EPSG_VERTICAL_OFFSET_BY_GRID_INTERPOLATION_GTX,
57
    ];
58
59
    /**
60
     * @internal
61
     */
62
    public function performOperation(string $srid, CoordinateReferenceSystem $to, bool $inReverse, array $additionalParams = []): self
63
    {
64
        $operation = CoordinateOperations::getOperationData($srid);
65
        $method = CoordinateOperationMethods::getFunctionName($operation['method']);
66
        $params = self::resolveParamsByOperation($srid, $operation['method'], $inReverse);
67
68
        if (isset(self::METHODS_REQUIRING_HORIZONTAL_POINT[$operation['method']])) {
69 254
            $params['horizontalPoint'] = $additionalParams['horizontalPoint'];
70
        }
71 254
72
        return $this->$method($to, ...$params);
73 254
    }
74 254
75 254
    protected static function resolveParamsByOperation(string $operationSrid, string $methodSrid, bool $inReverse): array
76
    {
77 254
        $params = [];
78 9
        $powerCoefficients = [];
79
        foreach (CoordinateOperationParams::getParamData($operationSrid) as $paramName => $paramData) {
80
            if (isset($paramData['fileProvider'])) {
81 254
                $params[$paramName] = (new $paramData['fileProvider']())->provideGrid();
82 254
            } else {
83
                if ($inReverse && $paramData['reverses']) {
84
                    $paramData['value'] *= -1;
85
                }
86
                if ($paramData['uom']) {
87 254
                    $param = UnitOfMeasureFactory::makeUnit($paramData['value'], $paramData['uom']);
88
                } else {
89
                    $param = $paramData['value'];
90
                }
91
                if (str_starts_with($paramName, 'Au') || str_starts_with($paramName, 'Bu')) {
92
                    $powerCoefficients[$paramName] = $param;
93
                } else {
94
                    $params[$paramName] = $param;
95
                }
96
            }
97
        }
98
        if ($powerCoefficients) {
99
            $params['powerCoefficients'] = $powerCoefficients;
100
        }
101
        if (isset(self::METHODS_THAT_REQUIRE_DIRECTION[$methodSrid])) {
102
            $params['inReverse'] = $inReverse;
103 254
        }
104
105 254
        return $params;
106 254
    }
107 254
108 254
    protected static function sign(float $number): int
109 7
    {
110
        if ($number < 0) {
111 254
            return -1;
112 68
        }
113
114 254
        return 1;
115 254
    }
116
117 10
    /**
118
     * Calculate surface distance between two points.
119 254
     */
120 27
    protected static function vincenty(GeographicValue $from, GeographicValue $to, Ellipsoid $ellipsoid): Length
121
    {
122 254
        $a = $ellipsoid->getSemiMajorAxis()->asMetres()->getValue();
123
        $b = $ellipsoid->getSemiMinorAxis()->asMetres()->getValue();
124
        $f = $ellipsoid->getInverseFlattening();
125
        $U1 = atan((1 - $f) * tan($from->getLatitude()->asRadians()->getValue()));
126 254
        $U2 = atan((1 - $f) * tan($to->getLatitude()->asRadians()->getValue()));
127 27
        $L = $to->getLongitude()->subtract($from->getLongitude())->asRadians()->getValue();
128
129 254
        $lambda = $L;
130 3
        do {
131
            $lambdaN = $lambda;
132
133 254
            $sinSigma = sqrt((cos($U2) * sin($lambda)) ** 2 + (cos($U1) * sin($U2) - sin($U1) * cos($U2) * cos($lambda)) ** 2);
134
            $cosSigma = sin($U1) * sin($U2) + cos($U1) * cos($U2) * cos($lambda);
135
            $sigma = atan2($sinSigma, $cosSigma);
136
137
            $sinAlpha = cos($U1) * cos($U2) * sin($lambda) / $sinSigma;
138
            $cosSqAlpha = (1 - $sinAlpha ** 2);
139
            $cos2SigmaM = $cosSqAlpha ? $cosSigma - (2 * sin($U1) * sin($U2) / $cosSqAlpha) : 0;
140
            $C = $f / 16 * $cosSqAlpha * (4 + $f * (4 - 3 * $cosSqAlpha));
141
            $lambda = $L + (1 - $C) * $f * $sinAlpha * ($sigma + $C * $sinSigma * ($cos2SigmaM + $C * $cosSigma * (-1 + 2 * $cos2SigmaM ** 2)));
142
        } while (abs($lambda - $lambdaN) >= static::ITERATION_CONVERGENCE_FORMULA && abs($lambda) < M_PI);
143
144
        // Antipodal case
145
        if (abs($lambda) >= M_PI) {
146
            if ($L >= 0) {
147
                $LPrime = M_PI - $L;
148
            } else {
149
                $LPrime = -M_PI - $L;
150 90
            }
151
152 90
            $lambdaPrime = 0;
153
            $sigma = M_PI - abs($U1 + $U2);
154
            $sinSigma = sin($sigma);
155
            $cosSqAlpha = 0.5;
156 90
            $sinAlpha = 0;
157
158
            do {
159
                $sinAlphaN = $sinAlpha;
160
161
                $C = $f / 16 * $cosSqAlpha * (4 + $f * (4 - 3 * $cosSqAlpha));
162 153
                $cos2SigmaM = cos($sigma) - 2 * sin($U1) * sin($U2) / $cosSqAlpha;
163
                $D = (1 - $C) * $f * ($sigma + $C * $sinSigma * ($cos2SigmaM + $C * cos($sigma) * (-1 + 2 * $cos2SigmaM ** 2)));
164 153
                $sinAlpha = ($LPrime - $lambdaPrime) / $D;
165 153
                $cosSqAlpha = (1 - $sinAlpha ** 2);
166 153
                $sinLambdaPrime = ($sinAlpha * $sinSigma) / (cos($U1) * cos($U2));
167 153
                $lambdaPrime = self::asin($sinLambdaPrime);
168 153
                $sinSqSigma = (cos($U2) * $sinLambdaPrime) ** 2 + (cos($U1) * sin($U2) + sin($U1) * cos($U2) * cos($lambdaPrime)) ** 2;
169 153
                $sinSigma = sqrt($sinSqSigma);
170
            } while (abs($sinAlpha - $sinAlphaN) >= static::ITERATION_CONVERGENCE_FORMULA);
171 153
        }
172
173 153
        $E = sqrt(1 + (($a ** 2 - $b ** 2) / $b ** 2) * $cosSqAlpha);
174
        $F = ($E - 1) / ($E + 1);
175 153
176 153
        $A = (1 + $F ** 2 / 4) / (1 - $F);
177 153
        $B = $F * (1 - 3 / 8 * $F ** 2);
178
179 153
        $deltaSigma = $B * $sinSigma * ($cos2SigmaM + $B / 4 * ($cosSigma * (-1 + 2 * $cos2SigmaM ** 2) - $B / 6 * $cos2SigmaM * (-3 + 4 * $sinSigma ** 2) * (-3 + 4 * $cos2SigmaM ** 2)));
180 153
181 153
        return new Metre($b * $A * ($sigma - $deltaSigma));
182 153
    }
183 153
184 153
    /**
185
     * General polynomial.
186
     * @param Coefficient[] $powerCoefficients
187 153
     */
188 27
    protected function generalPolynomialUnitless(
189 27
        float $xs,
190
        float $ys,
191
        UnitOfMeasure $ordinate1OfEvaluationPointInSourceCRS,
192
        UnitOfMeasure $ordinate2OfEvaluationPointInSourceCRS,
193
        UnitOfMeasure $ordinate1OfEvaluationPointInTargetCRS,
194 27
        UnitOfMeasure $ordinate2OfEvaluationPointInTargetCRS,
195 27
        Scale $scalingFactorForSourceCRSCoordDifferences,
196 27
        Scale $scalingFactorForTargetCRSCoordDifferences,
197 27
        Scale $A0,
198 27
        Scale $B0,
199
        array $powerCoefficients
200
    ): array {
201 27
        $xso = $ordinate1OfEvaluationPointInSourceCRS->getValue();
202
        $yso = $ordinate2OfEvaluationPointInSourceCRS->getValue();
203 27
        $xto = $ordinate1OfEvaluationPointInTargetCRS->getValue();
204 27
        $yto = $ordinate2OfEvaluationPointInTargetCRS->getValue();
205 27
206 27
        $U = $scalingFactorForSourceCRSCoordDifferences->asUnity()->getValue() * ($xs - $xso);
207 27
        $V = $scalingFactorForSourceCRSCoordDifferences->asUnity()->getValue() * ($ys - $yso);
208 27
209 27
        $mTdX = $A0->getValue();
210 27
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
211 27
            if ($coefficientName[0] === 'A') {
212 27
                sscanf($coefficientName, 'Au%dv%d', $uPower, $vPower);
1 ignored issue
show
Comprehensibility Best Practice introduced by
The variable $vPower seems to be never defined.
Loading history...
213
                $mTdX += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
214
            }
215 153
        }
216 153
217
        $mTdY = $B0->getValue();
218 153
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
219 153
            if ($coefficientName[0] === 'B') {
220
                sscanf($coefficientName, 'Bu%dv%d', $uPower, $vPower);
221 153
                $mTdY += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
222
            }
223 153
        }
224
225
        $xt = $xs - $xso + $xto + $mTdX / $scalingFactorForTargetCRSCoordDifferences->asUnity()->getValue();
226
        $yt = $ys - $yso + $yto + $mTdY / $scalingFactorForTargetCRSCoordDifferences->asUnity()->getValue();
227
228
        return ['xt' => $xt, 'yt' => $yt];
229
    }
230 18
231
    /**
232
     * Reversible polynomial.
233
     */
234
    protected function reversiblePolynomialUnitless(
235
        float $xs,
236
        float $ys,
237
        Angle $ordinate1OfEvaluationPoint,
238
        Angle $ordinate2OfEvaluationPoint,
239
        Scale $scalingFactorForCoordDifferences,
240
        Scale $A0,
241
        Scale $B0,
242
        array $powerCoefficients
243 18
    ): array {
244 18
        $xo = $ordinate1OfEvaluationPoint->getValue();
245 18
        $yo = $ordinate2OfEvaluationPoint->getValue();
246 18
247
        $U = $scalingFactorForCoordDifferences->asUnity()->getValue() * ($xs - $xo);
248 18
        $V = $scalingFactorForCoordDifferences->asUnity()->getValue() * ($ys - $yo);
249 18
250
        $mTdX = $A0->getValue();
251 18
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
252 18
            if ($coefficientName[0] === 'A') {
253 18
                sscanf($coefficientName, 'Au%dv%d', $uPower, $vPower);
1 ignored issue
show
Comprehensibility Best Practice introduced by
The variable $vPower seems to be never defined.
Loading history...
254 18
                $mTdX += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
255 18
            }
256
        }
257
258
        $mTdY = $B0->getValue();
259 18
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
260 18
            if ($coefficientName[0] === 'B') {
261 18
                sscanf($coefficientName, 'Bu%dv%d', $uPower, $vPower);
262 18
                $mTdY += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
263 18
            }
264
        }
265
266
        $xt = $xs + $mTdX * $scalingFactorForCoordDifferences->asUnity()->getValue();
267 18
        $yt = $ys + $mTdY * $scalingFactorForCoordDifferences->asUnity()->getValue();
268 18
269
        return ['xt' => $xt, 'yt' => $yt];
270 18
    }
271
272
    /**
273
     * Floating point vagaries mean that it's possible for inputs to be e.g. 1.00000000000001 which makes PHP give a
274
     * silent NaN as output so inputs need to be capped. atan/atan2 are not affected, they seem to cap internally.
275
     */
276 36
    protected static function acos(float $num): float
277
    {
278
        if ($num > 1.0) {
279
            $num = 1.0;
280
        } elseif ($num < -1) {
281
            $num = -1.0;
282
        }
283
284
        return acos($num);
285
    }
286 36
287 36
    /**
288
     * Floating point vagaries mean that it's possible for inputs to be e.g. 1.00000000000001 which makes PHP give a
289 36
     * silent NaN as output so inputs need to be capped. atan/atan2 are not affected, they seem to cap internally.
290 36
     */
291
    protected static function asin(float $num): float
292 36
    {
293 36
        if ($num > 1.0) {
294 36
            $num = 1.0;
295 36
        } elseif ($num < -1.0) {
296 36
            $num = -1.0;
297
        }
298
299
        return asin($num);
300 36
    }
301 36
302 36
    abstract public function getCRS(): CoordinateReferenceSystem;
303 36
304 36
    abstract public function getCoordinateEpoch(): ?DateTimeImmutable;
305
306
    abstract public function calculateDistance(self $to): Length;
307
}
308