2018-10-11 00:25:40 +03:00
// Copyright 2018 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package geo
import (
"math"
)
const (
earthRadius = 6371e3
radians = math . Pi / 180
degrees = 180 / math . Pi
2018-10-27 19:23:29 +03:00
piR = math . Pi * earthRadius
twoPiR = 2 * piR
2018-10-11 00:25:40 +03:00
)
2018-10-27 19:23:29 +03:00
// Haversine ...
func Haversine ( latA , lonA , latB , lonB float64 ) float64 {
2018-10-11 00:25:40 +03:00
φ1 := latA * radians
λ1 := lonA * radians
φ2 := latB * radians
λ2 := lonB * radians
Δφ := φ2 - φ1
Δλ := λ2 - λ1
2018-10-27 19:23:29 +03:00
sΔφ2 := math . Sin ( Δφ / 2 )
sΔλ2 := math . Sin ( Δλ / 2 )
return sΔφ2 * sΔφ2 + math . Cos ( φ1 ) * math . Cos ( φ2 ) * sΔλ2 * sΔλ2
}
// NormalizeDistance ...
func NormalizeDistance ( meters float64 ) float64 {
m1 := math . Mod ( meters , twoPiR )
if m1 <= piR {
return m1
}
return twoPiR - m1
}
// DistanceToHaversine ...
func DistanceToHaversine ( meters float64 ) float64 {
// convert the given distance to its haversine
sin := math . Sin ( 0.5 * meters / earthRadius )
return sin * sin
}
2018-11-06 13:40:52 +03:00
// DistanceFromHaversine ...
2018-11-01 23:49:39 +03:00
func DistanceFromHaversine ( haversine float64 ) float64 {
return earthRadius * 2 * math . Asin ( math . Sqrt ( haversine ) )
}
// DistanceTo return the distance in meters between two point.
2018-10-27 19:23:29 +03:00
func DistanceTo ( latA , lonA , latB , lonB float64 ) ( meters float64 ) {
a := Haversine ( latA , lonA , latB , lonB )
2018-11-01 23:49:39 +03:00
return DistanceFromHaversine ( a )
2018-10-11 00:25:40 +03:00
}
// DestinationPoint return the destination from a point based on a
// distance and bearing.
func DestinationPoint ( lat , lon , meters , bearingDegrees float64 ) (
destLat , destLon float64 ,
) {
// see http://williams.best.vwh.net/avform.htm#LL
δ := meters / earthRadius // angular distance in radians
θ := bearingDegrees * radians
φ1 := lat * radians
λ1 := lon * radians
φ2 := math . Asin ( math . Sin ( φ1 ) * math . Cos ( δ ) +
math . Cos ( φ1 ) * math . Sin ( δ ) * math . Cos ( θ ) )
λ2 := λ1 + math . Atan2 ( math . Sin ( θ ) * math . Sin ( δ ) * math . Cos ( φ1 ) ,
math . Cos ( δ ) - math . Sin ( φ1 ) * math . Sin ( φ2 ) )
λ2 = math . Mod ( λ2 + 3 * math . Pi , 2 * math . Pi ) - math . Pi // normalise to -180..+180°
return φ2 * degrees , λ2 * degrees
}
// BearingTo returns the (initial) bearing from point 'A' to point 'B'.
func BearingTo ( latA , lonA , latB , lonB float64 ) float64 {
// tanθ = sinΔλ⋅cosφ2 / cosφ1⋅sinφ2 − sinφ1⋅cosφ2⋅cosΔλ
// see mathforum.org/library/drmath/view/55417.html for derivation
φ1 := latA * radians
φ2 := latB * radians
Δλ := ( lonB - lonA ) * radians
y := math . Sin ( Δλ ) * math . Cos ( φ2 )
x := math . Cos ( φ1 ) * math . Sin ( φ2 ) - math . Sin ( φ1 ) * math . Cos ( φ2 ) * math . Cos ( Δλ )
θ := math . Atan2 ( y , x )
return math . Mod ( θ * degrees + 360 , 360 )
}
2018-11-06 13:40:52 +03:00
// RectFromCenter calculates the bounding box surrounding a circle.
func RectFromCenter ( lat , lon , meters float64 ) (
minLat , minLon , maxLat , maxLon float64 ,
) {
// see http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#Latitude
lat *= radians
lon *= radians
r := meters / earthRadius // angular radius
minLat = lat - r
maxLat = lat + r
latT := math . Asin ( math . Sin ( lat ) / math . Cos ( r ) )
lonΔ := math . Acos ( ( math . Cos ( r ) - math . Sin ( latT ) * math . Sin ( lat ) ) / ( math . Cos ( latT ) * math . Cos ( lat ) ) )
minLon = lon - lonΔ
maxLon = lon + lonΔ
// Adjust for north poll
if maxLat > math . Pi / 2 {
minLon = - math . Pi
maxLat = math . Pi / 2
maxLon = math . Pi
}
// Adjust for south poll
if minLat < - math . Pi / 2 {
minLat = - math . Pi / 2
minLon = - math . Pi
maxLon = math . Pi
}
// Adjust for wraparound. Remove this if the commented-out condition below this block is added.
if minLon < - math . Pi || maxLon > math . Pi {
minLon = - math . Pi
maxLon = math . Pi
}
/ *
// Consider splitting area into two bboxes, using the below checks, and erasing above block for performance. See http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#PolesAnd180thMeridian
// Adjust for wraparound if minimum longitude is less than -180 degrees.
if lonMin < - math . Pi {
// box 1:
latMin = latMin
latMax = latMax
lonMin += 2 * math . Pi
lonMax = math . Pi
// box 2:
latMin = latMin
latMax = latMax
lonMin = - math . Pi
lonMax = lonMax
}
// Adjust for wraparound if maximum longitude is greater than 180 degrees.
if lonMax > math . Pi {
// box 1:
latMin = latMin
latMax = latMax
lonMin = lonMin
lonMax = - math . Pi
// box 2:
latMin = latMin
latMax = latMax
lonMin = - math . Pi
lonMax -= 2 * math . Pi
}
* /
minLon = math . Mod ( minLon + 3 * math . Pi , 2 * math . Pi ) - math . Pi // normalise to -180..+180°
maxLon = math . Mod ( maxLon + 3 * math . Pi , 2 * math . Pi ) - math . Pi
minLat *= degrees
minLon *= degrees
maxLat *= degrees
maxLon *= degrees
return
}