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

Point::asin()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.3332

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 9
ccs 4
cts 6
cp 0.6667
crap 3.3332
rs 10
c 0
b 0
f 0
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