Passed
Push — master ( 0e0534...3c9843 )
by Doug
02:42
created

Point   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 314
Duplicated Lines 0 %

Test Coverage

Coverage 91.82%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 157
c 3
b 0
f 0
dl 0
loc 314
ccs 146
cts 159
cp 0.9182
rs 8.5599
wmc 48

11 Methods

Rating   Name   Duplication   Size   Complexity  
B resolveParamsByOperation() 0 32 8
A generalPolynomialUnitless() 0 41 5
A reversiblePolynomialUnitless() 0 36 5
A camelCase() 0 8 2
A sign() 0 7 2
A acos() 0 9 3
A getAxisByName() 0 9 3
B vincenty() 0 62 7
A performOperation() 0 29 6
A asin() 0 9 3
A resolveConcatenatedOperations() 0 20 4

How to fix   Complexity   

Complex Class

Complex classes like Point often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Point, and based on these observations, apply Extract Interface, too.

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