From 4b3ce41261462c5d4d501748db452068ad65c4fd Mon Sep 17 00:00:00 2001 From: riquezhang Date: Thu, 19 Dec 2024 17:54:34 +0800 Subject: [PATCH 1/2] feat: format rtree to string --- rtree.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/rtree.go b/rtree.go index 6db9cfb..0045825 100644 --- a/rtree.go +++ b/rtree.go @@ -9,6 +9,7 @@ import ( "fmt" "math" "sort" + "strings" ) // Comparator compares two spatials and returns whether they are equal. @@ -71,7 +72,7 @@ func (tree *Rtree) Size() int { } func (tree *Rtree) String() string { - return "foo" + return fmt.Sprintf(`{"size":%d,"depth":%d,"root":%v}`, tree.size, tree.height, tree.root) } // Depth returns the maximum depth of tree. @@ -223,7 +224,17 @@ type node struct { } func (n *node) String() string { - return fmt.Sprintf("node{leaf: %v, entries: %v}", n.leaf, n.entries) + format := func(v []entry) string { + var s []string + for _, e := range v { + s = append(s, fmt.Sprintf("%v", e)) + } + return fmt.Sprintf(`[%v]`, strings.Join(s, ",")) + } + if n.leaf { + return fmt.Sprintf(`{"leaf":%v,"entries":%v}`, n.leaf, format(n.entries)) + } + return fmt.Sprintf(`{"entries":%v}`, format(n.entries)) } // entry represents a spatial index record stored in a tree node. @@ -235,9 +246,9 @@ type entry struct { func (e entry) String() string { if e.child != nil { - return fmt.Sprintf("entry{bb: %v, child: %v}", e.bb, e.child) + return fmt.Sprintf(`{"bb":"%v","child":%v}`, e.bb, e.child) } - return fmt.Sprintf("entry{bb: %v, obj: %v}", e.bb, e.obj) + return fmt.Sprintf(`{"bb":"%v"}`, e.bb) } // Spatial is an interface for objects that can be stored in an Rtree and queried. From 78df826bb1bac5f76de7d2a6f2c1ca9ff0e89877 Mon Sep 17 00:00:00 2001 From: riquezhang Date: Thu, 19 Dec 2024 19:54:51 +0800 Subject: [PATCH 2/2] bugfix: adjust the level of the root when deleting nodes --- rtree.go | 33 ++++++++++++++++++++++++++++++++- rtree_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/rtree.go b/rtree.go index 0045825..3a63d8a 100644 --- a/rtree.go +++ b/rtree.go @@ -560,7 +560,38 @@ func (tree *Rtree) DeleteWithComparator(obj Spatial, cmp Comparator) bool { tree.condenseTree(n) tree.size-- - if !tree.root.leaf && len(tree.root.entries) == 1 { + /* + when the tree is deep, and deleting nodes, will cause the issue. + the tree could be like this: one obj but 3 levels depth. + { + "size": 1, + "depth": 3, + "root": { + "entries": [ + { + "bb": "[1.00, 2.00]x[1.00, 2.00]", + "child": { + "entries": [ + { + "bb": "[1.00, 2.00]x[1.00, 2.00]", + "child": { + "leaf": true, + "entries": [ + { + "bb": "[1.00, 2.00]x[1.00, 2.00]" + } + ] + } + } + ] + } + } + ] + } + } + so we need to merge the root in loop, instead of once. + */ + for !tree.root.leaf && len(tree.root.entries) == 1 { tree.root = tree.root.entries[0].child } diff --git a/rtree_test.go b/rtree_test.go index cd54d84..2d621de 100644 --- a/rtree_test.go +++ b/rtree_test.go @@ -1371,6 +1371,46 @@ func TestMinMaxDistFloatingPointRoundingError(t *testing.T) { } } +func TestInsertThenDeleteAllInDifferentOrder(t *testing.T) { + rects := []Rect{ + mustRect(Point{1, 1}, []float64{1, 1}), + mustRect(Point{2, 2}, []float64{1, 1}), + mustRect(Point{3, 3}, []float64{1, 1}), + mustRect(Point{4, 4}, []float64{1, 1}), + mustRect(Point{5, 5}, []float64{1, 1}), + } + things := []Spatial{} + for i := range rects { + things = append(things, &rects[i]) + } + + deleteOrders := [][]int{ + {0, 1, 2, 3, 4}, + // in this case, the last delete will cause the issue: no thing but 2 levels depth. + // {"size":0,"depth":2,"root":{"entries":[]}} + {1, 2, 3, 4, 0}, + } + for _, order := range deleteOrders { + rt := NewTree(2, 2, 2) + for _, thing := range things { + rt.Insert(thing) + } + if rt.Size() != 5 { + t.Errorf("Insert failed to insert") + } + + for _, idx := range order { + rt.Delete(things[idx]) + } + if rt.Size() != 0 { + t.Errorf("Delete failed to delete, got size: %d, expected size: 0", rt.Size()) + } + if rt.Depth() != 1 { + t.Errorf("Delete failed to delete, got depth: %d, expected depth: 1", rt.Depth()) + } + } +} + func ensureOrderedSubset(t *testing.T, actual []Spatial, expected []Spatial) { for i := range actual { if len(expected)-1 < i || actual[i] != expected[i] {