From 5fd07ce7af22e28b381fea788b439beaaa7ad748 Mon Sep 17 00:00:00 2001 From: gamzeozgul Date: Fri, 4 Jul 2025 16:38:34 +0300 Subject: [PATCH] feat(dbscan): implement NearestNeighbor search in KDTree --- clusters/dbscan/kdtree.go | 36 ++++++++++++++++++++++++++++++++++ clusters/dbscan/kdtree_test.go | 26 ++++++++++++++++++++++++ index/quadtree/node.go | 3 ++- index/quadtree/quadtree.go | 15 ++++++++------ index/quadtree/root.go | 3 ++- index/spatial_index.go | 9 +++++---- 6 files changed, 80 insertions(+), 12 deletions(-) diff --git a/clusters/dbscan/kdtree.go b/clusters/dbscan/kdtree.go index a1e0b7d..8551de4 100644 --- a/clusters/dbscan/kdtree.go +++ b/clusters/dbscan/kdtree.go @@ -253,3 +253,39 @@ func (n *nodeSorter) Less(i, j int) bool { } return a < b } + +// NearestNeighbor returns the index and distance of the closest point in the KDTree to the given point. +func (tree *KDTree) NearestNeighbor(pt space.Point) (nearestIdx int, minDist float64) { + nearestIdx = -1 + minDist = 1e20 // sufficiently large + var search func(t *T) + search = func(t *T) { + if t == nil { + return + } + dist := DistanceSphericalFast(tree.Points[t.PointID], pt) + if dist < minDist { + nearestIdx = t.PointID + minDist = dist + } + // Check equal points + for _, eqID := range t.EqualIDs { + eqDist := DistanceSphericalFast(tree.Points[eqID], pt) + if eqDist < minDist { + nearestIdx = eqID + minDist = eqDist + } + } + diff := pt[t.split] - tree.Points[t.PointID][t.split] + first, second := t.left, t.right + if diff > 0 { + first, second = t.right, t.left + } + search(first) + if diff*diff < minDist*minDist { + search(second) + } + } + search(tree.Root) + return +} diff --git a/clusters/dbscan/kdtree_test.go b/clusters/dbscan/kdtree_test.go index 2faf5d2..bf21f96 100644 --- a/clusters/dbscan/kdtree_test.go +++ b/clusters/dbscan/kdtree_test.go @@ -206,3 +206,29 @@ func isSortedOnDim(dim int, nodes []int, pts pointSlice) bool { } return true } + +func TestNearestNeighbor(t *testing.T) { + // Sabit bir nokta kümesi + pts := clusters.PointList{ + space.Point{0, 0}, + space.Point{1, 1}, + space.Point{2, 2}, + space.Point{5, 5}, + } + tree := NewKDTree(pts) + tests := []struct { + query space.Point + expected int + }{ + {space.Point{0.1, 0.1}, 0}, + {space.Point{1.2, 1.1}, 1}, + {space.Point{2.1, 2.2}, 2}, + {space.Point{4.9, 5.1}, 3}, + } + for _, tc := range tests { + idx, _ := tree.NearestNeighbor(tc.query) + if idx != tc.expected { + t.Errorf("NearestNeighbor(%v) = %d, want %d", tc.query, idx, tc.expected) + } + } +} diff --git a/index/quadtree/node.go b/index/quadtree/node.go index 33fa3d6..32cbced 100644 --- a/index/quadtree/node.go +++ b/index/quadtree/node.go @@ -8,7 +8,8 @@ import ( ) // Node Represents a node of a Quadtree. Nodes contain -// items which have a spatial extent corresponding to the node's position in the quadtree. +// +// items which have a spatial extent corresponding to the node's position in the quadtree. type Node struct { Items []interface{} Subnode [4]*Node diff --git a/index/quadtree/quadtree.go b/index/quadtree/quadtree.go index d8db6a5..ae98371 100644 --- a/index/quadtree/quadtree.go +++ b/index/quadtree/quadtree.go @@ -1,5 +1,6 @@ // Package quadtree A Quadtree is a spatial index structure for efficient range querying -// of items bounded by 2D rectangles. +// +// of items bounded by 2D rectangles. package quadtree import ( @@ -11,17 +12,19 @@ import ( ) // Quadtree A Quadtree is a spatial index structure for efficient range querying -// of items bounded by 2D rectangles. -// Geometries can be indexed by using their Envelopes. -// Any type of Object can also be indexed as -// long as it has an extent that can be represented by an Envelope. +// +// of items bounded by 2D rectangles. +// Geometries can be indexed by using their Envelopes. +// Any type of Object can also be indexed as +// long as it has an extent that can be represented by an Envelope. type Quadtree struct { Root *Root MinExtent float64 } // EnsureExtent Ensure that the envelope for the inserted item has non-zero extents. -// Use the current minExtent to pad the envelope, if necessary +// +// Use the current minExtent to pad the envelope, if necessary func EnsureExtent(itemEnv *envelope.Envelope, minExtent float64) *envelope.Envelope { minx := itemEnv.MinX diff --git a/index/quadtree/root.go b/index/quadtree/root.go index cedee74..692c7fd 100644 --- a/index/quadtree/root.go +++ b/index/quadtree/root.go @@ -34,7 +34,8 @@ func (r *Root) Insert(itemEnv *envelope.Envelope, item interface{}) { } // InsertContained insert an item which is known to be contained in the tree rooted at -// the given QuadNode root. Lower levels of the tree will be created if necessary to hold the item. +// +// the given QuadNode root. Lower levels of the tree will be created if necessary to hold the item. func (r *Root) InsertContained(tree *Node, itemEnv *envelope.Envelope, item interface{}) { isZeroX := IsZeroWidth(itemEnv.MinX, itemEnv.MaxX) isZeroY := IsZeroWidth(itemEnv.MinY, itemEnv.MaxY) diff --git a/index/spatial_index.go b/index/spatial_index.go index 3bb1566..edacfa0 100644 --- a/index/spatial_index.go +++ b/index/spatial_index.go @@ -7,10 +7,11 @@ import ( // SpatialIndex The basic operations supported // implementing spatial index algorithms. -// A spatial index typically provides a primary filter for range rectangle queries. -// A secondary filter is required to test for exact intersection. -// The secondary filter may consist of other kinds of tests, -// such as testing other spatial relationships. +// +// A spatial index typically provides a primary filter for range rectangle queries. +// A secondary filter is required to test for exact intersection. +// The secondary filter may consist of other kinds of tests, +// such as testing other spatial relationships. type SpatialIndex interface { // Insert Adds a spatial item with an extent specified by the given Envelope to the index Insert(itemEnv *envelope.Envelope, item interface{}) error