Passed
Push — 4.x ( f9780c...a64886 )
by Doug
05:20
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 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