Passed
Push — master ( 0e0534...3c9843 )
by Doug
02:42
created

CompoundPoint::convert()   B

Complexity

Conditions 10
Paths 8

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 10.1626

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 17
c 1
b 0
f 0
nc 8
nop 2
dl 0
loc 31
ccs 15
cts 17
cp 0.8824
crap 10.1626
rs 7.6666

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\CoordinateReferenceSystem\Compound;
17
use PHPCoord\CoordinateReferenceSystem\CoordinateReferenceSystem;
18
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
19
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
20
use PHPCoord\CoordinateReferenceSystem\Projected;
21
use PHPCoord\CoordinateReferenceSystem\Vertical;
22
use PHPCoord\Exception\InvalidCoordinateReferenceSystemException;
23
use PHPCoord\Exception\UnknownConversionException;
24
use PHPCoord\UnitOfMeasure\Angle\Angle;
25
use PHPCoord\UnitOfMeasure\Length\Length;
26
27
/**
28
 * Coordinate representing a point expressed in 2 different CRSs (2D horizontal + 1D Vertical).
29
 */
30
class CompoundPoint extends Point implements ConvertiblePoint
31
{
32
    use AutoConversion {
33
        convert as protected autoConvert;
34
    }
35
36
    /**
37
     * Horizontal point.
38
     * @var GeographicPoint|ProjectedPoint
39
     */
40
    protected Point $horizontalPoint;
41
42
    /**
43
     * Vertical point.
44
     */
45
    protected VerticalPoint $verticalPoint;
46
47
    /**
48
     * Coordinate reference system.
49
     */
50
    protected Compound $crs;
51
52
    /**
53
     * Coordinate epoch (date for which the specified coordinates represented this point).
54
     */
55
    protected ?DateTimeImmutable $epoch;
56
57
    /**
58
     * Constructor.
59
     * @param GeographicPoint|ProjectedPoint $horizontalPoint
60
     */
61 8
    protected function __construct(Point $horizontalPoint, VerticalPoint $verticalPoint, Compound $crs, ?DateTimeInterface $epoch = null)
62
    {
63 8
        $this->horizontalPoint = $horizontalPoint;
0 ignored issues
show
Documentation Bug introduced by
$horizontalPoint is of type PHPCoord\Point, but the property $horizontalPoint was declared to be of type PHPCoord\GeographicPoint|PHPCoord\ProjectedPoint. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
64 8
        $this->verticalPoint = $verticalPoint;
65 8
        $this->crs = $crs;
66
67 8
        if ($epoch instanceof DateTime) {
68 1
            $epoch = DateTimeImmutable::createFromMutable($epoch);
69
        }
70 8
        $this->epoch = $epoch;
71 8
    }
72
73
    /**
74
     * @param GeographicPoint|ProjectedPoint $horizontalPoint
75
     */
76 8
    public static function create(Point $horizontalPoint, VerticalPoint $verticalPoint, Compound $crs, ?DateTimeInterface $epoch = null)
77
    {
78 8
        return new static($horizontalPoint, $verticalPoint, $crs, $epoch);
79
    }
80
81 7
    public function getHorizontalPoint(): Point
82
    {
83 7
        return $this->horizontalPoint;
84
    }
85
86 6
    public function getVerticalPoint(): VerticalPoint
87
    {
88 6
        return $this->verticalPoint;
89
    }
90
91 8
    public function getCRS(): Compound
92
    {
93 8
        return $this->crs;
94
    }
95
96 6
    public function getCoordinateEpoch(): ?DateTimeImmutable
97
    {
98 6
        return $this->epoch;
99
    }
100
101
    /**
102
     * Calculate distance between two points.
103
     */
104 2
    public function calculateDistance(Point $to): Length
105
    {
106
        try {
107 2
            if ($to instanceof ConvertiblePoint) {
108 2
                $to = $to->convert($this->crs);
109
            }
110
        } finally {
111 2
            if ($to->getCRS()->getSRID() !== $this->crs->getSRID()) {
112 1
                throw new InvalidCoordinateReferenceSystemException('Can only calculate distances between two points in the same CRS');
113
            }
114
115
            /* @var CompoundPoint $to */
116 1
            return $this->horizontalPoint->calculateDistance($to->horizontalPoint);
117
        }
118
    }
119
120 5
    public function convert(CoordinateReferenceSystem $to, bool $ignoreBoundaryRestrictions = false): Point
121
    {
122
        try {
123 5
            return $this->autoConvert($to, $ignoreBoundaryRestrictions);
124 2
        } catch (UnknownConversionException $e) {
125 2
            if ($this->getHorizontalPoint() instanceof ConvertiblePoint) {
126
                // if 2D target, try again with just the horizontal component
127 2
                if (($to instanceof Geographic2D || $to instanceof Projected)) {
128
                    return $this->getHorizontalPoint()->convert($to, $ignoreBoundaryRestrictions);
129
                }
130
131
                // try separate horizontal + vertical conversions and stitch results together
132 2
                if ($to instanceof Compound) {
133 2
                    $newHorizontalPoint = $this->getHorizontalPoint()->convert($to->getHorizontal());
134
135 2
                    if ($this->getCRS()->getVertical()->getSRID() !== $to->getVertical()->getSRID()) {
136 2
                        $path = $this->findOperationPath($this->getCRS()->getVertical(), $to->getVertical(), $ignoreBoundaryRestrictions);
137
138 1
                        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 1
                            $newVerticalPoint = $this->getVerticalPoint();
140 1
                            foreach ($path as $step) {
141 1
                                $target = CoordinateReferenceSystem::fromSRID($step['in_reverse'] ? $step['source_crs'] : $step['target_crs']);
142 1
                                $newVerticalPoint = $newVerticalPoint->performOperation($step['operation'], $target, $step['in_reverse'], ['horizontalPoint' => $newHorizontalPoint]);
143
                            }
144
145 1
                            return static::create($newHorizontalPoint, $newVerticalPoint, $to, $this->epoch);
146
                        }
147
                    }
148
                }
149
            }
150
            throw $e;
151
        }
152
    }
153
154 3
    public function __toString(): string
155
    {
156 3
        return "({$this->horizontalPoint}, {$this->verticalPoint})";
157
    }
158
159
    /**
160
     * Geographic2D with Height Offsets.
161
     * This transformation allows calculation of coordinates in the target system by adding the parameter value to the
162
     * coordinate values of the point in the source system.
163
     */
164 2
    public function geographic2DWithHeightOffsets(
165
        Geographic3D $to,
166
        Angle $latitudeOffset,
167
        Angle $longitudeOffset,
168
        Length $geoidUndulation
169
    ): GeographicPoint {
170 2
        $toLatitude = $this->getHorizontalPoint()->getLatitude()->add($latitudeOffset);
0 ignored issues
show
Bug introduced by
The method getLatitude() does not exist on PHPCoord\Point. It seems like you code against a sub-type of PHPCoord\Point such as PHPCoord\GeographicPoint. ( Ignorable by Annotation )

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

170
        $toLatitude = $this->getHorizontalPoint()->/** @scrutinizer ignore-call */ getLatitude()->add($latitudeOffset);
Loading history...
171 2
        $toLongitude = $this->getHorizontalPoint()->getLongitude()->add($longitudeOffset);
0 ignored issues
show
Bug introduced by
The method getLongitude() does not exist on PHPCoord\Point. It seems like you code against a sub-type of PHPCoord\Point such as PHPCoord\GeographicPoint. ( Ignorable by Annotation )

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

171
        $toLongitude = $this->getHorizontalPoint()->/** @scrutinizer ignore-call */ getLongitude()->add($longitudeOffset);
Loading history...
172 2
        $toHeight = $this->getVerticalPoint()->getHeight()->add($geoidUndulation);
173
174 2
        return GeographicPoint::create($toLatitude, $toLongitude, $toHeight, $to, $this->epoch);
175
    }
176
}
177