forked from kellydunn/golang-geo
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpoint.go
150 lines (112 loc) · 4.02 KB
/
point.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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
}