Passed
Push — master ( 35f9c8...8bd5c3 )
by Doug
69:38 queued 11:29
created

CompoundPoint::convert()   C

Complexity

Conditions 13
Paths 25

Size

Total Lines 50
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 13.0555

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 13
eloc 29
c 4
b 0
f 0
nc 25
nop 2
dl 0
loc 50
ccs 27
cts 29
cp 0.931
crap 13.0555
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 96
    protected function __construct(Compound $crs, GeographicPoint|ProjectedPoint $horizontalPoint, VerticalPoint $verticalPoint, ?DateTimeInterface $epoch = null)
66
    {
67 96
        $this->horizontalPoint = $horizontalPoint;
68 96
        $this->verticalPoint = $verticalPoint;
69 96
        $this->crs = $crs;
70
71 96
        if ($epoch instanceof DateTime) {
72 10
            $epoch = DateTimeImmutable::createFromMutable($epoch);
73
        }
74 96
        $this->epoch = $epoch;
75
    }
76
77 96
    public static function create(Compound $crs, GeographicPoint|ProjectedPoint $horizontalPoint, VerticalPoint $verticalPoint, ?DateTimeInterface $epoch = null): self
78
    {
79 96
        return new static($crs, $horizontalPoint, $verticalPoint, $epoch);
80
    }
81
82 90
    public function getHorizontalPoint(): GeographicPoint|ProjectedPoint
83
    {
84 90
        return $this->horizontalPoint;
85
    }
86
87 71
    public function getVerticalPoint(): VerticalPoint
88
    {
89 71
        return $this->verticalPoint;
90
    }
91
92 77
    public function getCRS(): Compound
93
    {
94 77
        return $this->crs;
95
    }
96
97 38
    public function getCoordinateEpoch(): ?DateTimeImmutable
98
    {
99 38
        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 50
    public function convert(Compound|Geocentric|Geographic2D|Geographic3D|Projected|Vertical $to, bool $ignoreBoundaryRestrictions = false): Point
122
    {
123
        try {
124 50
            return $this->autoConvert($to, $ignoreBoundaryRestrictions);
125 31
        } catch (UnknownConversionException $e) {
126
            // if 2D target, try again with just the horizontal component
127 31
            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 13
            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 4
            $candidateIntermediates = Compound::findFromVertical($this->getVerticalPoint()->getCRS());
152 4
            unset($candidateIntermediates[$this->getCRS()->getSRID()]);
153
154 4
            foreach ($candidateIntermediates as $candidateIntermediate) {
155
                try {
156 4
                    if ($candidateIntermediate->getHorizontal() instanceof Geographic2D && $candidateIntermediate->getHorizontal()->getBaseCRS() instanceof Geographic3D) {
157 4
                        $candidateHorizontalPoint = $this->getHorizontalPoint()->convert($candidateIntermediate->getHorizontal());
158 4
                        $candidateIntermediatePoint = self::create(
159 4
                            $candidateIntermediate,
160 4
                            $candidateHorizontalPoint,
161 4
                            $this->getVerticalPoint(),
162 4
                            $this->epoch,
163 4
                        );
164
165 4
                        return $candidateIntermediatePoint->convert($candidateIntermediate->getHorizontal()->getBaseCRS())->convert($to);
166
                    }
167
                } catch (Throwable) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
168
                }
169
            }
170
            throw $e;
171
        }
172
    }
173
174 27
    public function __toString(): string
175
    {
176 27
        return "({$this->horizontalPoint}, {$this->verticalPoint})";
177
    }
178
179
    /**
180
     * Geographic2D with Height Offsets.
181
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
182
     * coordinate values of the point in the source system.
183
     */
184 18
    public function geographic2DWithHeightOffsets(
185
        Geographic3D $to,
186
        Angle $latitudeOffset,
187
        Angle $longitudeOffset,
188
        Length $geoidUndulation
189
    ): GeographicPoint {
190 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

190
        $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...
191 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

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

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

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