@@ -32,9 +32,6 @@ import kotlin.math.sin
3232import kotlin.math.sqrt
3333import kotlin.math.tan
3434
35- private fun Double.toRadians () = this * (PI / 180.0 )
36- private fun Double.toDegrees () = this * (180.0 / PI )
37-
3835object SphericalUtil {
3936 /* *
4037 * Returns the heading from one LatLng to another LatLng. Headings are
@@ -51,7 +48,8 @@ object SphericalUtil {
5148
5249 // Breaking the formula down into Y and X components for atan2().
5350 val y = sin(deltaLngRad) * cos(toLatRad)
54- val x = cos(fromLatRad) * sin(toLatRad) - sin(fromLatRad) * cos(toLatRad) * cos(deltaLngRad)
51+ val x = cos(fromLatRad) * sin(toLatRad) -
52+ sin(fromLatRad) * cos(toLatRad) * cos(deltaLngRad)
5553
5654 val headingRad = atan2(y, x)
5755
@@ -68,23 +66,26 @@ object SphericalUtil {
6866 */
6967 @JvmStatic
7068 fun computeOffset (from : LatLng , distance : Double , heading : Double ): LatLng {
71- var distance = distance
72- var heading = heading
73- distance / = EARTH_RADIUS
74- heading = Math .toRadians(heading)
75- // http://williams.best.vwh.net/avform.htm#LL
76- val fromLat = Math .toRadians(from.latitude)
77- val fromLng = Math .toRadians(from.longitude)
78- val cosDistance = cos(distance)
79- val sinDistance = sin(distance)
80- val sinFromLat = sin(fromLat)
81- val cosFromLat = cos(fromLat)
82- val sinLat = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading)
83- val dLng = atan2(
84- sinDistance * cosFromLat * sin(heading),
85- cosDistance - sinFromLat * sinLat
86- )
87- return LatLng (Math .toDegrees(asin(sinLat)), Math .toDegrees(fromLng + dLng))
69+ val distanceRad = distance / EARTH_RADIUS
70+ val headingRad = heading.toRadians()
71+
72+ val (fromLatRad, fromLngRad) = from.toRadians()
73+
74+ val cosDistance = cos(distanceRad)
75+ val sinDistance = sin(distanceRad)
76+ val sinFromLat = sin(fromLatRad)
77+ val cosFromLat = cos(fromLatRad)
78+
79+ val sinToLat = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(headingRad)
80+ val toLatRad = asin(sinToLat)
81+
82+ val y = sin(headingRad) * sinDistance * cosFromLat
83+ val x = cosDistance - sinFromLat * sinToLat
84+ val dLngRad = atan2(y, x)
85+
86+ val toLngRad = fromLngRad + dLngRad
87+
88+ return LatLng (toLatRad.toDegrees(), toLngRad.toDegrees())
8889 }
8990
9091 /* *
@@ -99,15 +100,13 @@ object SphericalUtil {
99100 */
100101 @JvmStatic
101102 fun computeOffsetOrigin (to : LatLng , distance : Double , heading : Double ): LatLng ? {
102- var distance = distance
103- var heading = heading
104- heading = Math .toRadians(heading)
105- distance / = EARTH_RADIUS
103+ val headingRad = heading.toRadians()
104+ val distanceRad = distance / EARTH_RADIUS
106105 // http://lists.maptools.org/pipermail/proj/2008-October/003939.html
107- val n1 = cos(distance )
108- val n2 = sin(distance ) * cos(heading )
109- val n3 = sin(distance ) * sin(heading )
110- val n4 = sin(Math .toRadians( to.latitude))
106+ val n1 = cos(distanceRad )
107+ val n2 = sin(distanceRad ) * cos(headingRad )
108+ val n3 = sin(distanceRad ) * sin(headingRad )
109+ val n4 = sin(to.latitude.toRadians( ))
111110 // There are two solutions for b. b = n2 * n4 +/- sqrt(), one solution results
112111 // in the latitude outside the [-90, 90] range. We first try one solution and
113112 // back off to the other if we are outside that range.
@@ -130,9 +129,9 @@ object SphericalUtil {
130129 // No solution which would make sense in LatLng-space.
131130 return null
132131 }
133- val fromLngRadians = Math .toRadians( to.longitude) -
132+ val fromLngRadians = to.longitude.toRadians( ) -
134133 atan2(n3, n1 * cos(fromLatRadians) - n2 * sin(fromLatRadians))
135- return LatLng (Math .toDegrees(fromLatRadians ), Math .toDegrees(fromLngRadians ))
134+ return LatLng (fromLatRadians .toDegrees(), fromLngRadians .toDegrees())
136135 }
137136
138137 /* *
@@ -147,17 +146,17 @@ object SphericalUtil {
147146 @JvmStatic
148147 fun interpolate (from : LatLng , to : LatLng , fraction : Double ): LatLng {
149148 // http://en.wikipedia.org/wiki/Slerp
150- val fromLat = Math .toRadians(from.latitude)
151- val fromLng = Math .toRadians(from.longitude)
152- val toLat = Math .toRadians(to.latitude)
153- val toLng = Math .toRadians(to.longitude)
154- val cosFromLat = cos(fromLat)
155- val cosToLat = cos(toLat)
149+ val (fromLatRad, fromLngRad) = from.toRadians()
150+ val (toLatRad, toLngRad) = to.toRadians()
151+
152+ val cosFromLat = cos(fromLatRad)
153+ val cosToLat = cos(toLatRad)
156154
157155 // Computes Spherical interpolation coefficients.
158156 val angle = computeAngleBetween(from, to)
159157 val sinAngle = sin(angle)
160158 if (sinAngle < 1E- 6 ) {
159+ // Fall back to linear interpolation for very small angles.
161160 return LatLng (
162161 from.latitude + fraction * (to.latitude - from.latitude),
163162 from.longitude + fraction * (to.longitude - from.longitude)
@@ -167,14 +166,15 @@ object SphericalUtil {
167166 val b = sin(fraction * angle) / sinAngle
168167
169168 // Converts from polar to vector and interpolate.
170- val x = a * cosFromLat * cos(fromLng ) + b * cosToLat * cos(toLng )
171- val y = a * cosFromLat * sin(fromLng ) + b * cosToLat * sin(toLng )
172- val z = a * sin(fromLat ) + b * sin(toLat )
169+ val x = a * cosFromLat * cos(fromLngRad ) + b * cosToLat * cos(toLngRad )
170+ val y = a * cosFromLat * sin(fromLngRad ) + b * cosToLat * sin(toLngRad )
171+ val z = a * sin(fromLatRad ) + b * sin(toLatRad )
173172
174173 // Converts interpolated vector back to polar.
175- val lat = atan2(z, sqrt(x * x + y * y))
176- val lng = atan2(y, x)
177- return LatLng (Math .toDegrees(lat), Math .toDegrees(lng))
174+ val latRad = atan2(z, sqrt(x * x + y * y))
175+ val lngRad = atan2(y, x)
176+
177+ return LatLng (latRad.toDegrees(), lngRad.toDegrees())
178178 }
179179
180180 /* *
@@ -184,7 +184,7 @@ object SphericalUtil {
184184 arcHav(havDistance(lat1, lat2, lng1 - lng2))
185185
186186 /* *
187- * Returns the angle between two LatLngs , in radians. This is the same as the distance
187+ * Returns the angle between two [LatLng]s , in radians. This is the same as the distance
188188 * on the unit sphere.
189189 */
190190 @JvmStatic
@@ -194,10 +194,11 @@ object SphericalUtil {
194194 )
195195
196196 /* *
197- * Returns the distance between two LatLngs , in meters.
197+ * Returns the distance between two [LatLng]s , in meters.
198198 */
199199 @JvmStatic
200- fun computeDistanceBetween (from : LatLng , to : LatLng ) = computeAngleBetween(from, to) * EARTH_RADIUS
200+ fun computeDistanceBetween (from : LatLng , to : LatLng ) =
201+ computeAngleBetween(from, to) * EARTH_RADIUS
201202
202203 /* *
203204 * Returns the length of the given path, in meters, on Earth.
@@ -207,19 +208,16 @@ object SphericalUtil {
207208 if (path.size < 2 ) {
208209 return 0.0
209210 }
210- var length = 0.0
211- var prev: LatLng ? = null
212- for (point in path) {
213- if (prev != null ) {
214- val prevLat = Math .toRadians(prev.latitude)
215- val prevLng = Math .toRadians(prev.longitude)
216- val lat = Math .toRadians(point.latitude)
217- val lng = Math .toRadians(point.longitude)
218- length + = distanceRadians(prevLat, prevLng, lat, lng)
219- }
220- prev = point
211+
212+ // Using zipWithNext() is a more functional and idiomatic way to handle
213+ // adjacent pairs in a collection. We then sum the distances between each pair.
214+ val totalDistance = path.zipWithNext().sumOf { (prev, point) ->
215+ val (prevLatRad, prevLngRad) = prev.toRadians()
216+ val (latRad, lngRad) = point.toRadians()
217+ distanceRadians(prevLatRad, prevLngRad, latRad, lngRad)
221218 }
222- return length * EARTH_RADIUS
219+
220+ return totalDistance * EARTH_RADIUS
223221 }
224222
225223 /* *
@@ -242,31 +240,33 @@ object SphericalUtil {
242240 @JvmStatic
243241 fun computeSignedArea (path : Polygon ) = computeSignedArea(path, EARTH_RADIUS )
244242
243+
245244 /* *
246245 * Returns the signed area of a closed path on a sphere of given radius.
247246 * The computed area uses the same units as the radius squared.
248247 * Used by SphericalUtilTest.
249248 */
250249 @JvmStatic
251- fun computeSignedArea (path : Polygon , radius : Double ): Double {
252- val size = path.size
253- if (size < 3 ) {
250+ fun computeSignedArea (path : List <LatLng >, radius : Double ): Double {
251+ if (path.size < 3 ) {
254252 return 0.0
255253 }
256- var total = 0.0
257- val prev = path[size - 1 ]
258- var prevTanLat = tan(( PI / 2 - prev.latitude.toRadians()) / 2 )
259- var prevLng = prev.longitude.toRadians()
254+
255+ // Create a closed path by appending the first point at the end.
256+ val closedPath = path + path.first( )
257+
260258 // For each edge, accumulate the signed area of the triangle formed by the North Pole
261259 // and that edge ("polar triangle").
262- for (point in path) {
260+ // `zipWithNext` creates pairs of consecutive vertices, representing the edges of the polygon.
261+ val totalArea = closedPath.zipWithNext { prev, point ->
262+ val prevTanLat = tan((PI / 2 - prev.latitude.toRadians()) / 2 )
263263 val tanLat = tan((PI / 2 - point.latitude.toRadians()) / 2 )
264+ val prevLng = prev.longitude.toRadians()
264265 val lng = point.longitude.toRadians()
265- total + = polarTriangleArea(tanLat, lng, prevTanLat, prevLng)
266- prevTanLat = tanLat
267- prevLng = lng
268- }
269- return total * (radius * radius)
266+ polarTriangleArea(tanLat, lng, prevTanLat, prevLng)
267+ }.sum()
268+
269+ return totalArea * (radius * radius)
270270 }
271271
272272 /* *
@@ -281,4 +281,12 @@ object SphericalUtil {
281281 val t = tan1 * tan2
282282 return 2 * atan2(t * sin(deltaLng), 1 + t * cos(deltaLng))
283283 }
284- }
284+ }
285+
286+ /* *
287+ * Helper extension function to convert a LatLng to a pair of radians.
288+ */
289+ private fun LatLng.toRadians () = Pair (latitude.toRadians(), longitude.toRadians())
290+
291+ private fun Double.toRadians () = this * (PI / 180.0 )
292+ private fun Double.toDegrees () = this * (180.0 / PI )
0 commit comments