Passed
Push — master ( 21cd64...5dd3a0 )
by Doug
50:51
created

CompoundPoint::convert()   C

Complexity

Conditions 13
Paths 25

Size

Total Lines 49
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 13.0615

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 13
eloc 28
c 3
b 0
f 0
nc 25
nop 2
dl 0
loc 49
ccs 26
cts 28
cp 0.9286
crap 13.0615
rs 6.6166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use DateTime;
12
use DateTimeImmutable;
13
use DateTimeInterface;
14
use PHPCoord\CoordinateOperation\AutoConversion;
15
use PHPCoord\CoordinateOperation\ConvertiblePoint;
16
use PHPCoord\CoordinateOperation\GeographicGeoidHeightGrid;
17
use PHPCoord\CoordinateOperation\OSTNOSGM15Grid;
18
use PHPCoord\CoordinateReferenceSystem\Compound;
19
use PHPCoord\CoordinateReferenceSystem\CoordinateReferenceSystem;
20
use PHPCoord\CoordinateReferenceSystem\Geocentric;
21
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
22
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
23
use PHPCoord\CoordinateReferenceSystem\Projected;
0 ignored issues
show
Bug introduced by
The type PHPCoord\CoordinateReferenceSystem\Projected 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\CoordinateReferenceSystem\Vertical;
25
use PHPCoord\CoordinateSystem\Cartesian;
26
use PHPCoord\Datum\Datum;
27
use PHPCoord\Exception\InvalidCoordinateReferenceSystemException;
28
use PHPCoord\Exception\UnknownConversionException;
29
use PHPCoord\UnitOfMeasure\Angle\Angle;
30
use PHPCoord\UnitOfMeasure\Angle\Degree;
31
use PHPCoord\UnitOfMeasure\Length\Length;
32
use PHPCoord\UnitOfMeasure\Length\Metre;
33
use PHPCoord\UnitOfMeasure\Scale\Unity;
34
use Throwable;
35
36
/**
37
 * Coordinate representing a point expressed in 2 different CRSs (2D horizontal + 1D Vertical).
38
 */
39
class CompoundPoint extends Point implements ConvertiblePoint
40
{
41
    use AutoConversion {
42
        convert as protected autoConvert;
43
    }
44
45
    /**
46
     * Horizontal point.
47
     */
48
    protected GeographicPoint|ProjectedPoint $horizontalPoint;
49
50
    /**
51
     * Vertical point.
52
     */
53
    protected VerticalPoint $verticalPoint;
54
55
    /**
56
     * Coordinate reference system.
57
     */
58
    protected Compound $crs;
59
60
    /**
61
     * Coordinate epoch (date for which the specified coordinates represented this point).
62
     */
63
    protected ?DateTimeImmutable $epoch;
64
65 95
    protected function __construct(Compound $crs, GeographicPoint|ProjectedPoint $horizontalPoint, VerticalPoint $verticalPoint, ?DateTimeInterface $epoch = null)
66
    {
67 95
        $this->horizontalPoint = $horizontalPoint;
68 95
        $this->verticalPoint = $verticalPoint;
69 95
        $this->crs = $crs;
70
71 95
        if ($epoch instanceof DateTime) {
72 9
            $epoch = DateTimeImmutable::createFromMutable($epoch);
73
        }
74 95
        $this->epoch = $epoch;
75
    }
76
77 95
    public static function create(Compound $crs, GeographicPoint|ProjectedPoint $horizontalPoint, VerticalPoint $verticalPoint, ?DateTimeInterface $epoch = null): self
78
    {
79 95
        return new static($crs, $horizontalPoint, $verticalPoint, $epoch);
80
    }
81
82 89
    public function getHorizontalPoint(): GeographicPoint|ProjectedPoint
83
    {
84 89
        return $this->horizontalPoint;
85
    }
86
87 70
    public function getVerticalPoint(): VerticalPoint
88
    {
89 70
        return $this->verticalPoint;
90
    }
91
92 76
    public function getCRS(): Compound
93
    {
94 76
        return $this->crs;
95
    }
96
97 37
    public function getCoordinateEpoch(): ?DateTimeImmutable
98
    {
99 37
        return $this->epoch;
100
    }
101
102
    /**
103
     * Calculate distance between two points.
104
     */
105 18
    public function calculateDistance(Point $to): Length
106
    {
107
        try {
108 18
            if ($to instanceof ConvertiblePoint) {
109 18
                $to = $to->convert($this->horizontalPoint->getCRS());
110
            }
111
        } finally {
112 18
            if ($to->getCRS()->getSRID() !== $this->horizontalPoint->getCRS()->getSRID()) {
113 9
                throw new InvalidCoordinateReferenceSystemException('Can only calculate distances between two points in the same CRS');
114
            }
115
116
            /* @var CompoundPoint $to */
117 9
            return $this->horizontalPoint->calculateDistance($to);
118
        }
119
    }
120
121 49
    public function convert(Compound|Geocentric|Geographic2D|Geographic3D|Projected|Vertical $to, bool $ignoreBoundaryRestrictions = false): Point
122
    {
123
        try {
124 49
            return $this->autoConvert($to, $ignoreBoundaryRestrictions);
125 30
        } catch (UnknownConversionException $e) {
126
            // if 2D target, try again with just the horizontal component
127 30
            if ($to instanceof Geographic2D || $to instanceof Projected) {
128 18
                return $this->getHorizontalPoint()->convert($to, $ignoreBoundaryRestrictions);
129
            }
130
131
            // try separate horizontal + vertical conversions and stitch results together
132 12
            if ($to instanceof Compound) {
133 9
                $newHorizontalPoint = $this->getHorizontalPoint()->convert($to->getHorizontal());
134
135 9
                if ($this->getCRS()->getVertical()->getSRID() !== $to->getVertical()->getSRID()) {
136 9
                    $path = $this->findOperationPath($this->getCRS()->getVertical(), $to->getVertical(), $ignoreBoundaryRestrictions);
137
138 9
                    if ($path) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
139 9
                        $newVerticalPoint = $this->getVerticalPoint();
140 9
                        foreach ($path as $step) {
141 9
                            $target = CoordinateReferenceSystem::fromSRID($step['in_reverse'] ? $step['source_crs'] : $step['target_crs']);
142 9
                            $newVerticalPoint = $newVerticalPoint->performOperation($step['operation'], $target, $step['in_reverse'], ['horizontalPoint' => $newHorizontalPoint]);
143
                        }
144
145 9
                        return static::create($to, $newHorizontalPoint, $newVerticalPoint, $this->epoch);
146
                    }
147
                }
148
            }
149
            // try converting to any/all of the other Compound CRSs that include the same vertical CRS, where the
150
            // horizontal CRS has a 3D equivalent. From there, try converting using the usual mechanisms
151 3
            $candidateIntermediates = Compound::findFromVertical($this->getVerticalPoint()->getCRS());
152 3
            unset($candidateIntermediates[$this->getCRS()->getSRID()]);
153
154 3
            foreach ($candidateIntermediates as $candidateIntermediate) {
155
                try {
156 3
                    if ($candidateIntermediate->getHorizontal() instanceof Geographic2D && $candidateIntermediate->getHorizontal()->getBaseCRS() instanceof Geographic3D) {
157 3
                        $candidateHorizontalPoint = $this->getHorizontalPoint()->convert($candidateIntermediate->getHorizontal());
158 3
                        $candidateIntermediatePoint = self::create(
159 3
                            $candidateIntermediate,
160 3
                            $candidateHorizontalPoint,
161 3
                            $this->getVerticalPoint()
162 3
                        );
163
164 3
                        return $candidateIntermediatePoint->convert($candidateIntermediate->getHorizontal()->getBaseCRS())->convert($to);
165
                    }
166
                } catch (Throwable) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
167
                }
168
            }
169
            throw $e;
170
        }
171
    }
172
173 27
    public function __toString(): string
174
    {
175 27
        return "({$this->horizontalPoint}, {$this->verticalPoint})";
176
    }
177
178
    /**
179
     * Geographic2D with Height Offsets.
180
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
181
     * coordinate values of the point in the source system.
182
     */
183 18
    public function geographic2DWithHeightOffsets(
184
        Geographic3D $to,
185
        Angle $latitudeOffset,
186
        Angle $longitudeOffset,
187
        Length $geoidUndulation
188
    ): GeographicPoint {
189 18
        $toLatitude = $this->getHorizontalPoint()->getLatitude()->add($latitudeOffset);
0 ignored issues
show
Bug introduced by
The method getLatitude() does not exist on PHPCoord\ProjectedPoint. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

189
        $toLatitude = $this->getHorizontalPoint()->/** @scrutinizer ignore-call */ getLatitude()->add($latitudeOffset);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
190 18
        $toLongitude = $this->getHorizontalPoint()->getLongitude()->add($longitudeOffset);
0 ignored issues
show
Bug introduced by
The method getLongitude() does not exist on PHPCoord\ProjectedPoint. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

190
        $toLongitude = $this->getHorizontalPoint()->/** @scrutinizer ignore-call */ getLongitude()->add($longitudeOffset);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
191 18
        $toHeight = $this->getVerticalPoint()->getHeight()->add($geoidUndulation);
192
193 18
        return GeographicPoint::create($to, $toLatitude, $toLongitude, $toHeight, $this->epoch);
194
    }
195
196
    /**
197
     * Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB).
198
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
199
     * coordinate differences.
200
     */
201 1
    public function geographic3DTo2DPlusGravityHeightOSGM15(
202
        Geographic3D $to,
203
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
204
    ): GeographicPoint {
205 1
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
206 1
        $etrs89NationalGrid = new Projected(
207 1
            'ETRS89 / National Grid',
208 1
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
209 1
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
210 1
            $osgb36NationalGrid->getBoundingArea()
211 1
        );
212
213 1
        $projected = $this->horizontalPoint->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
214
215 1
        return GeographicPoint::create(
216 1
            $to,
217 1
            $this->horizontalPoint->getLatitude(),
218 1
            $this->horizontalPoint->getLongitude(),
219 1
            $this->verticalPoint->getHeight()->add($geoidHeightCorrectionModelFile->getHeightAdjustment($projected)),
0 ignored issues
show
Bug introduced by
It seems like $projected can also be of type PHPCoord\GeographicPoint; however, parameter $point of PHPCoord\CoordinateOpera...::getHeightAdjustment() does only seem to accept PHPCoord\ProjectedPoint, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

219
            $this->verticalPoint->getHeight()->add($geoidHeightCorrectionModelFile->getHeightAdjustment(/** @scrutinizer ignore-type */ $projected)),
Loading history...
220 1
            $this->getCoordinateEpoch()
221 1
        );
222
    }
223
224
    /**
225
     * Geog3D to Geog2D+GravityRelatedHeight.
226
     */
227 9
    public function geographic3DTo2DPlusGravityHeightFromGrid(
228
        Geographic3D $to,
229
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
230
    ): GeographicPoint {
231 9
        return GeographicPoint::create(
232 9
            $to,
233 9
            $this->horizontalPoint->getLatitude(),
234 9
            $this->horizontalPoint->getLongitude(),
235 9
            $this->verticalPoint->getHeight()->add($geoidHeightCorrectionModelFile->getHeightAdjustment($this->horizontalPoint)),
0 ignored issues
show
Bug introduced by
It seems like $this->horizontalPoint can also be of type PHPCoord\ProjectedPoint; however, parameter $location of PHPCoord\CoordinateOpera...::getHeightAdjustment() does only seem to accept PHPCoord\GeographicPoint, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

235
            $this->verticalPoint->getHeight()->add($geoidHeightCorrectionModelFile->getHeightAdjustment(/** @scrutinizer ignore-type */ $this->horizontalPoint)),
Loading history...
236 9
            $this->getCoordinateEpoch()
237 9
        );
238
    }
239
}
240