Completed
Push — master ( 520645...0aa38f )
by Antoine
03:23
created

Distance::vincenty()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 61
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 6.0006

Importance

Changes 4
Bugs 1 Features 1
Metric Value
c 4
b 1
f 1
dl 0
loc 61
ccs 38
cts 39
cp 0.9744
rs 8.6806
cc 6
eloc 43
nc 5
nop 0
crap 6.0006

How to fix   Long Method   

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