Skip to content

Commit fa5ad2b

Browse files
authored
Merge pull request #76 from syncromatics/master
Add functions to calculate points based on distance and bearing
2 parents e58bd12 + 1b88c0e commit fa5ad2b

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

geo/distance.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,50 @@ func Midpoint(p, p2 orb.Point) orb.Point {
6969

7070
return r
7171
}
72+
73+
// PointAtBearingAndDistance returns the point at the given bearing and distance in meters from the point
74+
func PointAtBearingAndDistance(p orb.Point, bearing, distance float64) orb.Point {
75+
aLat := deg2rad(p[1])
76+
aLon := deg2rad(p[0])
77+
78+
bearingRadians := deg2rad(bearing)
79+
80+
distanceRatio := distance / orb.EarthRadius
81+
bLat := math.Asin(math.Sin(aLat)*math.Cos(distanceRatio) + math.Cos(aLat)*math.Sin(distanceRatio)*math.Cos(bearingRadians))
82+
bLon := aLon + math.Atan2(math.Sin(bearingRadians)*math.Sin(distanceRatio)*math.Cos(aLat), math.Cos(distanceRatio)-math.Sin(aLat)*math.Sin(bLat))
83+
84+
r := orb.Point{
85+
rad2deg(bLon),
86+
rad2deg(bLat),
87+
}
88+
89+
return r
90+
}
91+
92+
func PointAtDistanceAlongLine(ls orb.LineString, distance float64) (orb.Point, float64) {
93+
if len(ls) == 0 {
94+
panic("empty LineString")
95+
}
96+
97+
if distance < 0 || len(ls) == 1 {
98+
return ls[0], 0.0
99+
}
100+
101+
travelled := 0.0
102+
i := 1
103+
var from, to orb.Point
104+
for ; i < len(ls); i++ {
105+
from = ls[i-1]
106+
to = ls[i]
107+
actualSegmentDistance := DistanceHaversine(from, to)
108+
expectedSegmentDistance := distance - travelled
109+
if expectedSegmentDistance < actualSegmentDistance {
110+
bearing := Bearing(from, to)
111+
return PointAtBearingAndDistance(from, bearing, expectedSegmentDistance), bearing
112+
}
113+
travelled += actualSegmentDistance
114+
}
115+
116+
bearing := Bearing(from, to)
117+
return to, bearing
118+
}

geo/distance_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,112 @@ func TestMidpoint(t *testing.T) {
8989
t.Errorf("expected %v, got %v", answer, m)
9090
}
9191
}
92+
93+
func TestPointAtBearingAndDistance(t *testing.T) {
94+
expected := orb.Point{-0.841153, 52.68179432}
95+
bearing := 127.373
96+
distance := 85194.89
97+
actual := PointAtBearingAndDistance(orb.Point{-1.8444, 53.1506}, bearing, distance)
98+
99+
if d := DistanceHaversine(actual, expected); d > 1 {
100+
t.Errorf("expected %v, got %v (%vm away)", expected, actual, d)
101+
}
102+
}
103+
func TestMidpointAgainstPointAtBearingAndDistance(t *testing.T) {
104+
a := orb.Point{-1.8444, 53.1506}
105+
b := orb.Point{0.1406, 52.2047}
106+
bearing := Bearing(a, b)
107+
distance := DistanceHaversine(a, b)
108+
acceptableTolerance := 1e-06 // unit is meters
109+
110+
p1 := PointAtBearingAndDistance(a, bearing, distance/2)
111+
p2 := Midpoint(a, b)
112+
113+
if d := DistanceHaversine(p1, p2); d > acceptableTolerance {
114+
t.Errorf("expected %v to be within %vm of %v", p1, acceptableTolerance, p2)
115+
}
116+
}
117+
118+
func TestPointAtDistanceAlongLineWithEmptyLineString(t *testing.T) {
119+
defer func() {
120+
if r := recover(); r == nil {
121+
t.Errorf("PointAtDistanceAlongLine did not panic")
122+
}
123+
}()
124+
125+
line := orb.LineString{}
126+
PointAtDistanceAlongLine(line, 90000)
127+
}
128+
129+
func TestPointAtDistanceAlongLineWithSinglePoint(t *testing.T) {
130+
expectedPoint := orb.Point{-1.8444, 53.1506}
131+
line := orb.LineString{
132+
expectedPoint,
133+
}
134+
actualPoint, actualBearing := PointAtDistanceAlongLine(line, 90000)
135+
136+
if actualPoint != expectedPoint {
137+
t.Errorf("expected %v but got %v", expectedPoint, actualPoint)
138+
}
139+
if actualBearing != 0.0 {
140+
t.Errorf("expected %v but got %v", actualBearing, 0.0)
141+
}
142+
}
143+
144+
func TestPointAtDistanceAlongLineWithMinimalPoints(t *testing.T) {
145+
expected := orb.Point{-0.841153, 52.68179432}
146+
acceptableDistanceTolerance := 1.0 // unit is meters
147+
line := orb.LineString{
148+
orb.Point{-1.8444, 53.1506},
149+
orb.Point{0.1406, 52.2047},
150+
}
151+
acceptableBearingTolerance := 0.01 // unit is degrees
152+
expectedBearing := Bearing(line[0], line[1])
153+
actual, actualBearing := PointAtDistanceAlongLine(line, 85194.89)
154+
155+
if d := DistanceHaversine(expected, actual); d > acceptableDistanceTolerance {
156+
t.Errorf("expected %v to be within %vm of %v (%vm away)", actual, acceptableDistanceTolerance, expected, d)
157+
}
158+
if b := math.Abs(actualBearing - expectedBearing); b > acceptableBearingTolerance {
159+
t.Errorf("expected bearing %v to be within %v degrees of %v", actualBearing, acceptableBearingTolerance, expectedBearing)
160+
}
161+
}
162+
163+
func TestPointAtDistanceAlongLineWithMultiplePoints(t *testing.T) {
164+
expected := orb.Point{-0.78526, 52.65506}
165+
acceptableTolerance := 1.0 // unit is meters
166+
line := orb.LineString{
167+
orb.Point{-1.8444, 53.1506},
168+
orb.Point{-0.8411, 52.6817},
169+
orb.Point{0.1406, 52.2047},
170+
}
171+
acceptableBearingTolerance := 0.01 // unit is degrees
172+
expectedBearing := Bearing(line[1], line[2])
173+
actualPoint, actualBearing := PointAtDistanceAlongLine(line, 90000)
174+
175+
if d := DistanceHaversine(expected, actualPoint); d > acceptableTolerance {
176+
t.Errorf("expected %v to be within %vm of %v (%vm away)", expected, acceptableTolerance, actualPoint, d)
177+
}
178+
if b := math.Abs(actualBearing - expectedBearing); b > acceptableBearingTolerance {
179+
t.Errorf("expected bearing %v to be within %v degrees of %v", actualBearing, acceptableBearingTolerance, expectedBearing)
180+
}
181+
}
182+
183+
func TestPointAtDistanceAlongLinePastEndOfLine(t *testing.T) {
184+
expected := orb.Point{0.1406, 52.2047}
185+
line := orb.LineString{
186+
orb.Point{-1.8444, 53.1506},
187+
orb.Point{-0.8411, 52.6817},
188+
expected,
189+
}
190+
acceptableBearingTolerance := 0.01 // unit is degrees
191+
expectedBearing := Bearing(line[1], line[2])
192+
actualPoint, actualBearing := PointAtDistanceAlongLine(line, 200000)
193+
194+
if actualPoint != expected {
195+
t.Errorf("expected %v but got %v", expected, actualPoint)
196+
}
197+
if b := math.Abs(actualBearing - expectedBearing); b > acceptableBearingTolerance {
198+
t.Errorf("expected bearing %v to be within %v degrees of %v", actualBearing, acceptableBearingTolerance, expectedBearing)
199+
}
200+
}

0 commit comments

Comments
 (0)