Passed
Push — 4.x ( 739b15...17e0c5 )
by Doug
04:36 queued 02:58
created

GeographicPoint::calculateDistance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 12
ccs 5
cts 6
cp 0.8333
rs 10
cc 2
nc 2
nop 1
crap 2.0185
1
<?php
2
/**
3
 * PHPCoord.
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace PHPCoord;
10
11
use InvalidArgumentException;
12
use PHPCoord\CoordinateReferenceSystem\CoordinateReferenceSystem;
13
use PHPCoord\CoordinateReferenceSystem\Geographic2D;
14
use PHPCoord\CoordinateReferenceSystem\Geographic3D;
15
use PHPCoord\CoordinateSystem\Axis;
16
use PHPCoord\Datum\Ellipsoid;
17
use PHPCoord\Exception\InvalidCoordinateReferenceSystemException;
18
use PHPCoord\Exception\UnknownAxisException;
19
use PHPCoord\UnitOfMeasure\Angle\Angle;
20
use PHPCoord\UnitOfMeasure\Length\Length;
21
use PHPCoord\UnitOfMeasure\Length\Metre;
22
use PHPCoord\UnitOfMeasure\UnitOfMeasureFactory;
23
use TypeError;
24
25
/**
26
 * Coordinate representing a point on an ellipsoid.
27
 * @author Doug Wright
28
 */
29
class GeographicPoint extends Point
30
{
31
    /**
32
     * Latitude.
33
     * @var Angle
34
     */
35
    protected $latitude;
36
37
    /**
38
     * Longitude.
39
     * @var Angle
40
     */
41
    protected $longitude;
42
43
    /**
44
     * Height above ellipsoid (N.B. *not* height above ground, sea-level or anything else tangible).
45
     * @var ?Length
46
     */
47
    protected $height;
48
49
    /**
50
     * Coordinate reference system.
51
     * @var CoordinateReferenceSystem
52
     */
53
    protected $crs;
54
55
    /**
56
     * Constructor.
57
     * @param Angle                     $latitude  refer to CRS for preferred unit of measure, but any angle unit accepted
58
     * @param Angle                     $longitude refer to CRS for preferred unit of measure, but any angle unit accepted
59
     * @param ?Length                   $height    refer to CRS for preferred unit of measure, but any length unit accepted
60
     * @param Geographic2D|Geographic3D $crs
61
     */
62 7
    public function __construct(Angle $latitude, Angle $longitude, ?Length $height, CoordinateReferenceSystem $crs)
63
    {
64 7
        if (!$crs instanceof Geographic2D && !$crs instanceof Geographic3D) {
65
            throw new TypeError(sprintf("A geographic point must be associated with a geographic CRS, but a '%s' CRS was given", get_class($crs)));
66
        }
67
68 7
        if ($crs instanceof Geographic2D && $height !== null) {
69 1
            throw new InvalidCoordinateReferenceSystemException('A 2D geographic point must not include a height');
70
        }
71
72 6
        if ($crs instanceof Geographic3D && $height === null) {
73 1
            throw new InvalidCoordinateReferenceSystemException('A 3D geographic point must include a height, none given');
74
        }
75
76 5
        $this->crs = $crs;
77
78 5
        $this->latitude = UnitOfMeasureFactory::convertAngle($latitude, $this->getAxisByName(Axis::GEODETIC_LATITUDE)->getUnitOfMeasureId());
79 5
        $this->longitude = UnitOfMeasureFactory::convertAngle($longitude, $this->getAxisByName(Axis::GEODETIC_LONGITUDE)->getUnitOfMeasureId());
80
81 5
        if ($height) {
82 2
            $this->height = UnitOfMeasureFactory::convertLength($height, $this->getAxisByName(Axis::ELLIPSOIDAL_HEIGHT)->getUnitOfMeasureId());
83
        }
84 5
    }
85
86 4
    public function getLatitude(): Angle
87
    {
88 4
        return $this->latitude;
89
    }
90
91 4
    public function getLongitude(): Angle
92
    {
93 4
        return $this->longitude;
94
    }
95
96 3
    public function getHeight(): ?Length
97
    {
98 3
        return $this->height;
99
    }
100
101 5
    public function getCRS(): CoordinateReferenceSystem
102
    {
103 5
        return $this->crs;
104
    }
105
106
    /**
107
     * Calculate surface distance between two points.
108
     */
109 1
    public function calculateDistance(Point $to): Length
110
    {
111 1
        if ($to->getCRS()->getEpsgCode() !== $this->crs->getEpsgCode()) {
112
            throw new InvalidArgumentException('Can only calculate distances between two points in the same CRS');
113
        }
114
115
        //Mean radius definition taken from Wikipedia
116
        /** @var Ellipsoid $ellipsoid */
117 1
        $ellipsoid = $this->getCRS()->getDatum()->getEllipsoid();
118 1
        $radius = ((2 * $ellipsoid->getSemiMajorAxis()->asMetres()->getValue()) + $ellipsoid->getSemiMinorAxis()->asMetres()->getValue()) / 3;
119
120 1
        return new Metre(acos(sin($this->latitude->asRadians()->getValue()) * sin($to->latitude->asRadians()->getValue()) + cos($this->latitude->asRadians()->getValue()) * cos($to->latitude->asRadians()->getValue()) * cos($to->longitude->asRadians()->getValue() - $this->longitude->asRadians()->getValue())) * $radius);
121
    }
122
123 2
    public function __toString(): string
124
    {
125 2
        $values = [];
126 2
        foreach ($this->getCRS()->getCoordinateSystem()->getAxes() as $axis) {
127 2
            if ($axis->getName() === Axis::GEODETIC_LATITUDE) {
128 2
                $values[] = $this->latitude;
129 2
            } elseif ($axis->getName() === Axis::GEODETIC_LONGITUDE) {
130 2
                $values[] = $this->longitude;
131 1
            } elseif ($axis->getName() === Axis::ELLIPSOIDAL_HEIGHT) {
132 1
                $values[] = $this->height;
133
            } else {
134
                throw new UnknownAxisException(); // @codeCoverageIgnore
135
            }
136
        }
137
138 2
        return '(' . implode(', ', $values) . ')';
139
    }
140
}
141