-
-
Notifications
You must be signed in to change notification settings - Fork 38
/
bbox.go
389 lines (346 loc) · 9.34 KB
/
bbox.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
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
package geom
import (
"math"
)
// Extenter represents an interface that returns a boundbox.
type Extenter interface {
Extent() (extent [4]float64)
}
type PtMinMaxer interface {
// Min returns the minimum x and y values
Min() Point
// Max returns the maximum x and y values
Max() Point
}
// MinMaxer is a wrapper for an Extent that gets min/max of the extent
type MinMaxer interface {
MinX() float64
MinY() float64
MaxX() float64
MaxY() float64
}
// Extent represents the minx, miny, maxx and maxy
// A nil extent represents the whole universe.
type Extent [4]float64
/* ========================= ATTRIBUTES ========================= */
// Vertices return the vertices of the Bounding Box. The vertices are ordered in the following maner.
// (minx,miny), (maxx,miny), (maxx,maxy), (minx,maxy)
func (e *Extent) Vertices() [][2]float64 {
return [][2]float64{
{e.MinX(), e.MinY()},
{e.MaxX(), e.MinY()},
{e.MaxX(), e.MaxY()},
{e.MinX(), e.MaxY()},
}
}
// ClockwiseFunc returns weather the set of points should be considered clockwise or counterclockwise. The last point is not the same as the first point, and the function should connect these points as needed.
type ClockwiseFunc func(...[2]float64) bool
// Edges returns the clockwise order of the edges that make up the extent.
func (e *Extent) Edges(cwfn ClockwiseFunc) [][2][2]float64 {
v := e.Vertices()
if cwfn != nil && !cwfn(v...) {
v[0], v[1], v[2], v[3] = v[3], v[2], v[1], v[0]
}
return [][2][2]float64{
{v[0], v[1]},
{v[1], v[2]},
{v[2], v[3]},
{v[3], v[0]},
}
}
// MaxX is the largest of the x values.
func (e *Extent) MaxX() float64 {
if e == nil {
return math.MaxFloat64
}
return e[2]
}
// MinX is the smallest of the x values.
func (e *Extent) MinX() float64 {
if e == nil {
return -math.MaxFloat64
}
return e[0]
}
// MaxY is the largest of the y values.
func (e *Extent) MaxY() float64 {
if e == nil {
return math.MaxFloat64
}
return e[3]
}
// MinY is the smallest of the y values.
func (e *Extent) MinY() float64 {
if e == nil {
return -math.MaxFloat64
}
return e[1]
}
// Min returns the (MinX, MinY) values
func (e *Extent) Min() Point {
return Point{e[0], e[1]}
}
// Max returns the (MaxX, MaxY) values
func (e *Extent) Max() Point {
return Point{e[2], e[3]}
}
// XSpan is the distance of the Extent in X or inf
// TODO (gdey): look at how to have this function take into account the dpi.
func (e *Extent) XSpan() float64 {
if e == nil {
return math.Inf(1)
}
return e[2] - e[0]
}
// YSpan is the distance of the Extent in Y or Inf
func (e *Extent) YSpan() float64 {
if e == nil {
return math.Inf(1)
}
return e[3] - e[1]
}
// Extent returns back the min and max of the Extent
func (e *Extent) Extent() [4]float64 {
return [4]float64{e.MinX(), e.MinY(), e.MaxX(), e.MaxY()}
}
/* ========================= EXPANDING BOUNDING BOX ========================= */
// Add will expand the extent to contain the given extent.
func (e *Extent) Add(extent MinMaxer) {
if e == nil {
return
}
e[0] = math.Min(e[0], extent.MinX())
e[2] = math.Max(e[2], extent.MaxX())
e[1] = math.Min(e[1], extent.MinY())
e[3] = math.Max(e[3], extent.MaxY())
}
// AddPoints will expand the extent to contain the given points.
func (e *Extent) AddPoints(points ...[2]float64) {
// A nil extent is all encompassing.
if e == nil {
return
}
if len(points) == 0 {
return
}
for _, pt := range points {
e[0] = math.Min(pt[0], e[0])
e[1] = math.Min(pt[1], e[1])
e[2] = math.Max(pt[0], e[2])
e[3] = math.Max(pt[1], e[3])
}
}
// AddPointers will expand the Extent if a point is outside it
func (e *Extent) AddPointers(pts ...Pointer) {
for i := range pts {
e.AddPoints(pts[i].XY())
}
}
// AddGeometry expands the specified envelop to contain g.
func (e *Extent) AddGeometry(g Geometry) error {
return getExtent(g, e)
}
// AsPolygon will return the extent as a Polygon
func (e *Extent) AsPolygon() Polygon { return Polygon{e.Vertices()} }
// Area returns the area of the extent, if the extent is nil, it will return 0
func (e *Extent) Area() float64 {
return math.Abs((e.MaxY() - e.MinY()) * (e.MaxX() - e.MinX()))
}
// NewExtent returns an Extent for the provided points; in following format [4]float64{ MinX, MinY, MaxX, MaxY }
func NewExtent(points ...[2]float64) *Extent {
var xy [2]float64
if len(points) == 0 {
return nil
}
extent := Extent{points[0][0], points[0][1], points[0][0], points[0][1]}
if len(points) == 1 {
return &extent
}
for i := 1; i < len(points); i++ {
xy = points[i]
// Check the x coords
switch {
case xy[0] < extent[0]:
extent[0] = xy[0]
case xy[0] > extent[2]:
extent[2] = xy[0]
}
// Check the y coords
switch {
case xy[1] < extent[1]:
extent[1] = xy[1]
case xy[1] > extent[3]:
extent[3] = xy[1]
}
}
return &extent
}
// NewExtentFromPoints returns an Extent for the provided points; in following format [4]float64{ MinX, MinY, MaxX, MaxY }
func NewExtentFromPoints(points ...Point) *Extent {
if len(points) == 0 {
return nil
}
extent := Extent{points[0][0], points[0][1], points[0][0], points[0][1]}
if len(points) == 1 {
return &extent
}
for _, pt := range points[1:] {
// Check the x coords
switch {
case pt[0] < extent[0]:
extent[0] = pt[0]
case pt[0] > extent[2]:
extent[2] = pt[0]
}
// Check the y coords
switch {
case pt[1] < extent[1]:
extent[1] = pt[1]
case pt[1] > extent[3]:
extent[3] = pt[1]
}
}
return &extent
}
// NewExtentFromGeometry tries to create an extent based on the geometry
func NewExtentFromGeometry(g Geometry) (*Extent, error) {
var pts []Point
if err := getCoordinates(g, &pts); err != nil {
return nil, err
}
if len(pts) == 0 {
return nil, nil
}
e := Extent{pts[0][0], pts[0][1], pts[0][0], pts[0][1]}
for _, pt := range pts {
e.AddPoints([2]float64(pt))
}
return &e, nil
}
// Contains will return whether the given extent is inside the extent.
func (e *Extent) Contains(ne MinMaxer) bool {
// Nil extent contains the world.
if e == nil {
return true
}
if ne == nil {
return false
}
return e.MinX() <= ne.MinX() &&
e.MaxX() >= ne.MaxX() &&
e.MinY() <= ne.MinY() &&
e.MaxY() >= ne.MaxY()
}
// ContainsPoint will return whether the given point is inside of the extent.
func (e *Extent) ContainsPoint(pt [2]float64) bool {
if e == nil {
return true
}
return e.MinX() <= pt[0] && pt[0] <= e.MaxX() &&
e.MinY() <= pt[1] && pt[1] <= e.MaxY()
}
// ContainsLine will return weather the given line completely inside of the extent.
func (e *Extent) ContainsLine(l [2][2]float64) bool {
if e == nil {
return true
}
return e.ContainsPoint(l[0]) && e.ContainsPoint(l[1])
}
// ContainsGeom will return weather the given geometry is completely inside of the extent.
func (e *Extent) ContainsGeom(g Geometry) (bool, error) {
if e.IsUniverse() {
return true, nil
}
// Check to see if it can be a MinMaxer, if so use that.
if extenter, ok := g.(MinMaxer); ok {
return e.Contains(extenter), nil
}
// we will use a exntent that contains the geometry, and check to see if this extent contains that extent.
var ne = new(Extent)
if err := ne.AddGeometry(g); err != nil {
return false, err
}
return e.Contains(ne), nil
}
// ScaleBy will scale the points in the extent by the given scale factor.
func (e *Extent) ScaleBy(s float64) *Extent {
if e == nil {
return nil
}
return NewExtent(
[2]float64{e[0] * s, e[1] * s},
[2]float64{e[2] * s, e[3] * s},
)
}
// ExpandBy will expand extent by the given factor.
func (e *Extent) ExpandBy(s float64) *Extent {
if e == nil {
return nil
}
return NewExtent(
[2]float64{e[0] - s, e[1] - s},
[2]float64{e[2] + s, e[3] + s},
)
}
// Clone returns a new Extent with contents copied.
func (e *Extent) Clone() *Extent {
if e == nil {
return nil
}
return &Extent{e[0], e[1], e[2], e[3]}
}
// Intersect will return a new extent that is the intersect of the two extents.
//
// +-------------------------+
// | |
// | A +----------+------+
// | |//////////| |
// | |/// C ////| |
// | |//////////| |
// +--------------+----------+ |
// | B |
// +-----------------+
//
// For example the for the above Box A intersects Box B at the area surround by C.
//
// If the Boxes don't intersect does will be false, otherwise ibb will be the intersect.
func (e *Extent) Intersect(ne *Extent) (*Extent, bool) {
// if e in nil, then the intersect is ne. As a nil extent is the whole universe.
if e == nil {
return ne.Clone(), true
}
// if ne is nil, then the intersect is e. As a nil extent is the whole universe.
if ne == nil {
return e.Clone(), true
}
minx := e.MinX()
if minx < ne.MinX() {
minx = ne.MinX()
}
maxx := e.MaxX()
if maxx > ne.MaxX() {
maxx = ne.MaxX()
}
// The boxes don't intersect.
if minx >= maxx {
return nil, false
}
miny := e.MinY()
if miny < ne.MinY() {
miny = ne.MinY()
}
maxy := e.MaxY()
if maxy > ne.MaxY() {
maxy = ne.MaxY()
}
// The boxes don't intersect.
if miny >= maxy {
return nil, false
}
return &Extent{minx, miny, maxx, maxy}, true
}
// IsUniverse returns weather the extent contains the universe. This is true if the clip box is nil or the x,y values are max values.
func (e *Extent) IsUniverse() bool {
return e == nil || (e.MinX() == -math.MaxFloat64 && e.MaxX() == math.MaxFloat64 &&
e.MinY() == -math.MaxFloat64 && e.MaxY() == math.MaxFloat64)
}