You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
10 KiB
301 lines
10 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace KDTree
|
|
{
|
|
/// <summary>
|
|
/// A KD-Tree node which supports a generic number of dimensions. All data items
|
|
/// need the same number of dimensions.
|
|
/// This node splits based on the largest range of any dimension.
|
|
/// </summary>
|
|
/// <typeparam name="T">The generic data type this structure contains.</typeparam>
|
|
/// <remarks>This is based on this: https://bitbucket.org/rednaxela/knn-benchmark/src/tip/ags/utils/dataStructures/trees/thirdGenKD/ </remarks>
|
|
public class KDNode<T>
|
|
{
|
|
#region Internal properties and constructor
|
|
// All types
|
|
/// <summary>
|
|
/// The number of dimensions for this node.
|
|
/// </summary>
|
|
protected internal int iDimensions;
|
|
|
|
/// <summary>
|
|
/// The maximum capacity of this node.
|
|
/// </summary>
|
|
protected internal int iBucketCapacity;
|
|
|
|
// Leaf only
|
|
/// <summary>
|
|
/// The array of locations. [index][dimension]
|
|
/// </summary>
|
|
protected internal double[][] tPoints;
|
|
|
|
/// <summary>
|
|
/// The array of data values. [index]
|
|
/// </summary>
|
|
protected internal T[] tData;
|
|
|
|
// Stem only
|
|
/// <summary>
|
|
/// The left and right children.
|
|
/// </summary>
|
|
protected internal KDNode<T> pLeft, pRight;
|
|
/// <summary>
|
|
/// The split dimension.
|
|
/// </summary>
|
|
protected internal int iSplitDimension;
|
|
/// <summary>
|
|
/// The split value (larger go into the right, smaller go into left)
|
|
/// </summary>
|
|
protected internal double fSplitValue;
|
|
|
|
// Bounds
|
|
/// <summary>
|
|
/// The min and max bound for this node. All dimensions.
|
|
/// </summary>
|
|
protected internal double[] tMinBound, tMaxBound;
|
|
|
|
/// <summary>
|
|
/// Does this node represent only one point.
|
|
/// </summary>
|
|
protected internal bool bSinglePoint;
|
|
|
|
/// <summary>
|
|
/// Protected method which constructs a new KDNode.
|
|
/// </summary>
|
|
/// <param name="iDimensions">The number of dimensions for this node (all the same in the tree).</param>
|
|
/// <param name="iBucketCapacity">The initial capacity of the bucket.</param>
|
|
protected KDNode(int iDimensions, int iBucketCapacity)
|
|
{
|
|
// Variables.
|
|
this.iDimensions = iDimensions;
|
|
this.iBucketCapacity = iBucketCapacity;
|
|
this.Size = 0;
|
|
this.bSinglePoint = true;
|
|
|
|
// Setup leaf elements.
|
|
this.tPoints = new double[iBucketCapacity+1][];
|
|
this.tData = new T[iBucketCapacity+1];
|
|
}
|
|
#endregion
|
|
|
|
#region External Operations
|
|
/// <summary>
|
|
/// The number of items in this leaf node and all children.
|
|
/// </summary>
|
|
public int Size { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Is this KDNode a leaf or not?
|
|
/// </summary>
|
|
public bool IsLeaf { get { return tPoints != null; } }
|
|
|
|
/// <summary>
|
|
/// Insert a new point into this leaf node.
|
|
/// </summary>
|
|
/// <param name="tPoint">The position which represents the data.</param>
|
|
/// <param name="kValue">The value of the data.</param>
|
|
public void AddPoint(double[] tPoint, T kValue)
|
|
{
|
|
// Find the correct leaf node.
|
|
KDNode<T> pCursor = this;
|
|
while (!pCursor.IsLeaf)
|
|
{
|
|
// Extend the size of the leaf.
|
|
pCursor.ExtendBounds(tPoint);
|
|
pCursor.Size++;
|
|
|
|
// If it is larger select the right, or lower, select the left.
|
|
if (tPoint[pCursor.iSplitDimension] > pCursor.fSplitValue)
|
|
{
|
|
pCursor = pCursor.pRight;
|
|
}
|
|
else
|
|
{
|
|
pCursor = pCursor.pLeft;
|
|
}
|
|
}
|
|
|
|
// Insert it into the leaf.
|
|
pCursor.AddLeafPoint(tPoint, kValue);
|
|
}
|
|
#endregion
|
|
|
|
#region Internal Operations
|
|
/// <summary>
|
|
/// Insert the point into the leaf.
|
|
/// </summary>
|
|
/// <param name="tPoint">The point to insert the data at.</param>
|
|
/// <param name="kValue">The value at the point.</param>
|
|
private void AddLeafPoint(double[] tPoint, T kValue)
|
|
{
|
|
// Add the data point to this node.
|
|
tPoints[Size] = tPoint;
|
|
tData[Size] = kValue;
|
|
ExtendBounds(tPoint);
|
|
Size++;
|
|
|
|
// Split if the node is getting too large in terms of data.
|
|
if (Size == tPoints.Length - 1)
|
|
{
|
|
// If the node is getting too physically large.
|
|
if (CalculateSplit())
|
|
{
|
|
// If the node successfully had it's split value calculated, split node.
|
|
SplitLeafNode();
|
|
}
|
|
else
|
|
{
|
|
// If the node could not be split, enlarge node data capacity.
|
|
IncreaseLeafCapacity();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// If the point lies outside the boundaries, return false else true.
|
|
/// </summary>
|
|
/// <param name="tPoint">The point.</param>
|
|
/// <returns>True if the point is inside the boundaries, false outside.</returns>
|
|
private bool CheckBounds(double[] tPoint)
|
|
{
|
|
for (int i = 0; i < iDimensions; ++i)
|
|
{
|
|
if (tPoint[i] > tMaxBound[i]) return false;
|
|
if (tPoint[i] < tMinBound[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extend this node to contain a new point.
|
|
/// </summary>
|
|
/// <param name="tPoint">The point to contain.</param>
|
|
private void ExtendBounds(double[] tPoint)
|
|
{
|
|
// If we don't have bounds, create them using the new point then bail.
|
|
if (tMinBound == null)
|
|
{
|
|
tMinBound = new double[iDimensions];
|
|
tMaxBound = new double[iDimensions];
|
|
Array.Copy(tPoint, tMinBound, iDimensions);
|
|
Array.Copy(tPoint, tMaxBound, iDimensions);
|
|
return;
|
|
}
|
|
|
|
// For each dimension.
|
|
for (int i = 0; i < iDimensions; ++i)
|
|
{
|
|
if (Double.IsNaN(tPoint[i]))
|
|
{
|
|
if (!Double.IsNaN(tMinBound[i]) || !Double.IsNaN(tMaxBound[i]))
|
|
bSinglePoint = false;
|
|
|
|
tMinBound[i] = Double.NaN;
|
|
tMaxBound[i] = Double.NaN;
|
|
}
|
|
else if (tMinBound[i] > tPoint[i])
|
|
{
|
|
tMinBound[i] = tPoint[i];
|
|
bSinglePoint = false;
|
|
}
|
|
else if (tMaxBound[i] < tPoint[i])
|
|
{
|
|
tMaxBound[i] = tPoint[i];
|
|
bSinglePoint = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Double the capacity of this leaf.
|
|
/// </summary>
|
|
private void IncreaseLeafCapacity()
|
|
{
|
|
Array.Resize<double[]>(ref tPoints, tPoints.Length * 2);
|
|
Array.Resize<T>(ref tData, tData.Length * 2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Work out if this leaf node should split. If it should, a new split value and dimension is calculated
|
|
/// based on the dimension with the largest range.
|
|
/// </summary>
|
|
/// <returns>True if the node split, false if not.</returns>
|
|
private bool CalculateSplit()
|
|
{
|
|
// Don't split if we are just one point.
|
|
if (bSinglePoint)
|
|
return false;
|
|
|
|
// Find the dimension with the largest range. This will be our split dimension.
|
|
double fWidth = 0;
|
|
for (int i = 0; i < iDimensions; i++)
|
|
{
|
|
double fDelta = (tMaxBound[i] - tMinBound[i]);
|
|
if (Double.IsNaN(fDelta))
|
|
fDelta = 0;
|
|
|
|
if (fDelta > fWidth)
|
|
{
|
|
iSplitDimension = i;
|
|
fWidth = fDelta;
|
|
}
|
|
}
|
|
|
|
// If we are not wide (i.e. all the points are in one place), don't split.
|
|
if (fWidth == 0)
|
|
return false;
|
|
|
|
// Split in the middle of the node along the widest dimension.
|
|
fSplitValue = (tMinBound[iSplitDimension] + tMaxBound[iSplitDimension]) * 0.5;
|
|
|
|
// Never split on infinity or NaN.
|
|
if (fSplitValue == Double.PositiveInfinity)
|
|
fSplitValue = Double.MaxValue;
|
|
else if (fSplitValue == Double.NegativeInfinity)
|
|
fSplitValue = Double.MinValue;
|
|
|
|
// Don't let the split value be the same as the upper value as
|
|
// can happen due to rounding errors!
|
|
if (fSplitValue == tMaxBound[iSplitDimension])
|
|
fSplitValue = tMinBound[iSplitDimension];
|
|
|
|
// Success
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Split this leaf node by creating left and right children, then moving all the children of
|
|
/// this node into the respective buckets.
|
|
/// </summary>
|
|
private void SplitLeafNode()
|
|
{
|
|
// Create the new children.
|
|
pRight = new KDNode<T>(iDimensions, iBucketCapacity);
|
|
pLeft = new KDNode<T>(iDimensions, iBucketCapacity);
|
|
|
|
// Move each item in this leaf into the children.
|
|
for (int i = 0; i < Size; ++i)
|
|
{
|
|
// Store.
|
|
double[] tOldPoint = tPoints[i];
|
|
T kOldData = tData[i];
|
|
|
|
// If larger, put it in the right.
|
|
if (tOldPoint[iSplitDimension] > fSplitValue)
|
|
pRight.AddLeafPoint(tOldPoint, kOldData);
|
|
|
|
// If smaller, put it in the left.
|
|
else
|
|
pLeft.AddLeafPoint(tOldPoint, kOldData);
|
|
}
|
|
|
|
// Wipe the data from this KDNode.
|
|
tPoints = null;
|
|
tData = null;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|
|
|