Skip to content

Commit c8fc59a

Browse files
committed
Merge golang#98 into golang/geo fork
1 parent 740aa86 commit c8fc59a

File tree

5 files changed

+199
-21
lines changed

5 files changed

+199
-21
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
This fork of golang/geo exists to merge https://github.com/golang/geo/pull/98
2+
and add CellIndex intersection functionality.
3+
14
# Overview
25

36
S2 is a library for spherical geometry that aims to have the same robustness,

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
module github.com/golang/geo
1+
module github.com/MadHive/geo
22

33
go 1.12

s2/cell_index.go

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type cellIndexNode struct {
3434
// newCellIndexNode returns a node with the appropriate default values.
3535
func newCellIndexNode() cellIndexNode {
3636
return cellIndexNode{
37-
cellID: 0,
37+
cellID: CellID(0),
3838
label: cellIndexDoneContents,
3939
parent: -1,
4040
}
@@ -49,15 +49,52 @@ type rangeNode struct {
4949
contents int32 // Contents of this node (an index within the cell tree).
5050
}
5151

52+
type labels []int32
53+
54+
func (l *labels) Normalize() {
55+
encountered := make(map[int32]struct{})
56+
57+
for i := range *l {
58+
encountered[(*l)[i]] = struct{}{}
59+
}
60+
61+
*l = (*l)[:0]
62+
for key := range encountered {
63+
*l = append(*l, key)
64+
}
65+
}
66+
67+
type cellVisitor func(cellID CellID, label int32) bool
68+
5269
// CellIndexIterator is an iterator that visits the entire set of indexed
5370
// (CellID, label) pairs in an unspecified order.
5471
type CellIndexIterator struct {
55-
// TODO(roberts): Implement
72+
nodes []cellIndexNode
73+
pos int
5674
}
5775

5876
// NewCellIndexIterator creates an iterator for the given CellIndex.
5977
func NewCellIndexIterator(index *CellIndex) *CellIndexIterator {
60-
return &CellIndexIterator{}
78+
return &CellIndexIterator{
79+
nodes: index.cellTree,
80+
pos: 0,
81+
}
82+
}
83+
84+
func (c *CellIndexIterator) CellID() CellID {
85+
return c.nodes[c.pos].cellID
86+
}
87+
88+
func (c *CellIndexIterator) Label() int32 {
89+
return c.nodes[c.pos].label
90+
}
91+
92+
func (c *CellIndexIterator) Done() bool {
93+
return c.pos == len(c.nodes)
94+
}
95+
96+
func (c *CellIndexIterator) Next() {
97+
c.pos++
6198
}
6299

63100
// CellIndexRangeIterator is an iterator that seeks and iterates over a set of
@@ -202,7 +239,6 @@ func (c *CellIndexRangeIterator) Seek(target CellID) {
202239

203240
// Nonempty needs to find the next non-empty entry.
204241
for c.nonEmpty && c.IsEmpty() && !c.Done() {
205-
// c.Next()
206242
c.pos++
207243
}
208244
}
@@ -246,7 +282,7 @@ type CellIndexContentsIterator struct {
246282
func NewCellIndexContentsIterator(index *CellIndex) *CellIndexContentsIterator {
247283
it := &CellIndexContentsIterator{
248284
cellTree: index.cellTree,
249-
prevStartID: 0,
285+
prevStartID: CellID(0),
250286
nodeCutoff: -1,
251287
nextNodeCutoff: -1,
252288
node: cellIndexNode{label: cellIndexDoneContents},
@@ -256,7 +292,7 @@ func NewCellIndexContentsIterator(index *CellIndex) *CellIndexContentsIterator {
256292

257293
// Clear clears all state with respect to which range(s) have been visited.
258294
func (c *CellIndexContentsIterator) Clear() {
259-
c.prevStartID = 0
295+
c.prevStartID = CellID(0)
260296
c.nodeCutoff = -1
261297
c.nextNodeCutoff = -1
262298
c.node.label = cellIndexDoneContents
@@ -343,7 +379,7 @@ func (c *CellIndexContentsIterator) StartUnion(r *CellIndexRangeIterator) {
343379
// There is also a helper method that adds all elements of CellUnion with the
344380
// same label:
345381
//
346-
// index.AddCellUnion(cellUnion, label)
382+
// index.AddCellUnion(cellUnion, label)
347383
//
348384
// Note that the index is not dynamic; the contents of the index cannot be
349385
// changed once it has been built. Adding more after calling Build results in
@@ -353,7 +389,7 @@ func (c *CellIndexContentsIterator) StartUnion(r *CellIndexRangeIterator) {
353389
// is to use a built-in method such as IntersectingLabels (which returns
354390
// the labels of all cells that intersect a given target CellUnion):
355391
//
356-
// labels := index.IntersectingLabels(targetUnion);
392+
// labels := index.IntersectingLabels(targetUnion);
357393
//
358394
// Alternatively, you can use a ClosestCellQuery which computes the cell(s)
359395
// that are closest to a given target geometry.
@@ -482,7 +518,8 @@ func (c *CellIndex) Build() {
482518
c.cellTree = append(c.cellTree, cellIndexNode{
483519
cellID: deltas[i].cellID,
484520
label: deltas[i].label,
485-
parent: contents})
521+
parent: contents,
522+
})
486523
contents = int32(len(c.cellTree) - 1)
487524
} else if deltas[i].cellID == SentinelCellID {
488525
contents = c.cellTree[contents].parent
@@ -492,7 +529,48 @@ func (c *CellIndex) Build() {
492529
}
493530
}
494531

495-
// TODO(roberts): Differences from C++
496-
// IntersectingLabels
497-
// VisitIntersectingCells
498-
// CellIndexIterator
532+
func (c *CellIndex) IntersectingLabels(target CellUnion) []int32 {
533+
var labels labels
534+
c.VisitIntersectingCells(target, func(cellID CellID, label int32) bool {
535+
labels = append(labels, label)
536+
return true
537+
})
538+
539+
labels.Normalize()
540+
return labels
541+
}
542+
543+
func (c *CellIndex) VisitIntersectingCells(target CellUnion, visitor cellVisitor) bool {
544+
if len(target) == 0 {
545+
return true
546+
}
547+
548+
pos := 0
549+
contents := NewCellIndexContentsIterator(c)
550+
rangeIter := NewCellIndexRangeIterator(c)
551+
rangeIter.Begin()
552+
553+
for ok := true; ok; ok = pos != len(target) {
554+
if rangeIter.LimitID() <= target[pos].RangeMin() {
555+
rangeIter.Seek(target[pos].RangeMin())
556+
}
557+
558+
for ; rangeIter.StartID() <= target[pos].RangeMax(); rangeIter.Next() {
559+
for contents.StartUnion(rangeIter); !contents.Done(); contents.Next() {
560+
if !visitor(contents.CellID(), contents.Label()) {
561+
return false
562+
}
563+
}
564+
}
565+
566+
pos++
567+
if pos != len(target) && target[pos].RangeMax() < rangeIter.StartID() {
568+
pos = target.lowerBound(pos+1, len(target), rangeIter.StartID())
569+
if target[pos-1].RangeMax() >= rangeIter.StartID() {
570+
pos--
571+
}
572+
}
573+
}
574+
575+
return true
576+
}

s2/cell_index_test.go

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ func cellIndexNodesEqual(a, b []cellIndexNode) bool {
4949
return b[i].less(b[j])
5050
})
5151
return reflect.DeepEqual(a, b)
52-
5352
}
5453

5554
// copyCellIndexNodes creates a copy of the nodes so that sorting and other tests
@@ -338,9 +337,108 @@ func TestCellIndexRandomCellUnions(t *testing.T) {
338337
cellIndexQuadraticValidate(t, "Random Cell Unions", index, nil)
339338
}
340339

340+
func TestCellIndexIntersectionOptimization(t *testing.T) {
341+
type cellIndexTestInput struct {
342+
cellID string
343+
label int32
344+
}
345+
tests := []struct {
346+
label string
347+
have []cellIndexTestInput
348+
}{
349+
{
350+
// Tests various corner cases for the binary search optimization in
351+
// VisitIntersectingCells.
352+
label: "Intersection Optimization",
353+
have: []cellIndexTestInput{
354+
{"1/001", 1},
355+
{"1/333", 2},
356+
{"2/00", 3},
357+
{"2/0232", 4},
358+
},
359+
},
360+
}
361+
362+
for _, test := range tests {
363+
index := &CellIndex{}
364+
for _, v := range test.have {
365+
index.Add(cellIDFromString(v.cellID), v.label)
366+
}
367+
index.Build()
368+
checkIntersection(t, test.label, makeCellUnion("1/010", "1/3"), index)
369+
checkIntersection(t, test.label, makeCellUnion("2/010", "2/011", "2/02"), index)
370+
}
371+
}
372+
373+
func TestCellIndexIntersectionRandomCellUnions(t *testing.T) {
374+
// Construct cell unions from random CellIDs at random levels. Note that
375+
// because the cell level is chosen uniformly, there is a very high
376+
// likelihood that the cell unions will overlap.
377+
index := &CellIndex{}
378+
for i := int32(0); i < 100; i++ {
379+
index.AddCellUnion(randomCellUnion(10), i)
380+
}
381+
index.Build()
382+
for i := 0; i < 200; i++ {
383+
checkIntersection(t, "", randomCellUnion(10), index)
384+
}
385+
}
386+
387+
func TestCellIndexIntersectionSemiRandomCellUnions(t *testing.T) {
388+
for i := 0; i < 200; i++ {
389+
index := &CellIndex{}
390+
id := cellIDFromString("1/0123012301230123")
391+
var target CellUnion
392+
for j := 0; j < 100; j++ {
393+
switch {
394+
case oneIn(10):
395+
index.Add(id, int32(j))
396+
case oneIn(4):
397+
target = append(target, id)
398+
case oneIn(2):
399+
id = id.NextWrap()
400+
case oneIn(6) && !id.isFace():
401+
id = id.immediateParent()
402+
case oneIn(6) && !id.IsLeaf():
403+
id = id.ChildBegin()
404+
}
405+
}
406+
target.Normalize()
407+
index.Build()
408+
checkIntersection(t, "", target, index)
409+
}
410+
}
411+
412+
func checkIntersection(t *testing.T, desc string, target CellUnion, index *CellIndex) {
413+
var expected, actual []int32
414+
for it := NewCellIndexIterator(index); !it.Done(); it.Next() {
415+
if target.IntersectsCellID(it.CellID()) {
416+
expected = append(expected, it.Label())
417+
}
418+
}
419+
420+
index.VisitIntersectingCells(target, func(cellID CellID, label int32) bool {
421+
actual = append(actual, label)
422+
return true
423+
})
424+
425+
if !labelsEqual(actual, expected) {
426+
t.Errorf("%s: labels not equal but should be. %v != %v", desc, actual, expected)
427+
}
428+
}
429+
430+
func labelsEqual(a, b []int32) bool {
431+
sort.Slice(a, func(i, j int) bool {
432+
return a[i] < a[j]
433+
})
434+
sort.Slice(b, func(i, j int) bool {
435+
return b[i] < b[j]
436+
})
437+
return reflect.DeepEqual(a, b)
438+
}
439+
341440
// TODO(roberts): Differences from C++
342441
//
343442
// Add remainder of TestCellIndexContentsIteratorSuppressesDuplicates
344443
//
345444
// additional Iterator related parts
346-
// Intersections related

s2/s2_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,9 @@ import (
2626
"github.com/golang/geo/s1"
2727
)
2828

29-
var (
30-
// To set in testing add "--benchmark_brute_force=true" to your test command.
31-
benchmarkBruteForce = flag.Bool("benchmark_brute_force", false,
32-
"When set, use brute force algorithms in benchmarking.")
33-
)
29+
// To set in testing add "--benchmark_brute_force=true" to your test command.
30+
var benchmarkBruteForce = flag.Bool("benchmark_brute_force", false,
31+
"When set, use brute force algorithms in benchmarking.")
3432

3533
// float64Eq reports whether the two values are within the default epsilon.
3634
func float64Eq(x, y float64) bool { return float64Near(x, y, epsilon) }
@@ -143,6 +141,7 @@ func randomCellUnion(n int) CellUnion {
143141
for i := 0; i < n; i++ {
144142
cu = append(cu, randomCellID())
145143
}
144+
cu.Normalize()
146145
return cu
147146
}
148147

0 commit comments

Comments
 (0)