From c5756f7fae1bf34e30925257b2d57a9be1b95455 Mon Sep 17 00:00:00 2001 From: shaoyu Date: Fri, 16 Feb 2024 23:25:24 +0800 Subject: [PATCH] fix minMaxDist floating point rounding errors This manifests itsef in https://github.com/dhconnelly/rtreego/issues/29 --- rtree.go | 15 ++++++++++----- rtree_test.go | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/rtree.go b/rtree.go index 5dee5a7..6db9cfb 100644 --- a/rtree.go +++ b/rtree.go @@ -32,6 +32,9 @@ type Rtree struct { // deleted is a temporary buffer to avoid memory allocations in Delete. // It is just an optimization and not part of the data structure. deleted []*node + + // FloatingPointTolerance is the tolerance to guard against floating point rounding errors during minMaxDist calculations. + FloatingPointTolerance float64 } // NewTree returns an Rtree. If the number of objects given on initialization @@ -39,10 +42,11 @@ type Rtree struct { // Minimizing Top-down bulk-loading algorithm. func NewTree(dim, min, max int, objs ...Spatial) *Rtree { rt := &Rtree{ - Dim: dim, - MinChildren: min, - MaxChildren: max, - height: 1, + Dim: dim, + MinChildren: min, + MaxChildren: max, + height: 1, + FloatingPointTolerance: 1e-6, root: &node{ entries: []entry{}, leaf: true, @@ -765,7 +769,8 @@ func (tree *Rtree) nearestNeighbor(p Point, n *node, d float64, nearest Spatial) for _, e := range n.entries { minDist := p.minDist(e.bb) - if minDist > minMinMaxDist { + // Add a bit of tolerance to guard against floating point rounding errors. + if minDist > minMinMaxDist+tree.FloatingPointTolerance { continue } diff --git a/rtree_test.go b/rtree_test.go index 7a1f860..f280aa3 100644 --- a/rtree_test.go +++ b/rtree_test.go @@ -1354,6 +1354,24 @@ func TestNearestNeighborsHalf(t *testing.T) { } } +func TestMinMaxDistFloatingPointRoundingError(t *testing.T) { + rects := []Rect{ + Point{1134900, 15600}.ToRect(0), + Point{1134900, 25600}.ToRect(0), + Point{1134900, 22805}.ToRect(0), + Point{1134900, 29116}.ToRect(0), + } + things := make([]Spatial, 0, len(rects)) + for i := range rects { + things = append(things, &rects[i]) + } + rt := NewTree(2, 1, 2, things...) + n := rt.NearestNeighbor(Point{1134851.8, 25570.8}) + if n != things[1] { + t.Fatalf("wrong neighbor, expected %v, got %v", things[1], n) + } +} + func ensureOrderedSubset(t *testing.T, actual []Spatial, expected []Spatial) { for i := range actual { if len(expected)-1 < i || actual[i] != expected[i] {