Passed
Push — 4.x ( c37faa...bd875a )
by Doug
12:53
created

GeographicPoint::__construct()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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