Passed
Push — master ( 3e5121...8c57bf )
by Doug
27:00
created

Point::generalPolynomialUnitless()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 41
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 5

Importance

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