forked from paulmach/orb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
line_string.go
138 lines (113 loc) · 3.65 KB
/
line_string.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
// Package resample has a couple functions for resampling line geometry
// into more or less evenly spaces points.
package resample
import (
"github.com/paulmach/orb"
)
// Resample converts the line string into totalPoints-1 evenly spaced segments.
// This function will modify the linestring input.
func Resample(ls orb.LineString, df orb.DistanceFunc, totalPoints int) orb.LineString {
if totalPoints <= 0 {
return nil
}
ls, ret := resampleEdgeCases(ls, totalPoints)
if ret {
return ls
}
// precomputes the total distance and intermediate distances
total, dists := precomputeDistances(ls, df)
return resample(ls, dists, total, totalPoints)
}
// ToInterval coverts the line string into evenly spaced points of
// about the given distance.
// This function will modify the linestring input.
func ToInterval(ls orb.LineString, df orb.DistanceFunc, dist float64) orb.LineString {
if dist <= 0 {
return nil
}
// precomputes the total distance and intermediate distances
total, dists := precomputeDistances(ls, df)
totalPoints := int(total/dist) + 1
ls, ret := resampleEdgeCases(ls, totalPoints)
if ret {
return ls
}
return resample(ls, dists, total, totalPoints)
}
func resample(ls orb.LineString, dists []float64, totalDistance float64, totalPoints int) orb.LineString {
points := make([]orb.Point, 1, totalPoints)
points[0] = ls[0] // start stays the same
step := 1
dist := 0.0
currentDistance := totalDistance / float64(totalPoints-1)
// declare here and update had nice performance benefits need to retest
currentSeg := [2]orb.Point{}
for i := 0; i < len(ls)-1; i++ {
currentSeg[0] = ls[i]
currentSeg[1] = ls[i+1]
currentSegDistance := dists[i]
nextDistance := dist + currentSegDistance
for currentDistance <= nextDistance {
// need to add a point
percent := (currentDistance - dist) / currentSegDistance
points = append(points, orb.Point{
currentSeg[0][0] + percent*(currentSeg[1][0]-currentSeg[0][0]),
currentSeg[0][1] + percent*(currentSeg[1][1]-currentSeg[0][1]),
})
// move to the next distance we want
step++
currentDistance = totalDistance * float64(step) / float64(totalPoints-1)
if step == totalPoints-1 { // weird round off error on my machine
currentDistance = totalDistance
}
}
// past the current point in the original segment, so move to the next one
dist = nextDistance
}
// end stays the same, to handle round off errors
if totalPoints != 1 { // for 1, we want the first point
points[totalPoints-1] = ls[len(ls)-1]
}
return orb.LineString(points)
}
// resampleEdgeCases is used to handle edge case for
// resampling like not enough points and the line string is all the same point.
// will return nil if there are no edge cases. If return true if
// one of these edge cases was found and handled.
func resampleEdgeCases(ls orb.LineString, totalPoints int) (orb.LineString, bool) {
// degenerate case
if len(ls) <= 1 {
return ls, true
}
// if all the points are the same, treat as special case.
equal := true
for _, point := range ls {
if !ls[0].Equal(point) {
equal = false
break
}
}
if equal {
if totalPoints > len(ls) {
// extend to be requested length
for len(ls) != totalPoints {
ls = append(ls, ls[0])
}
return ls, true
}
// contract to be requested length
ls = ls[:totalPoints]
return ls, true
}
return ls, false
}
// precomputeDistances precomputes the total distance and intermediate distances.
func precomputeDistances(ls orb.LineString, df orb.DistanceFunc) (float64, []float64) {
total := 0.0
dists := make([]float64, len(ls)-1)
for i := 0; i < len(ls)-1; i++ {
dists[i] = df(ls[i], ls[i+1])
total += dists[i]
}
return total, dists
}