Passed
Push — 4.x ( f9780c...a64886 )
by Doug
05:20
created

Point::performOperation()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5.0729

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 15
c 2
b 0
f 0
nc 5
nop 3
dl 0
loc 25
ccs 12
cts 14
cp 0.8571
crap 5.0729
rs 9.4555
1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use function acos;
12
use function array_reverse;
13
use function array_values;
14
use function asin;
15
use DateTimeImmutable;
16
use function in_array;
17
use function lcfirst;
18
use PHPCoord\CoordinateOperation\CoordinateOperationMethods;
19
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...
20
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...
21
use PHPCoord\CoordinateReferenceSystem\CoordinateReferenceSystem;
22
use PHPCoord\CoordinateSystem\Axis;
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 PHPCoord\UnitOfMeasure\UnitOfMeasureFactory;
29
use function preg_match;
30
use function sscanf;
31
use function str_replace;
32
use Stringable;
33
use function ucwords;
34
35
abstract class Point implements Stringable
36
{
37
    protected const NEWTON_RAPHSON_CONVERGENCE = 1e-12;
38
39
    /**
40
     * @internal
41
     */
42 11
    public function performOperation(string $srid, CoordinateReferenceSystem $to, bool $inReverse): self
43
    {
44 11
        $operations = self::resolveConcatenatedOperations($srid, $inReverse);
45
46 11
        $point = $this;
47 11
        foreach ($operations as $operationSrid => $operation) {
48 11
            $method = CoordinateOperationMethods::getFunctionName($operation['method']);
49 11
            if (isset($operation['source_crs'])) {
50
                $destCRS = CoordinateReferenceSystem::fromSRID($inReverse ? $operation['source_crs'] : $operation['target_crs']);
51
            } else {
52 11
                $destCRS = $to;
53
            }
54
55 11
            $params = self::resolveParamsByOperation($operationSrid, $operation['method'], $inReverse);
56
57 11
            if (PHP_MAJOR_VERSION >= 8) {
58
                $point = $point->$method($destCRS, ...$params);
59
            } else {
60 11
                $point = $point->$method($destCRS, ...array_values($params));
61
            }
62
        }
63
64 11
        $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
65
66 11
        return $point;
67
    }
68
69 11
    protected static function camelCase(string $string): string
70
    {
71 11
        $string = str_replace([' ', '-'], '', ucwords($string, ' -'));
72 11
        if (!preg_match('/[ABC][uv\d]/', $string)) {
73 11
            $string = lcfirst($string);
74
        }
75
76 11
        return $string;
77
    }
78
79 11
    protected static function resolveConcatenatedOperations(string $operationSrid, bool $inReverse): array
80
    {
81 11
        $operations = [];
82 11
        $operation = CoordinateOperations::getOperationData($operationSrid);
83 11
        if (isset($operation['operations'])) {
84
            foreach ($operation['operations'] as $subOperation) {
85
                $subOperationData = CoordinateOperations::getOperationData($subOperation['operation']);
86
                $subOperationData['source_crs'] = $subOperation['source_crs'];
87
                $subOperationData['target_crs'] = $subOperation['target_crs'];
88
                $operations[$subOperation['operation']] = $subOperationData;
89
            }
90
        } else {
91 11
            $operations[$operationSrid] = $operation;
92
        }
93
94 11
        if ($inReverse) {
95 2
            $operations = array_reverse($operations, true);
96
        }
97
98 11
        return $operations;
99
    }
100
101 11
    protected static function resolveParamsByOperation(string $operationSrid, string $methodSrid, bool $inReverse): array
102
    {
103 11
        $params = [];
104 11
        $powerCoefficients = [];
105 11
        foreach (CoordinateOperationParams::getParamData($operationSrid) as $paramName => $paramData) {
106 11
            $value = $paramData['value'];
107 11
            if ($inReverse && $paramData['reverses']) {
108 1
                $value *= -1;
109
            }
110 11
            if ($paramData['uom']) {
111 11
                $param = UnitOfMeasureFactory::makeUnit($value, $paramData['uom']);
112
            } else {
113
                $param = $paramData['value'];
114
            }
115 11
            $paramName = static::camelCase($paramName);
116 11
            if (preg_match('/^(Au|Bu)/', $paramName)) {
117 3
                $powerCoefficients[$paramName] = $param;
118
            } else {
119 11
                $params[$paramName] = $param;
120
            }
121
        }
122 11
        if ($powerCoefficients) {
123 3
            $params['powerCoefficients'] = $powerCoefficients;
124
        }
125 11
        if (in_array($methodSrid, [
126
            CoordinateOperationMethods::EPSG_SIMILARITY_TRANSFORMATION,
127
            CoordinateOperationMethods::EPSG_AFFINE_PARAMETRIC_TRANSFORMATION,
128
        ], true)) {
129
            $params['inReverse'] = $inReverse;
130
        }
131
132 11
        return $params;
133
    }
134
135 201
    protected function getAxisByName(string $name): ?Axis
136
    {
137 201
        foreach ($this->getCRS()->getCoordinateSystem()->getAxes() as $axis) {
138 201
            if ($axis->getName() === $name) {
139 201
                return $axis;
140
            }
141
        }
142
143 135
        return null;
144
    }
145
146 10
    protected static function sign(float $number): int
147
    {
148 10
        if ($number < 0) {
149
            return -1;
150
        }
151
152 10
        return 1;
153
    }
154
155
    /**
156
     * General polynomial.
157
     * @param Coefficient[] $powerCoefficients
158
     */
159 2
    protected function generalPolynomialUnitless(
160
        float $xs,
161
        float $ys,
162
        UnitOfMeasure $ordinate1OfEvaluationPointInSourceCRS,
163
        UnitOfMeasure $ordinate2OfEvaluationPointInSourceCRS,
164
        UnitOfMeasure $ordinate1OfEvaluationPointInTargetCRS,
165
        UnitOfMeasure $ordinate2OfEvaluationPointInTargetCRS,
166
        Scale $scalingFactorForSourceCRSCoordDifferences,
167
        Scale $scalingFactorForTargetCRSCoordDifferences,
168
        Scale $A0,
169
        Scale $B0,
170
        array $powerCoefficients
171
    ): array {
172 2
        $xso = $ordinate1OfEvaluationPointInSourceCRS->getValue();
173 2
        $yso = $ordinate2OfEvaluationPointInSourceCRS->getValue();
174 2
        $xto = $ordinate1OfEvaluationPointInTargetCRS->getValue();
175 2
        $yto = $ordinate2OfEvaluationPointInTargetCRS->getValue();
176
177 2
        $U = $scalingFactorForSourceCRSCoordDifferences->asUnity()->getValue() * ($xs - $xso);
178 2
        $V = $scalingFactorForSourceCRSCoordDifferences->asUnity()->getValue() * ($ys - $yso);
179
180 2
        $mTdX = $A0->getValue();
181 2
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
182 2
            if ($coefficientName[0] === 'A') {
183 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...
184 2
                $mTdX += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
185
            }
186
        }
187
188 2
        $mTdY = $B0->getValue();
189 2
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
190 2
            if ($coefficientName[0] === 'B') {
191 2
                sscanf($coefficientName, 'Bu%dv%d', $uPower, $vPower);
192 2
                $mTdY += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
193
            }
194
        }
195
196 2
        $xt = $xs - $xso + $xto + $mTdX / $scalingFactorForTargetCRSCoordDifferences->asUnity()->getValue();
197 2
        $yt = $ys - $yso + $yto + $mTdY / $scalingFactorForTargetCRSCoordDifferences->asUnity()->getValue();
198
199 2
        return ['xt' => $xt, 'yt' => $yt];
200
    }
201
202
    /**
203
     * Reversible polynomial.
204
     */
205 4
    protected function reversiblePolynomialUnitless(
206
        float $xs,
207
        float $ys,
208
        Angle $ordinate1OfEvaluationPoint,
209
        Angle $ordinate2OfEvaluationPoint,
210
        Scale $scalingFactorForCoordDifferences,
211
        Scale $A0,
212
        Scale $B0,
213
        array $powerCoefficients
214
    ): array {
215 4
        $xo = $ordinate1OfEvaluationPoint->getValue();
216 4
        $yo = $ordinate2OfEvaluationPoint->getValue();
217
218 4
        $U = $scalingFactorForCoordDifferences->asUnity()->getValue() * ($xs - $xo);
219 4
        $V = $scalingFactorForCoordDifferences->asUnity()->getValue() * ($ys - $yo);
220
221 4
        $mTdX = $A0->getValue();
222 4
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
223 4
            if ($coefficientName[0] === 'A') {
224 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...
225 4
                $mTdX += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
226
            }
227
        }
228
229 4
        $mTdY = $B0->getValue();
230 4
        foreach ($powerCoefficients as $coefficientName => $coefficientValue) {
231 4
            if ($coefficientName[0] === 'B') {
232 4
                sscanf($coefficientName, 'Bu%dv%d', $uPower, $vPower);
233 4
                $mTdY += $coefficientValue->getValue() * $U ** $uPower * $V ** $vPower;
234
            }
235
        }
236
237 4
        $xt = $xs + $mTdX * $scalingFactorForCoordDifferences->asUnity()->getValue();
238 4
        $yt = $ys + $mTdY * $scalingFactorForCoordDifferences->asUnity()->getValue();
239
240 4
        return ['xt' => $xt, 'yt' => $yt];
241
    }
242
243
    /**
244
     * Floating point vagaries mean that it's possible for inputs to be e.g. 1.00000000000001 which makes PHP give a
245
     * silent NaN as output so inputs need to be capped. atan/atan2 are not affected, they seem to cap internally.
246
     */
247 1
    protected static function acos(float $num): float
248
    {
249 1
        if ($num > 1.0) {
250
            $num = 1.0;
251 1
        } elseif ($num < -1) {
252
            $num = -1.0;
253
        }
254
255 1
        return acos($num);
256
    }
257
258
    /**
259
     * Floating point vagaries mean that it's possible for inputs to be e.g. 1.00000000000001 which makes PHP give a
260
     * silent NaN as output so inputs need to be capped. atan/atan2 are not affected, they seem to cap internally.
261
     */
262 39
    protected static function asin(float $num): float
263
    {
264 39
        if ($num > 1.0) {
265
            $num = 1.0;
266 39
        } elseif ($num < -1.0) {
267
            $num = -1.0;
268
        }
269
270 39
        return asin($num);
271
    }
272
273
    abstract public function getCRS(): CoordinateReferenceSystem;
274
275
    abstract public function getCoordinateEpoch(): ?DateTimeImmutable;
276
277
    abstract public function calculateDistance(self $to): Length;
278
}
279