Skip to content

Commit

Permalink
add kd tree algorithm.
Browse files Browse the repository at this point in the history
  • Loading branch information
tianpf committed Aug 16, 2021
1 parent b7b0f18 commit 44a5255
Show file tree
Hide file tree
Showing 4 changed files with 494 additions and 0 deletions.
1 change: 1 addition & 0 deletions algorithm/operation/valid_op.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package operation define valid func for geometries.
package operation

import (
Expand Down
33 changes: 33 additions & 0 deletions index/kdtree/kd_node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package kdtree

import "github.com/spatial-go/geoos/algorithm/matrix"

// KdNode A node of a KdTree, which represents one or more points in the same location.
type KdNode struct {
matrix.Matrix
Data interface{}
Left *KdNode
//the right node of the tree
Right *KdNode
Count int
}

// X Returns the X coordinate of the node
func (k *KdNode) X() float64 {
return k.Matrix[0]
}

// Y Returns the Y coordinate of the node
func (k *KdNode) Y() float64 {
return k.Matrix[1]
}

// Increments counts of points at this location
func (k *KdNode) increment() {
k.Count++
}

// IsRepeated Tests whether more than one point with this value have been inserted (up to the tolerance)
func (k *KdNode) IsRepeated() bool {
return k.Count > 1
}
293 changes: 293 additions & 0 deletions index/kdtree/kd_tree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
// Package kdtree Contains classes which implement a k-D tree index over 2-D point data.
package kdtree

import (
"github.com/spatial-go/geoos/algorithm/matrix"
"github.com/spatial-go/geoos/algorithm/matrix/envelope"
"github.com/spatial-go/geoos/algorithm/measure"
)

// KdTree An implementation of a 2-D KD-Tree. KD-trees provide fast range searching and fast lookup for point data.
// This implementation supports detecting and snapping points which are closer
// than a given distance tolerance.
// If the same point (up to tolerance) is inserted
// more than once, it is snapped to the existing node.
// In other words, if a point is inserted which lies within the tolerance of a node already in the index,
// it is snapped to that node.
// When a point is snapped to a node then a new node is not created but the count of the existing node
// is incremented.
// If more than one node in the tree is within tolerance of an inserted point,
// the closest and then lowest node is snapped to.
type KdTree struct {
root *KdNode
numberOfNodes int64
tolerance float64
}

// ToMatrixesNotIncludeRepeated Converts a collection of KdNodes to an array of matrixes.
func (k *KdTree) ToMatrixesNotIncludeRepeated(kdnodes []*KdNode) []matrix.Matrix {
return k.ToMatrixes(kdnodes, false)
}

// ToMatrixes Converts a collection of KdNodes to an array of matrixes.
// specifying whether repeated nodes should be represented by multiple coordinates.
func (k *KdTree) ToMatrixes(kdnodes []*KdNode, includeRepeated bool) []matrix.Matrix {
ms := []matrix.Matrix{}
for _, node := range kdnodes {
count := 1
if includeRepeated {
count = node.Count
}
for i := 0; i < count; i++ {
ms = append(ms, node.Matrix)
}
}
return ms
}

// IsEmpty Tests whether the index contains any items.
func (k *KdTree) IsEmpty() bool {
if k.root == nil {
return true
}
return false
}

// InsertNoData Inserts a new point in the kd-tree, with no data.
func (k *KdTree) InsertNoData(p matrix.Matrix) *KdNode {
return k.Insert(p, nil)
}

//Insert Inserts a new point into the kd-tree.
func (k *KdTree) Insert(p matrix.Matrix, data interface{}) *KdNode {
if k.root == nil {
k.root = &KdNode{Matrix: p, Data: data}
return k.root
}

/**
* Check if the point is already in the tree, up to tolerance.
* If tolerance is zero, this phase of the insertion can be skipped.
*/
if k.tolerance > 0 {
matchNode := k.FindBestMatchNode(p)
if matchNode != nil {
// point already in index - increment counter
matchNode.increment()
return matchNode
}
}

return k.insertExact(p, data)
}

// FindBestMatchNode Finds the node in the tree which is the best match for a point
// being inserted.
// The match is made deterministic by returning the lowest of any nodes which
// lie the same distance from the point.
// There may be no match if the point is not within the distance tolerance of any
// existing node.
func (k *KdTree) FindBestMatchNode(p matrix.Matrix) *KdNode {
visitor := &BestMatchVisitor{Matrix: p, tolerance: k.tolerance}
k.QueryVisitor(visitor.QueryEnvelope(), visitor)
return visitor.MatchNode
}

// insertExact Inserts a point known to be beyond the distance tolerance of any existing node.
// The point is inserted at the bottom of the exact splitting path,
// so that tree shape is deterministic.
func (k *KdTree) insertExact(p matrix.Matrix, data interface{}) *KdNode {
currentNode := k.root
leafNode := k.root
isOddLevel := true
isLessThan := true

/**
* Traverse the tree, first cutting the plane left-right (by X ordinate)
* then top-bottom (by Y ordinate)
*/
for currentNode != nil {
isInTolerance := measure.PlanarDistance(p, currentNode.Matrix) <= k.tolerance

// check if point is already in tree (up to tolerance) and if so simply
// return existing node
if isInTolerance {
currentNode.increment()
return currentNode
}

if isOddLevel {
isLessThan = p[0] < currentNode.X()
} else {
isLessThan = p[1] < currentNode.Y()
}
leafNode = currentNode
if isLessThan {
//System.out.print("L");
currentNode = currentNode.Left
} else {
//System.out.print("R");
currentNode = currentNode.Right
}

isOddLevel = !isOddLevel
}
//System.out.println("<<");
// no node found, add new leaf node to tree
k.numberOfNodes++
node := &KdNode{Matrix: p, Data: data}
if isLessThan {
leafNode.Left = node
} else {
leafNode.Right = node
}
return node
}

// QueryNode Performs a range search of the points in the index and visits all nodes found.
func (k *KdTree) QueryNode(currentNode *KdNode,
queryEnv *envelope.Envelope, odd bool, visitor *BestMatchVisitor) {
if currentNode == nil {
return
}
var min, max, discriminant float64
if odd {
min = queryEnv.MinX
max = queryEnv.MaxX
discriminant = currentNode.X()
} else {
min = queryEnv.MinY
max = queryEnv.MaxY
discriminant = currentNode.Y()
}
searchLeft := min < discriminant
searchRight := discriminant <= max

// search is computed via in-order traversal
if searchLeft {
k.QueryNode(currentNode.Left, queryEnv, !odd, visitor)
}
if queryEnv.Contains(envelope.Matrix(currentNode.Matrix)) {
visitor.VisitItem(currentNode)
}
if searchRight {
k.QueryNode(currentNode.Right, queryEnv, !odd, visitor)
}

}

// QueryNodePoint Performs a range search of the points in the index and visits all nodes found.
func (k *KdTree) QueryNodePoint(currentNode *KdNode,
queryPt matrix.Matrix, odd bool) *KdNode {
if currentNode == nil {
return nil
}
if currentNode.Matrix.Equals(queryPt) {
return currentNode
}
var ord, discriminant float64
if odd {
ord = queryPt[0]
discriminant = currentNode.X()
} else {
ord = queryPt[1]
discriminant = currentNode.Y()
}
searchLeft := ord < discriminant

if searchLeft {
return k.QueryNodePoint(currentNode.Left, queryPt, !odd)
}
return k.QueryNodePoint(currentNode.Right, queryPt, !odd)
}

// QueryVisitor Performs a range search of the points in the index and visits all nodes found.
func (k *KdTree) QueryVisitor(queryEnv *envelope.Envelope, visitor *BestMatchVisitor) {
k.QueryNode(k.root, queryEnv, true, visitor)
}

// QueryEnv Performs a range search of the points in the index.
func (k *KdTree) QueryEnv(qEnv *envelope.Envelope) *BestMatchVisitor {
result := &BestMatchVisitor{}
k.QueryVisitor(qEnv, result)
return result
}

// Query Searches for a given point in the index and returns its node if found.
func (k *KdTree) Query(queryPt matrix.Matrix) *KdNode {
return k.QueryNodePoint(k.root, queryPt, true)
}

// Depth Computes the Depth of the tree.
func (k *KdTree) Depth() int {
return k.DepthNode(k.root)
}

// DepthNode Computes the Depth of the tree.
func (k *KdTree) DepthNode(currentNode *KdNode) int {
if currentNode == nil {
return 0
}
dL := k.DepthNode(currentNode.Left)
dR := k.DepthNode(currentNode.Right)
if dL > dR {
return 1 + dL
}
return 1 + dR
}

// Size Computes the Size (number of items) in the tree.
func (k *KdTree) Size() int {
return k.SizeNode(k.root)
}

// SizeNode Computes the Size (number of items) in the tree.
func (k *KdTree) SizeNode(currentNode *KdNode) int {
if currentNode == nil {
return 0
}
sizeL := k.SizeNode(currentNode.Left)
sizeR := k.SizeNode(currentNode.Right)
return 1 + sizeL + sizeR
}

// BestMatchVisitor A visitor for items in a SpatialIndex.
type BestMatchVisitor struct {
tolerance float64
MatchNode *KdNode
matchDist float64
matrix.Matrix
}

// QueryEnvelope ...
func (b *BestMatchVisitor) QueryEnvelope() *envelope.Envelope {
queryEnv := envelope.Matrix(b.Matrix)
queryEnv.ExpandBy(b.tolerance)
return queryEnv
}

// VisitItem Visits an item in the index.
func (b *BestMatchVisitor) VisitItem(node *KdNode) {
dist := measure.PlanarDistance(b.Matrix, node.Matrix)
isInTolerance := dist <= b.tolerance
if !isInTolerance {
return
}
update := false

if b.MatchNode == nil || dist < b.matchDist {
update = true
}
// if distances are the same, record the lesser coordinate
if b.MatchNode != nil {
comp, _ := node.Matrix.Compare(b.MatchNode.Matrix)
if dist == b.matchDist && comp < 1 {
update = true
}
}

if update {
b.MatchNode = node
b.matchDist = dist
}
}
Loading

0 comments on commit 44a5255

Please sign in to comment.