using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace KDTree { /// /// 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. /// /// The generic data type this structure contains. /// This is based on this: https://bitbucket.org/rednaxela/knn-benchmark/src/tip/ags/utils/dataStructures/trees/thirdGenKD/ public class KDNode { #region Internal properties and constructor // All types /// /// The number of dimensions for this node. /// protected internal int iDimensions; /// /// The maximum capacity of this node. /// protected internal int iBucketCapacity; // Leaf only /// /// The array of locations. [index][dimension] /// protected internal double[][] tPoints; /// /// The array of data values. [index] /// protected internal T[] tData; // Stem only /// /// The left and right children. /// protected internal KDNode pLeft, pRight; /// /// The split dimension. /// protected internal int iSplitDimension; /// /// The split value (larger go into the right, smaller go into left) /// protected internal double fSplitValue; // Bounds /// /// The min and max bound for this node. All dimensions. /// protected internal double[] tMinBound, tMaxBound; /// /// Does this node represent only one point. /// protected internal bool bSinglePoint; /// /// Protected method which constructs a new KDNode. /// /// The number of dimensions for this node (all the same in the tree). /// The initial capacity of the bucket. 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 /// /// The number of items in this leaf node and all children. /// public int Size { get; private set; } /// /// Is this KDNode a leaf or not? /// public bool IsLeaf { get { return tPoints != null; } } /// /// Insert a new point into this leaf node. /// /// The position which represents the data. /// The value of the data. public void AddPoint(double[] tPoint, T kValue) { // Find the correct leaf node. KDNode 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 /// /// Insert the point into the leaf. /// /// The point to insert the data at. /// The value at the point. 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(); } } } /// /// If the point lies outside the boundaries, return false else true. /// /// The point. /// True if the point is inside the boundaries, false outside. 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; } /// /// Extend this node to contain a new point. /// /// The point to contain. 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; } } } /// /// Double the capacity of this leaf. /// private void IncreaseLeafCapacity() { Array.Resize(ref tPoints, tPoints.Length * 2); Array.Resize(ref tData, tData.Length * 2); } /// /// 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. /// /// True if the node split, false if not. 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; } /// /// Split this leaf node by creating left and right children, then moving all the children of /// this node into the respective buckets. /// private void SplitLeafNode() { // Create the new children. pRight = new KDNode(iDimensions, iBucketCapacity); pLeft = new KDNode(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 } }