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