Completed
Push — master ( 723696...0885cd )
by Antoine
02:44
created

Distance   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 98.89%

Importance

Changes 0
Metric Value
wmc 18
lcom 1
cbo 4
dl 0
loc 221
ccs 89
cts 90
cp 0.9889
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A setFrom() 0 6 1
A getFrom() 0 4 1
A setTo() 0 6 1
A getTo() 0 4 1
A in() 0 6 1
A flat() 0 16 1
A greatCircle() 0 13 1
A haversine() 0 17 1
B vincenty() 0 61 6
A convertToUserUnit() 0 13 4
1
<?php
2
3
/*
4
 * This file is part of the Geotools library.
5
 *
6
 * (c) Antoine Corcy <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace League\Geotools\Distance;
13
14
use League\Geotools\CoordinateCouple;
15
use League\Geotools\Exception\NotConvergingException;
16
use League\Geotools\Coordinate\CoordinateInterface;
17
use League\Geotools\Coordinate\Ellipsoid;
18
use League\Geotools\GeotoolsInterface;
19
20
/**
21
 * Distance class
22
 *
23
 * @author Antoine Corcy <[email protected]>
24
 */
25
class Distance implements DistanceInterface
26
{
27
    use CoordinateCouple;
28
29
    /**
30
     * The user unit.
31
     *
32
     * @var string
33
     */
34
    protected $unit;
35
36
37
    /**
38
     * {@inheritDoc}
39
     */
40 50
    public function setFrom(CoordinateInterface $from)
41
    {
42 50
        $this->from = $from;
43
44 50
        return $this;
45
    }
46
47
    /**
48
     * {@inheritDoc}
49
     */
50 1
    public function getFrom()
51
    {
52 1
        return $this->from;
53
    }
54
55
    /**
56
     * {@inheritDoc}
57
     */
58 50
    public function setTo(CoordinateInterface $to)
59
    {
60 50
        $this->to = $to;
61
62 50
        return $this;
63
    }
64
65
    /**
66
     * {@inheritDoc}
67
     */
68 1
    public function getTo()
69
    {
70 1
        return $this->to;
71
    }
72
73
    /**
74
     * {@inheritDoc}
75
     */
76 38
    public function in($unit)
77
    {
78 38
        $this->unit = $unit;
79
80 38
        return $this;
81
    }
82
83
    /**
84
     * Returns the approximate flat distance between two coordinates
85
     * using Pythagoras’ theorem which is not very accurate.
86
     * @see http://en.wikipedia.org/wiki/Pythagorean_theorem
87
     * @see http://en.wikipedia.org/wiki/Equirectangular_projection
88
     *
89
     * @return double The distance in meters
90
     */
91 18
    public function flat()
92
    {
93 18
        Ellipsoid::checkCoordinatesEllipsoid($this->from, $this->to);
94
95 18
        $latA = deg2rad($this->from->getLatitude());
96 18
        $lngA = deg2rad($this->from->getLongitude());
97 18
        $latB = deg2rad($this->to->getLatitude());
98 18
        $lngB = deg2rad($this->to->getLongitude());
99
100 18
        $x = ($lngB - $lngA) * cos(($latA + $latB) / 2);
101 18
        $y = $latB - $latA;
102
103 18
        $d = sqrt(($x * $x) + ($y * $y)) * $this->from->getEllipsoid()->getA();
104
105 18
        return $this->convertToUserUnit($d);
106
    }
107
108
    /**
109
     * Returns the approximate distance between two coordinates
110
     * using the spherical trigonometry called Great Circle Distance.
111
     * @see http://www.ga.gov.au/earth-monitoring/geodesy/geodetic-techniques/distance-calculation-algorithms.html#circle
112
     * @see http://en.wikipedia.org/wiki/Cosine_law
113
     *
114
     * @return double The distance in meters
115
     */
116 10
    public function greatCircle()
117
    {
118 10
        Ellipsoid::checkCoordinatesEllipsoid($this->from, $this->to);
119
120 10
        $latA = deg2rad($this->from->getLatitude());
121 10
        $lngA = deg2rad($this->from->getLongitude());
122 10
        $latB = deg2rad($this->to->getLatitude());
123 10
        $lngB = deg2rad($this->to->getLongitude());
124
125 10
        $degrees = acos(sin($latA) * sin($latB) + cos($latA) * cos($latB) * cos($lngB - $lngA));
126
127 10
        return $this->convertToUserUnit($degrees * $this->from->getEllipsoid()->getA());
128
    }
129
130
    /**
131
    * Returns the approximate sea level great circle (Earth) distance between
132
    * two coordinates using the Haversine formula which is accurate to around 0.3%.
133
    * @see http://www.movable-type.co.uk/scripts/latlong.html
134
    *
135
    * @return double The distance in meters
136
    */
137 18
    public function haversine()
138
    {
139 18
        Ellipsoid::checkCoordinatesEllipsoid($this->from, $this->to);
140
141 18
        $latA = deg2rad($this->from->getLatitude());
142 18
        $lngA = deg2rad($this->from->getLongitude());
143 18
        $latB = deg2rad($this->to->getLatitude());
144 18
        $lngB = deg2rad($this->to->getLongitude());
145
146 18
        $dLat = $latB - $latA;
147 18
        $dLon = $lngB - $lngA;
148
149 18
        $a = sin($dLat / 2) * sin($dLat / 2) + cos($latA) * cos($latB) * sin($dLon / 2) * sin($dLon / 2);
150 18
        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
151
152 18
        return $this->convertToUserUnit($this->from->getEllipsoid()->getA() * $c);
153
    }
154
155
    /**
156
    * Returns geodetic distance between between two coordinates using Vincenty inverse
157
    * formula for ellipsoids which is accurate to within 0.5mm.
158
    * @see http://www.movable-type.co.uk/scripts/latlong-vincenty.html
159
    *
160
    * @return double The distance in meters
161
    */
162 18
    public function vincenty()
163
    {
164 18
        Ellipsoid::checkCoordinatesEllipsoid($this->from, $this->to);
165
166 18
        $a = $this->from->getEllipsoid()->getA();
167 18
        $b = $this->from->getEllipsoid()->getB();
168 18
        $f = 1 / $this->from->getEllipsoid()->getInvF();
169
170 18
        $lL = deg2rad($this->to->getLongitude() - $this->from->getLongitude());
171 18
        $u1 = atan((1 - $f) * tan(deg2rad($this->from->getLatitude())));
172 18
        $u2 = atan((1 - $f) * tan(deg2rad($this->to->getLatitude())));
173
174 18
        $sinU1 = sin($u1);
175 18
        $cosU1 = cos($u1);
176 18
        $sinU2 = sin($u2);
177 18
        $cosU2 = cos($u2);
178
179 18
        $lambda    = $lL;
180 18
        $iterLimit = 100;
181
182
        do {
183 18
            $sinLambda = sin($lambda);
184 18
            $cosLambda = cos($lambda);
185 18
            $sinSigma  = sqrt(($cosU2 * $sinLambda) * ($cosU2 * $sinLambda) +
186 18
                ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda) * ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda));
187
188 18
            if (0.0 === $sinSigma) {
189 1
                return 0.0; // co-incident points
190
            }
191
192 17
            $cosSigma   = $sinU1 * $sinU2 + $cosU1 * $cosU2 * $cosLambda;
193 17
            $sigma      = atan2($sinSigma, $cosSigma);
194 17
            $sinAlpha   = $cosU1 * $cosU2 * $sinLambda / $sinSigma;
195 17
            $cosSqAlpha = 1 - $sinAlpha * $sinAlpha;
196 17
            if ($cosSqAlpha != 0.0) {
197 17
                $cos2SigmaM = $cosSigma - 2 * $sinU1 * $sinU2 / $cosSqAlpha;
198
            }
199
            else {
200
                $cos2SigmaM = 0.0;
201
            }
202 17
            $cC      = $f / 16 * $cosSqAlpha * (4 + $f * (4 - 3 * $cosSqAlpha));
203 17
            $lambdaP = $lambda;
204 17
            $lambda  = $lL + (1 - $cC) * $f * $sinAlpha * ($sigma + $cC * $sinSigma *
205 17
                ($cos2SigmaM + $cC * $cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM)));
206 17
        } while (abs($lambda - $lambdaP) > 1e-12 && --$iterLimit > 0);
207
208
        // @codeCoverageIgnoreStart
209
        if (0 === $iterLimit) {
210
            throw new NotConvergingException('Vincenty formula failed to converge !');
211
        }
212
        // @codeCoverageIgnoreEnd
213
214 17
        $uSq        = $cosSqAlpha * ($a * $a - $b * $b) / ($b * $b);
215 17
        $aA         = 1 + $uSq / 16384 * (4096 + $uSq * (-768 + $uSq * (320 - 175 * $uSq)));
216 17
        $bB         = $uSq / 1024 * (256 + $uSq * (-128 + $uSq * (74 - 47 * $uSq)));
217 17
        $deltaSigma = $bB * $sinSigma * ($cos2SigmaM + $bB / 4 * ($cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM) -
218 17
            $bB / 6 * $cos2SigmaM * (-3 + 4 * $sinSigma * $sinSigma) * (-3 + 4 * $cos2SigmaM * $cos2SigmaM)));
219 17
        $s          = $b * $aA * ($sigma - $deltaSigma);
220
221 17
        return $this->convertToUserUnit($s);
222
    }
223
224
    /**
225
     * Converts results in meters to user's unit (if any).
226
     * The default returned value is in meters.
227
     *
228
     * @param double $meters
229
     *
230
     * @return double
231
     */
232 47
    protected function convertToUserUnit($meters)
233
    {
234 47
        switch ($this->unit) {
235 47
            case GeotoolsInterface::KILOMETER_UNIT:
236 21
                return $meters / 1000;
237 42
            case GeotoolsInterface::MILE_UNIT:
238 24
                return $meters / GeotoolsInterface::METERS_PER_MILE;
239 34
            case GeotoolsInterface::FOOT_UNIT:
240 23
                return $meters / GeotoolsInterface::FEET_PER_METER;
241
            default:
242 27
                return $meters;
243
        }
244
    }
245
}
246