-
Notifications
You must be signed in to change notification settings - Fork 125
/
point.go
187 lines (143 loc) · 5.03 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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package geo
import (
"bytes"
"encoding/binary"
"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
)
// NewPoint 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}
}
// Lat returns Point p's latitude.
func (p *Point) Lat() float64 {
return p.lat
}
// Lng returns Point p's longitude.
func (p *Point) Lng() float64 {
return p.lng
}
// PointAtDistanceAndBearing 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}
}
// GreatCircleDistance: 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
}
// BearingTo: 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
}
// MidpointTo: 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)
}
// MarshalBinary renders the current point to a byte slice.
// Implements the encoding.BinaryMarshaler Interface.
func (p *Point) MarshalBinary() ([]byte, error) {
var buf bytes.Buffer
err := binary.Write(&buf, binary.LittleEndian, p.lat)
if err != nil {
return nil, fmt.Errorf("unable to encode lat %v: %v", p.lat, err)
}
err = binary.Write(&buf, binary.LittleEndian, p.lng)
if err != nil {
return nil, fmt.Errorf("unable to encode lng %v: %v", p.lng, err)
}
return buf.Bytes(), nil
}
func (p *Point) UnmarshalBinary(data []byte) error {
buf := bytes.NewReader(data)
var lat float64
err := binary.Read(buf, binary.LittleEndian, &lat)
if err != nil {
return fmt.Errorf("binary.Read failed: %v", err)
}
var lng float64
err = binary.Read(buf, binary.LittleEndian, &lng)
if err != nil {
return fmt.Errorf("binary.Read failed: %v", err)
}
p.lat = lat
p.lng = lng
return nil
}
// MarshalJSON 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
}
// UnmarshalJSON 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
}