1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package s2
16
17 import (
18 "testing"
19
20 "github.com/golang/geo/r3"
21 "github.com/golang/geo/s1"
22 )
23
24 func TestShapeIndexBasics(t *testing.T) {
25 index := NewShapeIndex()
26 s := &edgeVectorShape{}
27
28 if index.Len() != 0 {
29 t.Errorf("initial index should be empty after creation")
30 }
31 index.Add(s)
32
33 if index.Len() == 0 {
34 t.Errorf("index should not be empty after adding shape")
35 }
36
37 index.Reset()
38 if index.Len() != 0 {
39 t.Errorf("index should be empty after reset, got %v %+v", index.Len(), index)
40 }
41 }
42
43 func TestShapeEdgeComparisons(t *testing.T) {
44 tests := []struct {
45 a, b Edge
46 want int
47 }{
48 {
49
50 a: Edge{PointFromCoords(-1, 0, 0), PointFromCoords(0, 0, 0)},
51 b: Edge{PointFromCoords(0, 0, 0), PointFromCoords(0, 0, 0)},
52 want: -1,
53 },
54 {
55
56 a: Edge{PointFromCoords(0, 2, 0), PointFromCoords(0, 0, 5)},
57 b: Edge{PointFromCoords(0, 2, 0), PointFromCoords(0, 0, 5)},
58 want: 0,
59 },
60 {
61
62 a: Edge{PointFromCoords(1, 0, 0), PointFromCoords(-6, 7, 8)},
63 b: Edge{PointFromCoords(0, 0, 0), PointFromCoords(1, 3, 5)},
64 want: 1,
65 },
66 {
67
68 a: Edge{PointFromCoords(5, -2, -0.4), PointFromCoords(-1, 0, 0)},
69 b: Edge{PointFromCoords(5, -2, -0.4), PointFromCoords(0, -1, -1)},
70 want: -1,
71 },
72 {
73
74 a: Edge{PointFromCoords(9, 8, 7), PointFromCoords(12, 3, -4)},
75 b: Edge{PointFromCoords(9, 8, 7), PointFromCoords(12, 3, -4)},
76 want: 0,
77 },
78 {
79
80 a: Edge{PointFromCoords(-11, 7.2, -4.6), PointFromCoords(0, 1, 0)},
81 b: Edge{PointFromCoords(-11, 7.2, -4.6), PointFromCoords(0, 0, 0.9)},
82 want: 1,
83 },
84 }
85
86 for _, test := range tests {
87 if got := test.a.Cmp(test.b); got != test.want {
88 t.Errorf("%v.Cmp(%v) = %v, want %v", test.a, test.b, got, test.want)
89 }
90 }
91 }
92
93 func TestShapeIndexCellBasics(t *testing.T) {
94 s := &ShapeIndexCell{}
95
96 if len(s.shapes) != 0 {
97 t.Errorf("len(s.shapes) = %v, want %d", len(s.shapes), 0)
98 }
99
100
101 c1 := &clippedShape{}
102 s.add(c1)
103
104 c2 := newClippedShape(7, 1)
105 s.add(c2)
106
107 c3 := &clippedShape{}
108 s.add(c3)
109
110
111 if got := s.shapes[1]; got != c2 {
112 t.Errorf("%v.shapes[%d] = %v, want %v", s, 1, got, c2)
113 }
114
115
116 if got := s.findByShapeID(7); got != c2 {
117 t.Errorf("%v.findByShapeID(%v) = %v, want %v", s, 7, got, c2)
118 }
119 }
120
121
122
123 func validateEdge(t *testing.T, a, b Point, ci CellID, hasEdge bool) {
124
125
126 padding := cellPadding
127 sign := 1.0
128 if !hasEdge {
129 sign = -1
130 }
131 padding += sign * intersectsRectErrorUVDist
132 bound := ci.boundUV().ExpandedByMargin(padding)
133 aUV, bUV, ok := ClipToPaddedFace(a, b, ci.Face(), padding)
134
135 if got := ok && edgeIntersectsRect(aUV, bUV, bound); got != hasEdge {
136 t.Errorf("edgeIntersectsRect(%v, %v, %v) = %v && clip = %v, want %v", aUV, bUV, bound, edgeIntersectsRect(aUV, bUV, bound), ok, hasEdge)
137 }
138 }
139
140
141
142 func validateInterior(t *testing.T, shape Shape, ci CellID, indexContainsCenter bool) {
143 if shape == nil {
144 if indexContainsCenter {
145 t.Errorf("%v was nil or does not have an interior, but should have", shape)
146 }
147 return
148 }
149 if got := containsBruteForce(shape, ci.Point()); got != indexContainsCenter {
150 t.Errorf("validating interior of shape containsCenter = %v, want %v", got, indexContainsCenter)
151 }
152 }
153
154
155
156
157 func quadraticValidate(t *testing.T, index *ShapeIndex) {
158
159
160
161
162 minCellID := CellIDFromFace(0).ChildBeginAtLevel(MaxLevel)
163 for it := index.Iterator(); ; it.Next() {
164
165
166 var skipped CellUnion
167 if !it.Done() {
168 cellID := it.CellID()
169 if cellID < minCellID {
170 t.Errorf("cell ID below min, got %v, want %v", cellID, minCellID)
171 }
172 skipped = CellUnionFromRange(minCellID, cellID.RangeMin())
173 minCellID = cellID.RangeMax().Next()
174 } else {
175
176 skipped = CellUnionFromRange(minCellID,
177 CellIDFromFace(5).ChildEndAtLevel(MaxLevel))
178 }
179
180
181
182 shortEdges := 0
183 for id, shape := range index.shapes {
184 for j := 0; j < len(skipped); j++ {
185 validateInterior(t, shape, skipped[j], false)
186 }
187
188
189 var clipped *clippedShape
190 if !it.Done() {
191 clipped = it.IndexCell().findByShapeID(id)
192 containsCenter := clipped != nil && clipped.containsCenter
193 validateInterior(t, shape, it.CellID(), containsCenter)
194 }
195
196 if shape == nil {
197 if clipped != nil {
198 t.Errorf("clipped should be nil when shape is nil")
199 }
200 continue
201 }
202
203
204 for e := 0; e < shape.NumEdges(); e++ {
205 edge := shape.Edge(e)
206 for j := 0; j < len(skipped); j++ {
207 validateEdge(t, edge.V0, edge.V1, skipped[j], false)
208 }
209 if !it.Done() {
210 hasEdge := clipped != nil && clipped.containsEdge(e)
211 validateEdge(t, edge.V0, edge.V1, it.CellID(), hasEdge)
212 if hasEdge && it.CellID().Level() < maxLevelForEdge(edge) {
213 shortEdges++
214 }
215 }
216 }
217 }
218
219 if shortEdges > index.maxEdgesPerCell {
220 t.Errorf("too many edges")
221 }
222
223 if it.Done() {
224 break
225 }
226 }
227 }
228
229
230 func copyIterator(i *ShapeIndexIterator) *ShapeIndexIterator {
231 s := &ShapeIndexIterator{
232 index: i.index,
233 position: i.position,
234 id: i.id,
235 cell: i.cell,
236 }
237 return s
238 }
239
240 func testIteratorMethods(t *testing.T, index *ShapeIndex) {
241 it := index.Iterator()
242 if it.Prev() {
243 t.Fatalf("new iterator should not be able to go backwards")
244 }
245
246 it.End()
247 if !it.Done() {
248 t.Errorf("iterator positioned at end should report as done")
249 }
250
251 var ids []CellID
252
253 minCellID := CellIDFromFace(0).ChildBeginAtLevel(MaxLevel)
254
255 for it.Begin(); !it.Done(); it.Next() {
256
257 ci := it.CellID()
258 skipped := CellUnionFromRange(minCellID, ci.RangeMin())
259
260 it2 := NewShapeIndexIterator(index, IteratorEnd)
261 for i := 0; i < len(skipped); i++ {
262 if it2.LocatePoint(skipped[i].Point()) {
263 t.Errorf("iterator should not have been able to find the cell %v which was not in the index", skipped[i].Point())
264 }
265
266 if got := it2.LocateCellID(skipped[i]); got != Disjoint {
267 t.Errorf("CellID location should be Disjoint for non-existent entry, got %v", got)
268 }
269 it2.Begin()
270 it2.seek(skipped[i])
271 if ci != it2.CellID() {
272 t.Errorf("seeking the current cell in the skipped list should match the current cellid. got %v, want %v", it2.CellID(), ci)
273 }
274 }
275
276 if len(ids) != 0 {
277 prevCell := ids[len(ids)-1]
278
279
280
281 it2 = copyIterator(it)
282 if !it2.Prev() {
283 t.Errorf("should have been able to go back because there are cells")
284 }
285 if prevCell != it2.CellID() {
286 t.Errorf("ShapeIndexIterator should be positioned at the beginning and not equal to last entry")
287 }
288
289 it2.Next()
290 if ci != it2.CellID() {
291 t.Errorf("advancing back one spot should give us the current cell")
292 }
293
294 it2.seek(prevCell)
295 if prevCell != it2.CellID() {
296 t.Errorf("seek from beginning for the first previous cell %v should not give us the current cell %v", prevCell, it.CellID())
297 }
298 }
299
300 it2.Begin()
301 if ci.Point() != it.Center() {
302 t.Errorf("point at center of current position should equal center of the crrent CellID. got %v, want %v", it.Center(), ci.Point())
303 }
304
305 if !it2.LocatePoint(it.Center()) {
306 t.Errorf("it.LocatePoint(it.Center()) should have been able to locate the point it is currently at")
307 }
308
309 if ci != it2.CellID() {
310 t.Errorf("CellID of the Point we just located should be equal. got %v, want %v", it2.CellID(), ci)
311 }
312
313 it2.Begin()
314 if got := it2.LocateCellID(ci); got != Indexed {
315 t.Errorf("it.LocateCellID(%v) = %v, want %v", ci, got, Indexed)
316 }
317
318 if ci != it2.CellID() {
319 t.Errorf("CellID of the CellID we just located should match. got %v, want %v", it2.CellID(), ci)
320 }
321
322 if !ci.isFace() {
323 it2.Begin()
324 if got := it2.LocateCellID(ci.immediateParent()); Subdivided != got {
325 t.Errorf("it2.LocateCellID(%v) = %v, want %v", ci.immediateParent(), got, Subdivided)
326 }
327
328 if it2.CellID() > ci {
329 t.Errorf("CellID of the immediate parent should be above the current cell, got %v, want %v", it2.CellID(), ci)
330 }
331
332 if it2.CellID() < ci.immediateParent().RangeMin() {
333 t.Errorf("CellID of the current position should fall below the RangeMin of the parent. got %v, want %v", it2.CellID(), ci.immediateParent().RangeMin())
334 }
335 }
336
337 if !ci.IsLeaf() {
338 for i := 0; i < 4; i++ {
339 it2.Begin()
340 if got, want := it2.LocateCellID(ci.Children()[i]), Indexed; got != want {
341 t.Errorf("it2.LocateCellID(%v.Children[%d]) = %v, want %v", ci, i, got, want)
342 }
343
344 if ci != it2.CellID() {
345 t.Errorf("it2.CellID() = %v, want %v", it2.CellID(), ci)
346 }
347 }
348 }
349
350 ids = append(ids, ci)
351
352 minCellID = ci.RangeMax().Next()
353 }
354 }
355
356 func TestShapeIndexNoEdges(t *testing.T) {
357 index := NewShapeIndex()
358 iter := index.Iterator()
359
360 if !iter.Done() {
361 t.Errorf("iterator for empty index should start at Done but did not")
362 }
363 testIteratorMethods(t, index)
364 }
365
366 func TestShapeIndexOneEdge(t *testing.T) {
367 index := NewShapeIndex()
368 e := edgeVectorShapeFromPoints(PointFromCoords(1, 0, 0), PointFromCoords(0, 1, 0))
369 if got := index.Add(e); got != 0 {
370 t.Errorf("the first element added to the index should have id 0, got %v", got)
371 }
372 quadraticValidate(t, index)
373 testIteratorMethods(t, index)
374 }
375
376 func TestShapeIndexManyIdenticalEdges(t *testing.T) {
377 const numEdges = 100
378 a := PointFromCoords(0.99, 0.99, 1)
379 b := PointFromCoords(-0.99, -0.99, 1)
380
381 index := NewShapeIndex()
382 for i := int32(0); i < numEdges; i++ {
383 if got := index.Add(edgeVectorShapeFromPoints(a, b)); got != i {
384 t.Errorf("element %d id = %v, want %v", i, got, i)
385 }
386 }
387 quadraticValidate(t, index)
388 testIteratorMethods(t, index)
389
390
391
392 for it := index.Iterator(); !it.Done(); it.Next() {
393 if it.CellID().Level() != 0 {
394 t.Errorf("it.CellID.Level() = %v, want nonzero", it.CellID().Level())
395 }
396 }
397 }
398
399 func TestShapeIndexDegenerateEdge(t *testing.T) {
400
401
402 a := PointFromCoords(1, 1, 1)
403 shape := edgeVectorShapeFromPoints(a, a)
404 index := NewShapeIndex()
405 index.Add(shape)
406 quadraticValidate(t, index)
407
408 count := 0
409 for it := index.Iterator(); !it.Done(); it.Next() {
410 if !it.CellID().IsLeaf() {
411 t.Errorf("the cell for this shape should be a leaf cell.")
412 }
413 if got := len(it.IndexCell().shapes); got != 1 {
414 t.Errorf("there should only be one shape stored in the index cell, got %d", got)
415 }
416 if got := len(it.IndexCell().shapes[0].edges); got != 1 {
417 t.Errorf("point should only have one edge, got %d", got)
418 }
419 count++
420 }
421 if count != 3 {
422 t.Errorf("expected 3 index cells, got %d", count)
423 }
424
425 }
426
427 func TestShapeIndexManyTinyEdges(t *testing.T) {
428
429
430
431
432 a := cellIDFromPoint(PointFromCoords(1, 0, 0)).Point()
433 b := Point{a.Add(r3.Vector{0, 1e-12, 0}).Normalize()}
434 shape := &edgeVectorShape{}
435 for i := 0; i < 100; i++ {
436 shape.Add(a, b)
437 }
438
439 index := NewShapeIndex()
440 index.Add(shape)
441 quadraticValidate(t, index)
442
443
444 it := index.Iterator()
445 if it.Done() {
446 t.Errorf("ShapeIndexIterator should not be positioned at the end for %v", index)
447 return
448 }
449 if !(it.CellID().IsLeaf()) {
450 t.Errorf("there should be only one leaf cell in the index but it.CellID().IsLeaf() returned false")
451 }
452 it.Next()
453 if !(it.Done()) {
454 t.Errorf("ShapeIndexIterator should be positioned at the end now since there should have been only one element")
455 }
456 }
457
458 func TestShapeIndexShrinkToFitOptimization(t *testing.T) {
459
460
461
462
463
464
465 loop := RegularLoop(PointFromCoords(1, 0.5, 0.5), s1.Degree*89, 100)
466 index := NewShapeIndex()
467 index.Add(loop)
468 quadraticValidate(t, index)
469 }
470
471 func TestShapeIndexMixedGeometry(t *testing.T) {
472
473
474
475
476 index := NewShapeIndex()
477 index.Add(makePolyline("0:0, 2:1, 0:2, 2:3, 0:4, 2:5, 0:6"))
478 index.Add(makePolyline("1:0, 3:1, 1:2, 3:3, 1:4, 3:5, 1:6"))
479 index.Add(makePolyline("2:0, 4:1, 2:2, 4:3, 2:4, 4:5, 2:6"))
480
481 loop := LoopFromCell(CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(MaxLevel)))
482 index.Add(loop)
483 it := index.Iterator()
484
485 c := CellIDFromFace(1)
486 if got, want := it.LocateCellID(c), Disjoint; got != want {
487 t.Errorf("%v.LocateCellID(%v) = %v, want %v\n%v", it, c, got, want, index)
488 }
489 }
490
491 func TestShapeIndexLoopSpanningThreeFaces(t *testing.T) {
492 const numEdges = 100
493
494
495 polygon := concentricLoopsPolygon(PointFromCoords(1, -1, -1), 2, numEdges)
496 index := NewShapeIndex()
497
498 for _, l := range polygon.loops {
499 index.Add(l)
500 }
501
502 quadraticValidate(t, index)
503 testIteratorMethods(t, index)
504 }
505
506 func TestShapeIndexNumEdgesUpTo(t *testing.T) {
507 index := makeShapeIndex("0:0 | 0:1 | 0:2 | 0:3 | 0:4 # 1:0, 1:1 | 1:2, 1:3 | 1:4, 1:5, 1:6 #")
508
509 if got, want := len(index.shapes), 4; got != want {
510 t.Errorf("len(index.shapes) = %v, want %v", got, want)
511 }
512
513 numEdgesTests := []struct {
514 shapeID int32
515 want int
516 }{
517 {shapeID: 0, want: 5},
518 {shapeID: 1, want: 1},
519 {shapeID: 2, want: 1},
520 {shapeID: 3, want: 2},
521 }
522
523 for _, test := range numEdgesTests {
524 if got := index.Shape(test.shapeID).NumEdges(); got != test.want {
525 t.Errorf("index.Shape(%d).NumEdges() = %d, want %d", test.shapeID, got, test.want)
526 }
527 }
528
529 if got, want := index.NumEdges(), 9; got != want {
530 t.Errorf("index.NumEdges() = %d, want %d", got, want)
531 }
532
533 countTests := []struct {
534 limit int
535 want int
536 }{
537 {limit: 1, want: 5},
538 {limit: 5, want: 5},
539 {limit: 6, want: 6},
540 {limit: 8, want: 9},
541 }
542 for _, test := range countTests {
543 if got := index.NumEdgesUpTo(test.limit); got != test.want {
544 t.Errorf("index.NumEdgesUpTo(%d) = %d, want %d", test.limit, got, test.want)
545 }
546 }
547 }
548
549
550
551
552
553
554 func BenchmarkShapeIndexIteratorLocatePoint(b *testing.B) {
555 index := NewShapeIndex()
556 for i := 0; i < 100; i++ {
557 var points []Point
558 for j := 0; j < 100; j++ {
559 points = append(points, randomPoint())
560 }
561 polyline := Polyline(points)
562 index.Add(&polyline)
563 }
564
565 it := index.Iterator()
566 b.ResetTimer()
567 for i := 0; i < b.N; i++ {
568 it.LocatePoint(randomPoint())
569 }
570 }
571
View as plain text