added vendor

This commit is contained in:
sm
2015-09-29 01:14:03 -07:00
commit 6662b1e058
9 changed files with 1090 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
Copyright (c) 2013 Kelly Dunn
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+150
View File
@@ -0,0 +1,150 @@
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
}
+138
View File
@@ -0,0 +1,138 @@
package geo
import (
"encoding/json"
"fmt"
"log"
"testing"
)
// Tests that a call to NewPoint should return a pointer to a Point with the specified values assigned correctly.
func TestNewPoint(t *testing.T) {
p := NewPoint(40.5, 120.5)
if p == nil {
t.Error("Expected to get a pointer to a new point, but got nil instead.")
}
if p.lat != 40.5 {
t.Errorf("Expected to be able to specify 40.5 as the lat value of a new point, but got %f instead", p.lat)
}
if p.lng != 120.5 {
t.Errorf("Expected to be able to specify 120.5 as the lng value of a new point, but got %f instead", p.lng)
}
}
// Tests that calling GetLat() after creating a new point returns the expected lat value.
func TestLat(t *testing.T) {
p := NewPoint(40.5, 120.5)
lat := p.Lat()
if lat != 40.5 {
t.Error("Expected a call to GetLat() to return the same lat value as was set before, but got %f instead", lat)
}
}
// Tests that calling GetLng() after creating a new point returns the expected lng value.
func TestLng(t *testing.T) {
p := NewPoint(40.5, 120.5)
lng := p.Lng()
if lng != 120.5 {
t.Error("Expected a call to GetLng() to return the same lat value as was set before, but got %f instead", lng)
}
}
// Seems brittle :\
func TestGreatCircleDistance(t *testing.T) {
// Test that SEA and SFO are ~ 1091km apart, accurate to 100 meters.
sea := &Point{lat: 47.4489, lng: -122.3094}
sfo := &Point{lat: 37.6160933, lng: -122.3924223}
sfoToSea := 1093.379199082169
dist := sea.GreatCircleDistance(sfo)
if !(dist < (sfoToSea+0.1) && dist > (sfoToSea-0.1)) {
t.Error("Unnacceptable result.", dist)
}
}
func TestPointAtDistanceAndBearing(t *testing.T) {
sea := &Point{lat: 47.44745785, lng: -122.308065668024}
p := sea.PointAtDistanceAndBearing(1090.7, 180)
// Expected results of transposing point
// ~1091km at bearing of 180 degrees
resultLat := 37.638557
resultLng := -122.308066
withinLatBounds := p.lat < resultLat+0.001 && p.lat > resultLat-0.001
withinLngBounds := p.lng < resultLng+0.001 && p.lng > resultLng-0.001
if !(withinLatBounds && withinLngBounds) {
t.Error("Unnacceptable result.", fmt.Sprintf("[%f, %f]", p.lat, p.lng))
}
}
func TestBearingTo(t *testing.T) {
p1 := &Point{lat: 40.7486, lng: -73.9864}
p2 := &Point{lat: 0.0, lng: 0.0}
bearing := p1.BearingTo(p2)
// Expected bearing 60 degrees
resultBearing := 100.610833
withinBearingBounds := bearing < resultBearing+0.001 && bearing > resultBearing-0.001
if !withinBearingBounds {
t.Error("Unnacceptable result.", fmt.Sprintf("%f", bearing))
}
}
func TestMidpointTo(t *testing.T) {
p1 := &Point{lat: 52.205, lng: 0.119}
p2 := &Point{lat: 48.857, lng: 2.351}
p := p1.MidpointTo(p2)
// Expected midpoint 50.5363°N, 001.2746°E
resultLat := 50.53632
resultLng := 1.274614
withinLatBounds := p.lat < resultLat+0.001 && p.lat > resultLat-0.001
withinLngBounds := p.lng < resultLng+0.001 && p.lng > resultLng-0.001
if !(withinLatBounds && withinLngBounds) {
t.Error("Unnacceptable result.", fmt.Sprintf("[%f, %f]", p.lat, p.lng))
}
}
// Enures that a point can be marhalled into JSON
func TestMarshalJSON(t *testing.T) {
p := NewPoint(40.7486, -73.9864)
res, err := json.Marshal(p)
if err != nil {
log.Print(err)
t.Error("Should not encounter an error when attempting to Marshal a Point to JSON")
}
if string(res) != `{"lat":40.7486,"lng":-73.9864}` {
t.Error("Point should correctly Marshal to JSON")
}
}
// Enures that a point can be unmarhalled from JSON
func TestUnmarshalJSON(t *testing.T) {
data := []byte(`{"lat":40.7486,"lng":-73.9864}`)
p := &Point{}
err := p.UnmarshalJSON(data)
if err != nil {
t.Errorf("Should not encounter an error when attempting to Unmarshal a Point from JSON")
}
if p.lat != 40.7486 || p.lng != -73.9864 {
t.Errorf("Point has mismatched data after Unmarshalling from JSON")
}
}