st/vendor/github.com/kellydunn/golang-geo/point.go
2015-09-29 01:14:03 -07:00

151 lines
4.0 KiB
Go

package geo
import (
"bytes"
"encoding/json"
"fmt"
"log"
"math"
)
// Represents a Physical Point in geographic notation [lat, lng].
type Point struct {
lat float64
lng float64
}
const (
// According to Wikipedia, the Earth's radius is about 6,371km
EARTH_RADIUS = 6371
)
// Returns a new Point populated by the passed in latitude (lat) and longitude (lng) values.
func NewPoint(lat float64, lng float64) *Point {
return &Point{lat: lat, lng: lng}
}
// Returns Point p's latitude.
func (p *Point) Lat() float64 {
return p.lat
}
// Returns Point p's longitude.
func (p *Point) Lng() float64 {
return p.lng
}
// Returns a Point populated with the lat and lng coordinates
// by transposing the origin point the passed in distance (in kilometers)
// by the passed in compass bearing (in degrees).
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
func (p *Point) PointAtDistanceAndBearing(dist float64, bearing float64) *Point {
dr := dist / EARTH_RADIUS
bearing = (bearing * (math.Pi / 180.0))
lat1 := (p.lat * (math.Pi / 180.0))
lng1 := (p.lng * (math.Pi / 180.0))
lat2_part1 := math.Sin(lat1) * math.Cos(dr)
lat2_part2 := math.Cos(lat1) * math.Sin(dr) * math.Cos(bearing)
lat2 := math.Asin(lat2_part1 + lat2_part2)
lng2_part1 := math.Sin(bearing) * math.Sin(dr) * math.Cos(lat1)
lng2_part2 := math.Cos(dr) - (math.Sin(lat1) * math.Sin(lat2))
lng2 := lng1 + math.Atan2(lng2_part1, lng2_part2)
lng2 = math.Mod((lng2+3*math.Pi), (2*math.Pi)) - math.Pi
lat2 = lat2 * (180.0 / math.Pi)
lng2 = lng2 * (180.0 / math.Pi)
return &Point{lat: lat2, lng: lng2}
}
// Calculates the Haversine distance between two points in kilometers.
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
func (p *Point) GreatCircleDistance(p2 *Point) float64 {
dLat := (p2.lat - p.lat) * (math.Pi / 180.0)
dLon := (p2.lng - p.lng) * (math.Pi / 180.0)
lat1 := p.lat * (math.Pi / 180.0)
lat2 := p2.lat * (math.Pi / 180.0)
a1 := math.Sin(dLat/2) * math.Sin(dLat/2)
a2 := math.Sin(dLon/2) * math.Sin(dLon/2) * math.Cos(lat1) * math.Cos(lat2)
a := a1 + a2
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return EARTH_RADIUS * c
}
// Calculates the initial bearing (sometimes referred to as forward azimuth)
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
func (p *Point) BearingTo(p2 *Point) float64 {
dLon := (p2.lng - p.lng) * math.Pi / 180.0
lat1 := p.lat * math.Pi / 180.0
lat2 := p2.lat * math.Pi / 180.0
y := math.Sin(dLon) * math.Cos(lat2)
x := math.Cos(lat1)*math.Sin(lat2) -
math.Sin(lat1)*math.Cos(lat2)*math.Cos(dLon)
brng := math.Atan2(y, x) * 180.0 / math.Pi
return brng
}
// Calculates the midpoint between 'this' point and the supplied point.
// Original implementation from http://www.movable-type.co.uk/scripts/latlong.html
func (p *Point) MidpointTo(p2 *Point) *Point {
lat1 := p.lat * math.Pi / 180.0
lat2 := p2.lat * math.Pi / 180.0
lon1 := p.lng * math.Pi / 180.0
dLon := (p2.lng - p.lng) * math.Pi / 180.0
bx := math.Cos(lat2) * math.Cos(dLon)
by := math.Cos(lat2) * math.Sin(dLon)
lat3Rad := math.Atan2(
math.Sin(lat1)+math.Sin(lat2),
math.Sqrt(math.Pow(math.Cos(lat1)+bx, 2)+math.Pow(by, 2)),
)
lon3Rad := lon1 + math.Atan2(by, math.Cos(lat1)+bx)
lat3 := lat3Rad * 180.0 / math.Pi
lon3 := lon3Rad * 180.0 / math.Pi
return NewPoint(lat3, lon3)
}
// Renders the current Point to valid JSON.
// Implements the json.Marshaller Interface.
func (p *Point) MarshalJSON() ([]byte, error) {
res := fmt.Sprintf(`{"lat":%v, "lng":%v}`, p.lat, p.lng)
return []byte(res), nil
}
// Decodes the current Point from a JSON body.
// Throws an error if the body of the point cannot be interpreted by the JSON body
func (p *Point) UnmarshalJSON(data []byte) error {
// TODO throw an error if there is an issue parsing the body.
dec := json.NewDecoder(bytes.NewReader(data))
var values map[string]float64
err := dec.Decode(&values)
if err != nil {
log.Print(err)
return err
}
*p = *NewPoint(values["lat"], values["lng"])
return nil
}