-
Notifications
You must be signed in to change notification settings - Fork 16
/
simplify.go
193 lines (173 loc) · 5.33 KB
/
simplify.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
188
189
190
191
192
193
package geom
import "math"
// Simplifier is an interface for types that can be simplified.
type Simplifier interface {
Simplify(tolerance float64) Geom
}
// Simplify simplifies p
// by removing points according to the tolerance parameter,
// while ensuring that the resulting shape is not self intersecting
// (but only if the input shape is not self intersecting). Self-intersecting
// polygons may cause the algorithm to fall into an infinite loop.
//
// It is based on the algorithm:
// J. L. G. Pallero, Robust line simplification on the plane.
// Comput. Geosci. 61, 152–159 (2013).
func (p Polygon) Simplify(tolerance float64) Geom {
var out Polygon = make([]Path, len(p))
for i, r := range p {
out[i] = simplifyCurve(r, p, tolerance)
}
return out
}
// Simplify simplifies mp
// by removing points according to the tolerance parameter,
// while ensuring that the resulting shape is not self intersecting
// (but only if the input shape is not self intersecting). Self-intersecting
// polygons may cause the algorithm to fall into an infinite loop.
//
// It is based on the algorithm:
// J. L. G. Pallero, Robust line simplification on the plane.
// Comput. Geosci. 61, 152–159 (2013).
func (mp MultiPolygon) Simplify(tolerance float64) Geom {
out := make(MultiPolygon, len(mp))
for i, p := range mp {
out[i] = p.Simplify(tolerance).(Polygon)
}
return out
}
// Simplify simplifies l
// by removing points according to the tolerance parameter,
// while ensuring that the resulting shape is not self intersecting
// (but only if the input shape is not self intersecting).
//
// It is based on the algorithm:
// J. L. G. Pallero, Robust line simplification on the plane.
// Comput. Geosci. 61, 152–159 (2013).
func (l LineString) Simplify(tolerance float64) Geom {
return LineString(simplifyCurve(Path(l), []Path{}, tolerance))
}
// Simplify simplifies ml
// by removing points according to the tolerance parameter,
// while ensuring that the resulting shape is not self intersecting
// (but only if the input shape is not self intersecting).
//
// It is based on the algorithm:
// J. L. G. Pallero, Robust line simplification on the plane.
// Comput. Geosci. 61, 152–159 (2013).
func (ml MultiLineString) Simplify(tolerance float64) Geom {
out := make(MultiLineString, len(ml))
for i, l := range ml {
out[i] = l.Simplify(tolerance).(LineString)
}
return out
}
func simplifyCurve(curve Path,
otherCurves []Path, tol float64) []Point {
out := make([]Point, 0, len(curve))
if len(curve) == 0 {
return nil
}
i := 0
for {
out = append(out, curve[i])
breakTime := false
for j := i + 2; j < len(curve); j++ {
breakTime2 := false
for k := i + 1; k < j; k++ {
d := distPointToSegment(curve[k], curve[i], curve[j])
if d > tol {
// we have found a candidate point to keep
for {
// Make sure this simplification doesn't cause any self
// intersections.
if j > i+2 &&
(segMakesNotSimple(curve[i], curve[j-1], []Path{out[0:i]}) ||
segMakesNotSimple(curve[i], curve[j-1], []Path{curve[j:]}) ||
segMakesNotSimple(curve[i], curve[j-1], otherCurves)) {
j--
} else {
i = j - 1
out = append(out, curve[i])
breakTime2 = true
break
}
}
}
if breakTime2 {
break
}
}
if j == len(curve)-1 {
// Add last point regardless of distance.
out = append(out, curve[j])
breakTime = true
}
}
if breakTime {
break
}
}
return out
}
func segMakesNotSimple(segStart, segEnd Point, paths []Path) bool {
seg1 := segment{segStart, segEnd}
for _, p := range paths {
for i := 0; i < len(p)-1; i++ {
seg2 := segment{p[i], p[i+1]}
if seg1.start == seg2.start || seg1.end == seg2.end ||
seg1.start == seg2.end || seg1.end == seg2.start {
// colocated endpoints are not a problem here
return false
}
numIntersections, _, _ := findIntersection(seg1, seg2)
if numIntersections > 0 {
return true
}
}
}
return false
}
// pointOnSegment calculates whether point p is exactly on the finite line segment
// defined by points l1 and l2.
func pointOnSegment(p, l1, l2 Point) bool {
if (p.X < l1.X && p.X < l2.X) || (p.X > l1.X && p.X > l2.X) ||
(p.Y < l1.Y && p.Y < l2.Y) || (p.Y > l1.Y && p.Y > l2.Y) {
return false
}
d1 := pointSubtract(l1, p)
d2 := pointSubtract(l2, l1)
// If the two slopes are the same, then the point is on the line
if (d1.X == 0 && d2.X == 0) || d1.Y/d1.X == d2.Y/d2.X {
return true
}
return false
}
// dist_Point_to_Segment(): get the distance of a point to a segment
// Input: a Point P and a Segment S (in any dimension)
// Return: the shortest distance from P to S
// from http://geomalgorithms.com/a02-_lines.html
func distPointToSegment(p, segStart, segEnd Point) float64 {
v := pointSubtract(segEnd, segStart)
w := pointSubtract(p, segStart)
c1 := dot(w, v)
if c1 <= 0. {
return d(p, segStart)
}
c2 := dot(v, v)
if c2 <= c1 {
return d(p, segEnd)
}
b := c1 / c2
pb := Point{segStart.X + b*v.X, segStart.Y + b*v.Y}
return d(p, pb)
}
func pointSubtract(p1, p2 Point) Point {
return Point{X: p1.X - p2.X, Y: p1.Y - p2.Y}
}
// dot product
func dot(u, v Point) float64 { return u.X*v.X + u.Y*v.Y }
// norm = length of vector
func norm(v Point) float64 { return math.Sqrt(dot(v, v)) }
// distance = norm of difference
func d(u, v Point) float64 { return norm(pointSubtract(u, v)) }