Completed
Push — master ( 9405e1...3c4dfe )
by Marcus
02:01
created

BearingEllipsoidal::directVincenty()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 54
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 54
rs 9.0306
cc 4
eloc 41
nc 2
nop 3

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
 * Calculation of bearing between two points using a
4
 * ellipsoidal model of the earth
5
 *
6
 * This class is based on the awesome work Chris Veness
7
 * has done. For more information visit the following URL.
8
 *
9
 * @see http://www.movable-type.co.uk/scripts/latlong-vincenty.html
10
 *
11
 * @author   Marcus Jaschen <[email protected]>
12
 * @license  https://opensource.org/licenses/GPL-3.0 GPL
13
 * @link     https://github.com/mjaschen/phpgeo
14
 */
15
16
namespace Location\Bearing;
17
18
use Location\Coordinate;
19
use Location\Exception\NotConvergingException;
20
21
/**
22
 * Calculation of bearing between two points using a
23
 * ellipsoidal model of the earth
24
 *
25
 * @author   Marcus Jaschen <[email protected]>
26
 * @license  https://opensource.org/licenses/GPL-3.0 GPL
27
 * @link     https://github.com/mjaschen/phpgeo
28
 */
29
class BearingEllipsoidal implements BearingInterface
30
{
31
    /**
32
     * This method calculates the initial bearing between the
33
     * two points.
34
     *
35
     * @param \Location\Coordinate $point1
36
     * @param \Location\Coordinate $point2
37
     *
38
     * @return float Bearing Angle
39
     */
40
    public function calculateBearing(Coordinate $point1, Coordinate $point2)
41
    {
42
        return $this->inverseVincenty($point1, $point2)['bearing_initial'];
43
    }
44
45
    /**
46
     * Calculates the final bearing between the two points.
47
     *
48
     * @param \Location\Coordinate $point1
49
     * @param \Location\Coordinate $point2
50
     *
51
     * @return float
52
     */
53
    public function calculateFinalBearing(Coordinate $point1, Coordinate $point2)
54
    {
55
        return $this->inverseVincenty($point1, $point2)['bearing_final'];
56
    }
57
58
    /**
59
     * Calculates a destination point for the given point, bearing angle,
60
     * and distance.
61
     *
62
     * @param \Location\Coordinate $point
63
     * @param float $bearing the bearing angle between 0 and 360 degrees
64
     * @param float $distance the distance to the destination point in meters
65
     *
66
     * @return Coordinate
67
     */
68
    public function calculateDestination(Coordinate $point, $bearing, $distance)
69
    {
70
        return $this->directVincenty($point, $bearing, $distance)['destination'];
71
    }
72
73
    /**
74
     * Calculates the final bearing angle for a destination point.
75
     * The method expects a starting point point, the bearing angle,
76
     * and the distance to destination.
77
     *
78
     * @param \Location\Coordinate $point
79
     * @param float $bearing
80
     * @param float $distance
81
     *
82
     * @return float
83
     */
84
    public function calculateDestinationFinalBearing(Coordinate $point, $bearing, $distance)
85
    {
86
        return $this->directVincenty($point, $bearing, $distance)['bearing_final'];
87
    }
88
89
    private function directVincenty(Coordinate $point, $bearing, $distance)
90
    {
91
        $φ1 = deg2rad($point->getLat());
92
        $λ1 = deg2rad($point->getLng());
93
        $α1 = deg2rad($bearing);
94
95
        $a = $point->getEllipsoid()->getA();
96
        $b = $point->getEllipsoid()->getB();
97
        $f = 1 / $point->getEllipsoid()->getF();
98
99
        $sinα1 = sin($α1);
100
        $cosα1 = cos($α1);
101
102
        $tanU1  = (1 - $f) * tan($φ1);
103
        $cosU1  = 1 / sqrt(1 + $tanU1 * $tanU1);
104
        $sinU1  = $tanU1 * $cosU1;
105
        $σ1     = atan2($tanU1, $cosα1);
106
        $sinα   = $cosU1 * $sinα1;
107
        $cosSqα = 1 - $sinα * $sinα;
108
        $uSq    = $cosSqα * ($a * $a - $b * $b) / ($b * $b);
109
        $A      = 1 + $uSq / 16384 * (4096 + $uSq * (- 768 + $uSq * (320 - 175 * $uSq)));
110
        $B      = $uSq / 1024 * (256 + $uSq * (- 128 + $uSq * (74 - 47 * $uSq)));
111
112
        $σ          = $distance / ($b * $A);
113
        $iterations = 0;
114
115
        do {
116
            $cos2σm = cos(2 * $σ1 + $σ);
117
            $sinσ   = sin($σ);
118
            $cosσ   = cos($σ);
119
            $Δσ     = $B * $sinσ * ($cos2σm + $B / 4 * ($cosσ * (- 1 + 2 * $cos2σm * $cos2σm) - $B / 6 * $cos2σm * (- 3 + 4 * $sinσ * $sinσ) * (- 3 + 4 * $cos2σm * $cos2σm)));
120
            $σs     = $σ;
121
            $σ      = $distance / ($b * $A) + $Δσ;
122
        } while (abs($σ - $σs) > 1e-12 && ++ $iterations < 200);
123
124
        if ($iterations >= 200) {
125
            throw new NotConvergingException('Inverse Vincenty Formula did not converge');
126
        }
127
128
        $tmp = $sinU1 * $sinσ - $cosU1 * $cosσ * $cosα1;
129
        $φ2  = atan2($sinU1 * $cosσ + $cosU1 * $sinσ * $cosα1, (1 - $f) * sqrt($sinα * $sinα + $tmp * $tmp));
130
        $λ   = atan2($sinσ * $sinα1, $cosU1 * $cosσ - $sinU1 * $sinσ * $cosα1);
131
        $C   = $f / 16 * $cosSqα * (4 + $f * (4 - 3 * $cosSqα));
132
        $L   = $λ - (1 - $C) * $f * $sinα * ($σ + $C * $sinσ * ($cos2σm + $C * $cosσ * (- 1 + 2 * $cos2σm * $cos2σm)));
133
        $λ2  = fmod($λ1 + $L + 3 * M_PI, 2 * M_PI) - M_PI;
134
135
        $α2 = atan2($sinα, - $tmp);
136
        $α2 = fmod($α2 + 2 * M_PI, 2 * M_PI);
137
138
        return [
139
            'destination'   => new Coordinate(rad2deg($φ2), rad2deg($λ2), $point->getEllipsoid()),
140
            'bearing_final' => rad2deg($α2),
141
        ];
142
    }
143
144
    private function inverseVincenty(Coordinate $point1, Coordinate $point2)
145
    {
146
        $φ1 = deg2rad($point1->getLat());
147
        $φ2 = deg2rad($point2->getLat());
148
        $λ1 = deg2rad($point1->getLng());
149
        $λ2 = deg2rad($point2->getLng());
150
151
        $a = $point1->getEllipsoid()->getA();
152
        $b = $point1->getEllipsoid()->getB();
153
        $f = 1 / $point1->getEllipsoid()->getF();
154
155
        $L = $λ2 - $λ1;
156
157
        $tanU1 = (1 - $f) * tan($φ1);
158
        $cosU1 = 1 / sqrt(1 + $tanU1 * $tanU1);
159
        $sinU1 = $tanU1 * $cosU1;
160
        $tanU2 = (1 - $f) * tan($φ2);
161
        $cosU2 = 1 / sqrt(1 + $tanU2 * $tanU2);
162
        $sinU2 = $tanU2 * $cosU2;
163
164
        $λ = $L;
165
166
        $iterations = 0;
167
168 View Code Duplication
        do {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
169
            $sinλ   = sin($λ);
170
            $cosλ   = cos($λ);
171
            $sinSqσ = ($cosU2 * $sinλ) * ($cosU2 * $sinλ)
172
                      + ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosλ) * ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosλ);
173
            $sinσ   = sqrt($sinSqσ);
174
175
            if ($sinσ == 0) {
176
                return 0;
177
            }
178
179
            $cosσ   = $sinU1 * $sinU2 + $cosU1 * $cosU2 * $cosλ;
180
            $σ      = atan2($sinσ, $cosσ);
181
            $sinα   = $cosU1 * $cosU2 * $sinλ / $sinσ;
182
            $cosSqα = 1 - $sinα * $sinα;
183
184
            if ($cosSqα == 0.0) {
185
                $cos2σM = 0;
186
            } else {
187
                $cos2σM = $cosσ - 2 * $sinU1 * $sinU2 / $cosSqα;
188
            }
189
190
            $C  = $f / 16 * $cosSqα * (4 + $f * (4 - 3 * $cosSqα));
191
            $λp = $λ;
192
            $λ  = $L + (1 - $C) * $f * $sinα * ($σ + $C * $sinσ * ($cos2σM + $C * $cosσ * (- 1 + 2 * $cos2σM * $cos2σM)));
193
        } while (abs($λ - $λp) > 1e-12 && ++ $iterations < 200);
194
195
        if ($iterations >= 200) {
196
            throw new NotConvergingException('Inverse Vincenty Formula did not converge');
197
        }
198
199
        $uSq = $cosSqα * ($a * $a - $b * $b) / ($b * $b);
200
        $A   = 1 + $uSq / 16384 * (4096 + $uSq * (- 768 + $uSq * (320 - 175 * $uSq)));
201
        $B   = $uSq / 1024 * (256 + $uSq * (- 128 + $uSq * (74 - 47 * $uSq)));
202
        $Δσ  = $B * $sinσ * ($cos2σM + $B / 4 * ($cosσ * (- 1 + 2 * $cos2σM * $cos2σM) - $B / 6 * $cos2σM * (- 3 + 4 * $sinσ * $sinσ) * (- 3 + 4 * $cos2σM * $cos2σM)));
203
204
        $s = $b * $A * ($σ - $Δσ);
205
206
        $α1 = atan2($cosU2 * $sinλ, $cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosλ);
207
        $α2 = atan2($cosU1 * $sinλ, - $sinU1 * $cosU2 + $cosU1 * $sinU2 * $cosλ);
208
209
        $α1 = fmod($α1 + 2 * M_PI, 2 * M_PI);
210
        $α2 = fmod($α2 + 2 * M_PI, 2 * M_PI);
211
212
        $s = round($s, 3);
213
214
        return [
215
            'distance'        => $s,
216
            'bearing_initial' => rad2deg($α1),
217
            'bearing_final'   => rad2deg($α2),
218
        ];
219
    }
220
}
221