-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathquery.go
More file actions
304 lines (256 loc) · 6.88 KB
/
query.go
File metadata and controls
304 lines (256 loc) · 6.88 KB
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
package region
// IsEmpty returns true if the region contains no rectangles.
func (r *Region) IsEmpty() bool {
return !r.extents.IsValid()
}
// NotEmpty returns true if the region contains at least one rectangle.
func (r *Region) NotEmpty() bool {
return r.extents.IsValid()
}
// Extents returns the bounding box of the entire region.
// Returns an invalid box if the region is empty.
func (r *Region) Extents() Box {
return r.extents
}
// NumRects returns the number of rectangles in the region.
func (r *Region) NumRects() int {
return r.numRects()
}
// Rectangles returns a copy of all rectangles in the region.
// The rectangles are in Y-X banded order.
func (r *Region) Rectangles() []Box {
rects := r.getRects()
if rects == nil {
return nil
}
result := make([]Box, len(rects))
copy(result, rects)
return result
}
// ContainsPoint returns true if the point (x, y) is inside the region.
func (r *Region) ContainsPoint(x, y int32) bool {
ok, _ := r.ContainsPointBox(x, y)
return ok
}
// ContainsPointBox returns true if the point (x, y) is inside the region,
// along with the box that contains the point (if found).
func (r *Region) ContainsPointBox(x, y int32) (bool, Box) {
numRects := r.numRects()
// Check numRects == 0 or point not in extents
if numRects == 0 || !r.extents.Contains(x, y) {
return false, Box{}
}
// Single rectangle optimization
if numRects == 1 {
return true, r.extents
}
rects := r.rects // We know rects != nil since numRects > 1
// Binary search: find first rectangle where Y2 > y (find_box_for_y)
n := len(rects)
lo, hi := 0, n
for lo < hi {
mid := (lo + hi) / 2
if rects[mid].Y2 <= y {
lo = mid + 1
} else {
hi = mid
}
}
// Scan through rectangles in the band
for i := lo; i < n; i++ {
pbox := &rects[i]
// If we've passed the point's Y or X, we missed it
if y < pbox.Y1 || x < pbox.X1 {
break
}
// Not far enough over yet
if x >= pbox.X2 {
continue
}
// Found it
return true, *pbox
}
return false, Box{}
}
// ContainsRectangle returns the overlap relationship between the region and a box.
// Returns:
// - OverlapOut if the box is completely outside the region
// - OverlapIn if the box is completely inside the region
// - OverlapPart if the box partially overlaps the region
//
// Boxes are not validated here; invalid boxes may return any OverlapType
// depending on their coordinates.
// The algorithm walks the banded rectangles to cover the box, tracking whether
// any part is covered (partIn) and any part is uncovered (partOut). It stops
// once the box is fully covered, partially covered, or disjoint.
func (r *Region) ContainsRectangle(box Box) OverlapType {
rects := r.getRects()
numRects := len(rects)
// Useful optimization: check numRects == 0 or no extent overlap
if numRects == 0 || !r.extents.Intersects(box) {
return OverlapOut
}
// Single rectangle optimization
if numRects == 1 {
// We know that it must be OverlapIn or OverlapPart
if r.extents.Subsumes(box) {
return OverlapIn
}
return OverlapPart
}
partOut := false
partIn := false
// (x, y) starts at upper left of rect, moving to the right and down
x := box.X1
y := box.Y1
// Can stop when both partOut and partIn are TRUE, or we reach box.Y2
pboxEnd := numRects
for pbox := 0; pbox < pboxEnd; pbox++ {
// Getting up to speed or skipping remainder of band
if rects[pbox].Y2 <= y {
pbox = findBoxForY(rects, pbox, pboxEnd, y)
if pbox == pboxEnd {
break
}
}
if rects[pbox].Y1 > y {
partOut = true // Missed part of rectangle above
if partIn || rects[pbox].Y1 >= box.Y2 {
break
}
y = rects[pbox].Y1 // x guaranteed to be == box.X1
}
if rects[pbox].X2 <= x {
continue // Not far enough over yet
}
if rects[pbox].X1 > x {
partOut = true // Missed part of rectangle to left
if partIn {
break
}
}
if rects[pbox].X1 < box.X2 {
partIn = true // Definitely overlap
if partOut {
break
}
}
if rects[pbox].X2 >= box.X2 {
y = rects[pbox].Y2 // Finished with this band
if y >= box.Y2 {
break
}
x = box.X1 // Reset x out to left again
} else {
// Because boxes in a band are maximal width, if the first box
// to overlap the rectangle doesn't completely cover it in that
// band, the rectangle must be partially out, since some of it
// will be uncovered in that band. partIn will have been set true
// by now...
partOut = true
break
}
}
if partIn {
if y < box.Y2 {
return OverlapPart
}
return OverlapIn
}
return OverlapOut
}
// findBoxForY locates the first box whose Y2 is greater than y using binary search.
// Returns end if no such box exists.
// In time O(log n), locate the first box whose y2 is greater than y.
func findBoxForY(rects []Box, begin, end int, y int32) int {
if end == begin {
return end
}
if end-begin == 1 {
if rects[begin].Y2 > y {
return begin
}
return end
}
mid := begin + (end-begin)/2
if rects[mid].Y2 > y {
// If no box is found in [begin, mid], the function
// will return mid, which is then known to be the correct answer.
return findBoxForY(rects, begin, mid, y)
}
return findBoxForY(rects, mid, end, y)
}
// Equal returns true if two regions contain the same set of rectangles.
func (r *Region) Equal(other *Region) bool {
if r == other {
return true
}
if r == nil || other == nil {
return false
}
// Quick check: extents must be equal
if !r.extents.Equal(other.extents) {
return false
}
// Both empty
if r.IsEmpty() && other.IsEmpty() {
return true
}
rects1 := r.getRects()
rects2 := other.getRects()
if len(rects1) != len(rects2) {
return false
}
for i := range rects1 {
if !rects1[i].Equal(rects2[i]) {
return false
}
}
return true
}
// SelfCheck validates the internal consistency of the region.
// Returns true if the region is valid, false otherwise.
// This is primarily useful for debugging.
func (r *Region) SelfCheck() bool {
if r == nil {
return false
}
// Empty region is valid
if !r.extents.IsValid() {
return len(r.rects) == 0
}
rects := r.getRects()
if len(rects) == 0 {
return false // Valid extents but no rects
}
// Check that all rectangles are valid
for _, rect := range rects {
if !rect.IsValid() {
return false
}
}
// Check basic Y-X ordering (Y1 sorted, then X1 within same Y1)
for i := 1; i < len(rects); i++ {
prev := &rects[i-1]
curr := &rects[i]
// Check Y ordering
if curr.Y1 < prev.Y1 {
return false // Not sorted by Y1
}
// Same band: check X ordering and non-overlap
if prev.Y1 == curr.Y1 && prev.Y2 == curr.Y2 {
if curr.X1 < prev.X1 {
return false // Not sorted by X1 in same band
}
if prev.X2 > curr.X1 {
return false // Overlapping in same band
}
}
}
// Check that extents correctly bound all rectangles
computedExtents := rects[0]
for _, rect := range rects[1:] {
computedExtents = computedExtents.Union(rect)
}
return r.extents.Equal(computedExtents)
}