forked from go-spatial/geom
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgeom.go
335 lines (270 loc) · 6.84 KB
/
geom.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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
// Package geom describes geometry interfaces.
package geom
import (
"math"
"reflect"
)
// Geometry is an object with a spatial reference.
// if a method accepts a Geometry type it's only expected to support the geom types in this package
type Geometry interface{}
// Pointer is a point with two dimensions.
type Pointer interface {
Geometry
XY() [2]float64
}
// MultiPointer is a geometry with multiple points.
type MultiPointer interface {
Geometry
Points() [][2]float64
}
// LineStringer is a line of two or more points.
type LineStringer interface {
Geometry
Verticies() [][2]float64
}
// MultiLineStringer is a geometry with multiple LineStrings.
type MultiLineStringer interface {
Geometry
LineStrings() [][][2]float64
}
// Polygoner is a geometry consisting of multiple Linear Rings.
// There must be only one exterior LineString with a clockwise winding order.
// There may be one or more interior LineStrings with a counterclockwise winding orders.
// It is assumed that the last point is connected to the first point, and the first point is NOT duplicated at the end.
type Polygoner interface {
Geometry
LinearRings() [][][2]float64
}
// MultiPolygoner is a geometry of multiple polygons.
type MultiPolygoner interface {
Geometry
Polygons() [][][][2]float64
}
// Collectioner is a collections of different geometries.
type Collectioner interface {
Geometry
Geometries() []Geometry
}
// getCoordinates is a helper function for GetCoordinates to avoid too many
// array copies and still provide a convenient interface to the user.
func getCoordinates(g Geometry, pts *[]Point) error {
switch gg := g.(type) {
default:
return ErrUnknownGeometry{g}
case Pointer:
*pts = append(*pts, Point(gg.XY()))
return nil
case MultiPointer:
mpts := gg.Points()
for i := range mpts {
*pts = append(*pts, Point(mpts[i]))
}
return nil
case LineStringer:
mpts := gg.Verticies()
for i := range mpts {
*pts = append(*pts, Point(mpts[i]))
}
return nil
case MultiLineStringer:
for _, ls := range gg.LineStrings() {
if err := getCoordinates(LineString(ls), pts); err != nil {
return err
}
}
return nil
case Polygoner:
for _, ls := range gg.LinearRings() {
if err := getCoordinates(LineString(ls), pts); err != nil {
return err
}
}
return nil
case MultiPolygoner:
for _, p := range gg.Polygons() {
if err := getCoordinates(Polygon(p), pts); err != nil {
return err
}
}
return nil
case Collectioner:
for _, child := range gg.Geometries() {
if err := getCoordinates(child, pts); err != nil {
return err
}
}
return nil
}
}
/*
GetCoordinates return a list of points that make up a geometry. This makes no
attempt to remove duplicate points.
*/
func GetCoordinates(g Geometry) (pts []Point, err error) {
// recursively retrieve points.
err = getCoordinates(g, &pts)
return pts, err
}
// getExtent is a helper function to efficiently build an Extent without
// collecting all coordinates first.
func getExtent(g Geometry, e *Extent) error {
switch gg := g.(type) {
default:
return ErrUnknownGeometry{g}
case Pointer:
e.AddPoints(gg.XY())
return nil
case MultiPointer:
e.AddPoints(gg.Points()...)
return nil
case LineStringer:
e.AddPoints(gg.Verticies()...)
return nil
case MultiLineStringer:
for _, ls := range gg.LineStrings() {
if err := getExtent(LineString(ls), e); err != nil {
return err
}
}
return nil
case Polygoner:
for _, ls := range gg.LinearRings() {
if err := getExtent(LineString(ls), e); err != nil {
return err
}
}
return nil
case MultiPolygoner:
for _, p := range gg.Polygons() {
if err := getExtent(Polygon(p), e); err != nil {
return err
}
}
return nil
case Collectioner:
for _, child := range gg.Geometries() {
if err := getExtent(child, e); err != nil {
return err
}
}
return nil
}
}
// extractLines is a helper function for ExtractLines to avoid too many
// array copies and still provide a convenient interface to the user.
func extractLines(g Geometry, lines *[]Line) error {
switch gg := g.(type) {
default:
return ErrUnknownGeometry{g}
case Pointer:
return nil
case MultiPointer:
return nil
case LineStringer:
v := gg.Verticies()
for i := 0; i < len(v)-1; i++ {
*lines = append(*lines, Line{v[i], v[i+1]})
}
return nil
case MultiLineStringer:
for _, ls := range gg.LineStrings() {
if err := extractLines(LineString(ls), lines); err != nil {
return err
}
}
return nil
case Polygoner:
for _, v := range gg.LinearRings() {
lr := LineString(v)
if err := extractLines(lr, lines); err != nil {
return err
}
v := lr.Verticies()
if len(v) > 2 && lr.IsRing() == false {
// create a connection from last -> first if it doesn't exist
*lines = append(*lines, Line{v[len(v)-1], v[0]})
}
}
return nil
case MultiPolygoner:
for _, p := range gg.Polygons() {
if err := extractLines(Polygon(p), lines); err != nil {
return err
}
}
return nil
case Collectioner:
for _, child := range gg.Geometries() {
if err := extractLines(child, lines); err != nil {
return err
}
}
return nil
}
}
// ExtractLines extracts all linear components from a geometry (line segements).
// If the geometry contains no line segements (e.g. empty geometry or
// point), then an empty array will be returned.
//
// Duplicate lines will not be removed.
func ExtractLines(g Geometry) (lines []Line, err error) {
err = extractLines(g, &lines)
return lines, err
}
// helper function to check it the given interface is nil, or the
// value store in it is nil
func isNil(a interface{}) bool {
defer func() { recover() }()
return a == nil || reflect.ValueOf(a).IsNil()
}
// IsEmpty returns if the geometry represents an empty geometry
func IsEmpty(geo Geometry) bool {
if isNil(geo) {
return true
}
switch g := geo.(type) {
case Point:
return g[0] == nan && g[1] == nan
case Pointer:
xy := g.XY()
return xy[0] == nan && xy[1] == nan
case LineString:
return len(g) == 0
case LineStringer:
return len(g.Verticies()) == 0
case Polygon:
return len(g) == 0
case Polygoner:
return len(g.LinearRings()) == 0
case MultiPoint:
return len(g) == 0
case MultiPointer:
return len(g.Points()) == 0
case MultiLineString:
return len(g) == 0
case MultiLineStringer:
return len(g.LineStrings()) == 0
case MultiPolygon:
return len(g) == 0
case MultiPolygoner:
return len(g.Polygons()) == 0
case Collection:
return len(g) == 0
case Collectioner:
return len(g.Geometries()) == 0
default:
return true
}
}
// RoundToPrec will round the given value to the precision value.
// The precision value should be a power of 10.
func RoundToPrec(v float64, prec int) float64 {
if v == -0.0 {
return 0.0
}
if prec == 0 {
return math.Round(v)
}
RoundingFactor := math.Pow10(prec)
return math.Round(v*RoundingFactor) / RoundingFactor
}