using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using GeoAPI.Geometries;
using GisSharpBlog.NetTopologySuite.Geometries;
namespace GisSharpBlog.NetTopologySuite.Index.KdTree
{
///
/// An implementation of a 2-D KD-Tree. KD-trees provide fast range searching on point data.
///
///
/// This implementation supports detecting and snapping points which are closer than a given
/// tolerance value. If the same point (up to tolerance) is inserted more than once a new node is
/// not created but the count of the existing node is incremented.
///
/// The type of the user data object
/// David Skea
/// Martin Davis
public class KdTree
where T : class
{
private KdNode _root;
// ReSharper disable once UnusedField.Compiler
private KdNode _last = null;
private long _numberOfNodes;
private readonly double _tolerance;
///
/// Creates a new instance of a KdTree with a snapping tolerance of 0.0.
/// (I.e. distinct points will not be snapped)
///
public KdTree()
: this(0.0)
{
}
///
/// Creates a new instance of a KdTree, specifying a snapping distance tolerance.
/// Points which lie closer than the tolerance to a point already
/// in the tree will be treated as identical to the existing point.
///
/// The tolerance distance for considering two points equal
public KdTree(double tolerance)
{
_tolerance = tolerance;
}
///
/// Tests whether the index contains any items.
///
public bool IsEmpty
{
get
{
if (_root == null) return true;
return false;
}
}
///
/// Inserts a new point in the kd-tree, with no data.
///
/// The point to insert
/// The kdnode containing the point
public KdNode Insert(ICoordinate p)
{
return Insert(p, null);
}
///
/// Inserts a new point into the kd-tree.
///
/// The point to insert
/// A data item for the point
///
/// A new KdNode if a new point is inserted, else an existing
/// node is returned with its counter incremented. This can be checked
/// by testing returnedNode.getCount() > 1.
///
public KdNode Insert(ICoordinate p, T data)
{
if (_root == null)
{
_root = new KdNode(p, data);
return _root;
}
var currentNode = _root;
var leafNode = _root;
var isOddLevel = true;
var isLessThan = true;
/**
* Traverse the tree,
* first cutting the plane left-right (by X ordinate)
* then top-bottom (by Y ordinate)
*/
while (currentNode != _last)
{
// test if point is already a node
if (currentNode != null)
{
var isInTolerance = p.Distance(currentNode.Coordinate) <= _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)
{
// ReSharper disable once PossibleNullReferenceException
isLessThan = p.X < currentNode.X;
}
else
{
// ReSharper disable once PossibleNullReferenceException
isLessThan = p.Y < currentNode.Y;
}
leafNode = currentNode;
currentNode = isLessThan
? currentNode.Left
: currentNode.Right;
isOddLevel = !isOddLevel;
}
// no node found, add new leaf node to tree
_numberOfNodes = _numberOfNodes + 1;
var node = new KdNode(p, data);
node.Left = null;
node.Right = null;
if (isLessThan)
{
leafNode.Left = node;
}
else
{
leafNode.Right = node;
}
return node;
}
private static void QueryNode(KdNode currentNode, KdNode bottomNode,
Envelope queryEnv, bool odd, ICollection> result)
{
if (currentNode == null)
return;
if (currentNode == bottomNode)
return;
double min;
double max;
double discriminant;
if (odd)
{
min = queryEnv.MinX;
max = queryEnv.MaxX;
discriminant = currentNode.X;
}
else
{
min = queryEnv.MinY;
max = queryEnv.MaxY;
discriminant = currentNode.Y;
}
bool searchLeft = min < discriminant;
bool searchRight = discriminant <= max;
if (searchLeft)
{
QueryNode(currentNode.Left, bottomNode, queryEnv, !odd, result);
}
if (queryEnv.Contains(currentNode.Coordinate))
{
result.Add(currentNode);
}
if (searchRight)
{
QueryNode(currentNode.Right, bottomNode, queryEnv, !odd, result);
}
}
///
/// Performs a range search of the points in the index.
///
/// The range rectangle to query
/// A collection of the KdNodes found
public ICollection> Query(Envelope queryEnv)
{
KdNode last = null;
ICollection> result = new Collection>();
QueryNode(_root, _last, queryEnv, true, result);
return result;
}
///
/// Performs a range search of the points in the index.
///
/// The range rectangle to query
/// A collection to accumulate the result nodes into
public void Query(Envelope queryEnv, ICollection> result)
{
QueryNode(_root, _last, queryEnv, true, result);
}
private static void NearestNeighbor(KdNode currentNode, KdNode bottomNode,
ICoordinate queryCoordinate, ref KdNode closestNode, ref double closestDistanceSq)
{
while (true)
{
if (currentNode == null)
return;
if (currentNode == bottomNode)
return;
var distSq = Math.Pow(currentNode.X - queryCoordinate.X, 2) +
Math.Pow(currentNode.Y - queryCoordinate.Y, 2);
if (distSq < closestDistanceSq)
{
closestNode = currentNode;
closestDistanceSq = distSq;
}
var searchLeft = false;
var searchRight = false;
if (currentNode.Left != null)
searchLeft = (Math.Pow(currentNode.Left.X - queryCoordinate.X, 2) +
Math.Pow(currentNode.Left.Y - queryCoordinate.Y, 2)) < closestDistanceSq;
if (currentNode.Right != null)
searchRight = (Math.Pow(currentNode.Right.X - queryCoordinate.X, 2) +
Math.Pow(currentNode.Right.Y - queryCoordinate.Y, 2)) < closestDistanceSq;
if (searchLeft)
{
NearestNeighbor(currentNode.Left, bottomNode, queryCoordinate, ref closestNode,
ref closestDistanceSq);
}
if (searchRight)
{
currentNode = currentNode.Right;
continue;
}
break;
}
}
///
/// Performs a nearest neighbor search of the points in the index.
///
/// The point to search the nearset neighbor for
public KdNode NearestNeighbor(ICoordinate coord)
{
KdNode result = null;
var closestDistSq = double.MaxValue;
NearestNeighbor(_root, _last, coord, ref result, ref closestDistSq);
return result;
}
}
}