Passed
Push — master ( 2a4317...e180e7 )
by Doug
47:34
created

CompoundPoint::convert()   C

Complexity

Conditions 14
Paths 13

Size

Total Lines 54
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 14.0581

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 14
eloc 30
nc 13
nop 2
dl 0
loc 54
ccs 28
cts 30
cp 0.9333
crap 14.0581
rs 6.2666
c 2
b 0
f 0

How to fix   Long Method    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 94
    protected function __construct(Compound $crs, GeographicPoint|ProjectedPoint $horizontalPoint, VerticalPoint $verticalPoint, ?DateTimeInterface $epoch = null)
66
    {
67 94
        $this->horizontalPoint = $horizontalPoint;
68 94
        $this->verticalPoint = $verticalPoint;
69 94
        $this->crs = $crs;
70
71 94
        if ($epoch instanceof DateTime) {
72 9
            $epoch = DateTimeImmutable::createFromMutable($epoch);
73
        }
74 94
        $this->epoch = $epoch;
75
    }
76
77 94
    public static function create(Compound $crs, GeographicPoint|ProjectedPoint $horizontalPoint, VerticalPoint $verticalPoint, ?DateTimeInterface $epoch = null): self
78
    {
79 94
        return new static($crs, $horizontalPoint, $verticalPoint, $epoch);
80
    }
81
82 88
    public function getHorizontalPoint(): GeographicPoint|ProjectedPoint
83
    {
84 88
        return $this->horizontalPoint;
85
    }
86
87 69
    public function getVerticalPoint(): VerticalPoint
88
    {
89 69
        return $this->verticalPoint;
90
    }
91
92 75
    public function getCRS(): Compound
93
    {
94 75
        return $this->crs;
95
    }
96
97 36
    public function getCoordinateEpoch(): ?DateTimeImmutable
98
    {
99 36
        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 48
    public function convert(Compound|Geocentric|Geographic2D|Geographic3D|Projected|Vertical $to, bool $ignoreBoundaryRestrictions = false): Point
122
    {
123
        try {
124 48
            return $this->autoConvert($to, $ignoreBoundaryRestrictions);
125 29
        } catch (UnknownConversionException $e) {
126
            // if 2D target, try again with just the horizontal component
127 29
            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 11
            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 2
            if ($to instanceof Geographic3D) {
150
                // try converting to any/all of the other Compound CRSs that include the same vertical CRS, where the
151
                // horizontal CRS has a 3D equivalent. From there, try converting using the usual mechanisms
152
153 2
                $candidateIntermediates = Compound::findFromVertical($this->getVerticalPoint()->getCRS());
154 2
                unset($candidateIntermediates[$this->getCRS()->getSRID()]);
155
156 2
                foreach ($candidateIntermediates as $candidateIntermediate) {
157
                    try {
158 2
                        if ($candidateIntermediate->getHorizontal() instanceof Geographic2D && $candidateIntermediate->getHorizontal()->getBaseCRS() instanceof Geographic3D) {
159 2
                            $newHorizontalPoint = $this->getHorizontalPoint()->convert($candidateIntermediate->getHorizontal());
160 2
                            $workingPoint = self::create(
161 2
                                $candidateIntermediate,
162 2
                                $newHorizontalPoint,
163 2
                                $this->getVerticalPoint()
164 2
                            );
165
166 2
                            $candidateIntermediatePoint = $workingPoint->convert($candidateIntermediate->getHorizontal()->getBaseCRS());
167
168 2
                            return $candidateIntermediatePoint->convert($to);
169
                        }
170
                    } catch (Throwable) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
171
                    }
172
                }
173
            }
174
            throw $e;
175
        }
176
    }
177
178 27
    public function __toString(): string
179
    {
180 27
        return "({$this->horizontalPoint}, {$this->verticalPoint})";
181
    }
182
183
    /**
184
     * Geographic2D with Height Offsets.
185
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
186
     * coordinate values of the point in the source system.
187
     */
188 18
    public function geographic2DWithHeightOffsets(
189
        Geographic3D $to,
190
        Angle $latitudeOffset,
191
        Angle $longitudeOffset,
192
        Length $geoidUndulation
193
    ): GeographicPoint {
194 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

194
        $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...
195 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

195
        $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...
196 18
        $toHeight = $this->getVerticalPoint()->getHeight()->add($geoidUndulation);
197
198 18
        return GeographicPoint::create($to, $toLatitude, $toLongitude, $toHeight, $this->epoch);
199
    }
200
201
    /**
202
     * Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB).
203
     * Uses ETRS89 / National Grid as an intermediate coordinate system for bi-linear interpolation of gridded grid
204
     * coordinate differences.
205
     */
206 1
    public function geographic3DTo2DPlusGravityHeightOSGM15(
207
        Geographic3D $to,
208
        OSTNOSGM15Grid $geoidHeightCorrectionModelFile
209
    ): GeographicPoint {
210 1
        $osgb36NationalGrid = Projected::fromSRID(Projected::EPSG_OSGB36_BRITISH_NATIONAL_GRID);
211 1
        $etrs89NationalGrid = new Projected(
212 1
            'ETRS89 / National Grid',
213 1
            Cartesian::fromSRID(Cartesian::EPSG_2D_AXES_EASTING_NORTHING_E_N_ORIENTATIONS_EAST_NORTH_UOM_M),
214 1
            Datum::fromSRID(Datum::EPSG_EUROPEAN_TERRESTRIAL_REFERENCE_SYSTEM_1989_ENSEMBLE),
215 1
            $osgb36NationalGrid->getBoundingArea()
216 1
        );
217
218 1
        $projected = $this->horizontalPoint->transverseMercator($etrs89NationalGrid, new Degree(49), new Degree(-2), new Unity(0.9996012717), new Metre(400000), new Metre(-100000));
219
220 1
        return GeographicPoint::create(
221 1
            $to,
222 1
            $this->horizontalPoint->getLatitude(),
223 1
            $this->horizontalPoint->getLongitude(),
224 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

224
            $this->verticalPoint->getHeight()->add($geoidHeightCorrectionModelFile->getHeightAdjustment(/** @scrutinizer ignore-type */ $projected)),
Loading history...
225 1
            $this->getCoordinateEpoch()
226 1
        );
227
    }
228
229
    /**
230
     * Geog3D to Geog2D+GravityRelatedHeight.
231
     */
232 8
    public function geographic3DTo2DPlusGravityHeightFromGrid(
233
        Geographic3D $to,
234
        GeographicGeoidHeightGrid $geoidHeightCorrectionModelFile
235
    ): GeographicPoint {
236 8
        return GeographicPoint::create(
237 8
            $to,
238 8
            $this->horizontalPoint->getLatitude(),
239 8
            $this->horizontalPoint->getLongitude(),
240 8
            $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

240
            $this->verticalPoint->getHeight()->add($geoidHeightCorrectionModelFile->getHeightAdjustment(/** @scrutinizer ignore-type */ $this->horizontalPoint)),
Loading history...
241 8
            $this->getCoordinateEpoch()
242 8
        );
243
    }
244
}
245