From bf8f857969929a0403503c3e02412464d0fb0f49 Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Fri, 13 Jan 2023 13:53:07 +0100 Subject: [PATCH 1/7] fix randomCellUnion --- s2/s2_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/s2/s2_test.go b/s2/s2_test.go index 9cdd7d05..fc546990 100644 --- a/s2/s2_test.go +++ b/s2/s2_test.go @@ -26,11 +26,9 @@ import ( "github.com/golang/geo/s1" ) -var ( - // To set in testing add "--benchmark_brute_force=true" to your test command. - benchmarkBruteForce = flag.Bool("benchmark_brute_force", false, - "When set, use brute force algorithms in benchmarking.") -) +// To set in testing add "--benchmark_brute_force=true" to your test command. +var benchmarkBruteForce = flag.Bool("benchmark_brute_force", false, + "When set, use brute force algorithms in benchmarking.") // float64Eq reports whether the two values are within the default epsilon. func float64Eq(x, y float64) bool { return float64Near(x, y, epsilon) } @@ -143,6 +141,7 @@ func randomCellUnion(n int) CellUnion { for i := 0; i < n; i++ { cu = append(cu, randomCellID()) } + cu.Normalize() return cu } From ca5e2cb8d7139a7d901cf4cd793ecf27deb8b5f3 Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Fri, 13 Jan 2023 13:55:08 +0100 Subject: [PATCH 2/7] refactor --- s2/cell_index.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/s2/cell_index.go b/s2/cell_index.go index ef16d089..a884c79f 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -34,7 +34,7 @@ type cellIndexNode struct { // newCellIndexNode returns a node with the appropriate default values. func newCellIndexNode() cellIndexNode { return cellIndexNode{ - cellID: 0, + cellID: CellID(0), label: cellIndexDoneContents, parent: -1, } @@ -202,7 +202,6 @@ func (c *CellIndexRangeIterator) Seek(target CellID) { // Nonempty needs to find the next non-empty entry. for c.nonEmpty && c.IsEmpty() && !c.Done() { - // c.Next() c.pos++ } } @@ -246,7 +245,7 @@ type CellIndexContentsIterator struct { func NewCellIndexContentsIterator(index *CellIndex) *CellIndexContentsIterator { it := &CellIndexContentsIterator{ cellTree: index.cellTree, - prevStartID: 0, + prevStartID: CellID(0), nodeCutoff: -1, nextNodeCutoff: -1, node: cellIndexNode{label: cellIndexDoneContents}, @@ -256,7 +255,7 @@ func NewCellIndexContentsIterator(index *CellIndex) *CellIndexContentsIterator { // Clear clears all state with respect to which range(s) have been visited. func (c *CellIndexContentsIterator) Clear() { - c.prevStartID = 0 + c.prevStartID = CellID(0) c.nodeCutoff = -1 c.nextNodeCutoff = -1 c.node.label = cellIndexDoneContents @@ -343,7 +342,7 @@ func (c *CellIndexContentsIterator) StartUnion(r *CellIndexRangeIterator) { // There is also a helper method that adds all elements of CellUnion with the // same label: // -// index.AddCellUnion(cellUnion, label) +// index.AddCellUnion(cellUnion, label) // // Note that the index is not dynamic; the contents of the index cannot be // changed once it has been built. Adding more after calling Build results in @@ -353,7 +352,7 @@ func (c *CellIndexContentsIterator) StartUnion(r *CellIndexRangeIterator) { // is to use a built-in method such as IntersectingLabels (which returns // the labels of all cells that intersect a given target CellUnion): // -// labels := index.IntersectingLabels(targetUnion); +// labels := index.IntersectingLabels(targetUnion); // // Alternatively, you can use a ClosestCellQuery which computes the cell(s) // that are closest to a given target geometry. @@ -482,7 +481,8 @@ func (c *CellIndex) Build() { c.cellTree = append(c.cellTree, cellIndexNode{ cellID: deltas[i].cellID, label: deltas[i].label, - parent: contents}) + parent: contents, + }) contents = int32(len(c.cellTree) - 1) } else if deltas[i].cellID == SentinelCellID { contents = c.cellTree[contents].parent From 3243874f674ed4441a33520e090d5589ae235d63 Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Fri, 13 Jan 2023 14:00:02 +0100 Subject: [PATCH 3/7] add cellIndexIterator --- s2/cell_index.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/s2/cell_index.go b/s2/cell_index.go index a884c79f..cd9b23ca 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -52,12 +52,32 @@ type rangeNode struct { // CellIndexIterator is an iterator that visits the entire set of indexed // (CellID, label) pairs in an unspecified order. type CellIndexIterator struct { - // TODO(roberts): Implement + nodes []cellIndexNode + pos int } // NewCellIndexIterator creates an iterator for the given CellIndex. func NewCellIndexIterator(index *CellIndex) *CellIndexIterator { - return &CellIndexIterator{} + return &CellIndexIterator{ + nodes: index.cellTree, + pos: 0, + } +} + +func (c *CellIndexIterator) CellID() CellID { + return c.nodes[c.pos].cellID +} + +func (c *CellIndexIterator) Label() int32 { + return c.nodes[c.pos].label +} + +func (c *CellIndexIterator) Done() bool { + return c.pos == len(c.nodes) +} + +func (c *CellIndexIterator) Next() { + c.pos++ } // CellIndexRangeIterator is an iterator that seeks and iterates over a set of @@ -495,4 +515,3 @@ func (c *CellIndex) Build() { // TODO(roberts): Differences from C++ // IntersectingLabels // VisitIntersectingCells -// CellIndexIterator From ab64de45d31209e5f135078c787c111fb8584683 Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Fri, 13 Jan 2023 14:36:35 +0100 Subject: [PATCH 4/7] add cellIndex intersection --- s2/cell_index.go | 66 +++++++++++++++++++++++++++ s2/cell_index_test.go | 102 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 166 insertions(+), 2 deletions(-) diff --git a/s2/cell_index.go b/s2/cell_index.go index cd9b23ca..5ce4fff8 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -49,6 +49,26 @@ type rangeNode struct { contents int32 // Contents of this node (an index within the cell tree). } +type labels []int32 + +func (l *labels) Normalize() { + if len(*l) == 0 { + return + } + labels := *l + sort.Slice(labels, func(i, j int) bool { return labels[i] < labels[j] }) + + var lastIndex int + for i := 0; i < len(*l); i++ { + if labels[lastIndex] != labels[i] { + lastIndex++ + labels[lastIndex] = labels[i] + } + } +} + +type cellVisitor func(cellID CellID, label int32) bool + // CellIndexIterator is an iterator that visits the entire set of indexed // (CellID, label) pairs in an unspecified order. type CellIndexIterator struct { @@ -512,6 +532,52 @@ func (c *CellIndex) Build() { } } +func (c *CellIndex) IntersectingLabels(target CellUnion) []int32 { + var labels labels + c.VisitIntersectingCells(target, func(cellID CellID, label int32) bool { + labels = append(labels, label) + return true + }) + + labels.Normalize() + return labels +} + +func (c *CellIndex) VisitIntersectingCells(target CellUnion, visitor cellVisitor) bool { + if len(target) == 0 { + return true + } + + pos := 0 + contents := NewCellIndexContentsIterator(c) + rangeIter := NewCellIndexRangeIterator(c) + rangeIter.Begin() + + for ok := true; ok; ok = pos != len(target) { + if rangeIter.LimitID() <= target[pos].RangeMin() { + rangeIter.Seek(target[pos].RangeMin()) + } + + for ; rangeIter.StartID() <= target[pos].RangeMax(); rangeIter.Next() { + for contents.StartUnion(rangeIter); !contents.Done(); contents.Next() { + if !visitor(contents.CellID(), contents.Label()) { + return false + } + } + } + + pos++ + if pos != len(target) && target[pos].RangeMax() < rangeIter.StartID() { + pos = target.lowerBound(pos+1, len(target), rangeIter.StartID()) + if target[pos-1].RangeMax() >= rangeIter.StartID() { + pos-- + } + } + } + + return true +} + // TODO(roberts): Differences from C++ // IntersectingLabels // VisitIntersectingCells diff --git a/s2/cell_index_test.go b/s2/cell_index_test.go index aae2e859..a11f86e2 100644 --- a/s2/cell_index_test.go +++ b/s2/cell_index_test.go @@ -49,7 +49,6 @@ func cellIndexNodesEqual(a, b []cellIndexNode) bool { return b[i].less(b[j]) }) return reflect.DeepEqual(a, b) - } // copyCellIndexNodes creates a copy of the nodes so that sorting and other tests @@ -338,9 +337,108 @@ func TestCellIndexRandomCellUnions(t *testing.T) { cellIndexQuadraticValidate(t, "Random Cell Unions", index, nil) } +func TestCellIndexIntersectionOptimization(t *testing.T) { + type cellIndexTestInput struct { + cellID string + label int32 + } + tests := []struct { + label string + have []cellIndexTestInput + }{ + { + // Tests various corner cases for the binary search optimization in + // VisitIntersectingCells. + label: "Intersection Optimization", + have: []cellIndexTestInput{ + {"1/001", 1}, + {"1/333", 2}, + {"2/00", 3}, + {"2/0232", 4}, + }, + }, + } + + for _, test := range tests { + index := &CellIndex{} + for _, v := range test.have { + index.Add(cellIDFromString(v.cellID), v.label) + } + index.Build() + checkIntersection(t, test.label, makeCellUnion("1/010", "1/3"), index) + checkIntersection(t, test.label, makeCellUnion("2/010", "2/011", "2/02"), index) + } +} + +func TestCellIndexIntersectionRandomCellUnions(t *testing.T) { + // Construct cell unions from random CellIDs at random levels. Note that + // because the cell level is chosen uniformly, there is a very high + // likelihood that the cell unions will overlap. + index := &CellIndex{} + for i := int32(0); i < 100; i++ { + index.AddCellUnion(randomCellUnion(10), i) + } + index.Build() + for i := 0; i < 200; i++ { + checkIntersection(t, "", randomCellUnion(10), index) + } +} + +func TestCellIndexIntersectionSemiRandomCellUnions(t *testing.T) { + for i := 0; i < 200; i++ { + index := &CellIndex{} + id := cellIDFromString("1/0123012301230123") + var target CellUnion + for j := 0; j < 100; j++ { + switch { + case oneIn(10): + index.Add(id, int32(j)) + case oneIn(4): + target = append(target, id) + case oneIn(2): + id = id.NextWrap() + case oneIn(6) && !id.isFace(): + id = id.immediateParent() + case oneIn(6) && !id.IsLeaf(): + id = id.ChildBegin() + } + } + target.Normalize() + index.Build() + checkIntersection(t, "", target, index) + } +} + +func checkIntersection(t *testing.T, desc string, target CellUnion, index *CellIndex) { + var expected, actual []int32 + for it := NewCellIndexIterator(index); !it.Done(); it.Next() { + if target.IntersectsCellID(it.CellID()) { + expected = append(expected, it.Label()) + } + } + + index.VisitIntersectingCells(target, func(cellID CellID, label int32) bool { + actual = append(actual, label) + return true + }) + + if !labelsEqual(actual, expected) { + t.Errorf("%s: labels not equal but should be. %v != %v", desc, actual, expected) + } +} + +func labelsEqual(a, b []int32) bool { + sort.Slice(a, func(i, j int) bool { + return a[i] < a[j] + }) + sort.Slice(b, func(i, j int) bool { + return b[i] < b[j] + }) + return reflect.DeepEqual(a, b) +} + // TODO(roberts): Differences from C++ // // Add remainder of TestCellIndexContentsIteratorSuppressesDuplicates // // additional Iterator related parts -// Intersections related From d40ce0707a3302b28edb409a4c0fd302a41f0ae5 Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Sat, 14 Jan 2023 13:54:42 +0100 Subject: [PATCH 5/7] remove comments --- s2/cell_index.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/s2/cell_index.go b/s2/cell_index.go index 5ce4fff8..359ece01 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -577,7 +577,3 @@ func (c *CellIndex) VisitIntersectingCells(target CellUnion, visitor cellVisitor return true } - -// TODO(roberts): Differences from C++ -// IntersectingLabels -// VisitIntersectingCells From 3cea3724f08c9094c1c0641d780c5aafcd0effca Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Mon, 23 Jan 2023 09:23:45 +0100 Subject: [PATCH 6/7] fix labels normalize --- s2/cell_index.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/s2/cell_index.go b/s2/cell_index.go index 359ece01..afe33aa8 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -52,18 +52,15 @@ type rangeNode struct { type labels []int32 func (l *labels) Normalize() { - if len(*l) == 0 { - return + encountered := make(map[int32]struct{}) + + for i := range *l { + encountered[(*l)[i]] = struct{}{} } - labels := *l - sort.Slice(labels, func(i, j int) bool { return labels[i] < labels[j] }) - - var lastIndex int - for i := 0; i < len(*l); i++ { - if labels[lastIndex] != labels[i] { - lastIndex++ - labels[lastIndex] = labels[i] - } + + *l = (*l)[:0] + for key := range encountered { + *l = append(*l, key) } } From 1b4d3d78c356d40fb8f44d7aef146453454b96d7 Mon Sep 17 00:00:00 2001 From: Jesse Rosenstock Date: Tue, 1 Apr 2025 10:32:20 +0200 Subject: [PATCH 7/7] cell_index_test.go: Replace cellIDFromString with CellIDFromString --- s2/cell_index_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/s2/cell_index_test.go b/s2/cell_index_test.go index 40420c73..d8f01af6 100644 --- a/s2/cell_index_test.go +++ b/s2/cell_index_test.go @@ -362,7 +362,7 @@ func TestCellIndexIntersectionOptimization(t *testing.T) { for _, test := range tests { index := &CellIndex{} for _, v := range test.have { - index.Add(cellIDFromString(v.cellID), v.label) + index.Add(CellIDFromString(v.cellID), v.label) } index.Build() checkIntersection(t, test.label, makeCellUnion("1/010", "1/3"), index) @@ -387,7 +387,7 @@ func TestCellIndexIntersectionRandomCellUnions(t *testing.T) { func TestCellIndexIntersectionSemiRandomCellUnions(t *testing.T) { for i := 0; i < 200; i++ { index := &CellIndex{} - id := cellIDFromString("1/0123012301230123") + id := CellIDFromString("1/0123012301230123") var target CellUnion for j := 0; j < 100; j++ { switch {