using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; namespace KDTree { /// /// A NearestNeighbour iterator for the KD-tree which intelligently iterates and captures relevant data in the search space. /// /// The type of data the iterator should handle. public class NearestNeighbour : IEnumerator, IEnumerable { /// The point from which are searching in n-dimensional space. private double[] tSearchPoint; /// A distance function which is used to compare nodes and value positions. private DistanceFunctions kDistanceFunction; /// The tree nodes which have yet to be evaluated. private MinHeap> pPending; /// The values which have been evaluated and selected. private IntervalHeap pEvaluated; /// The root of the kd tree to begin searching from. private KDNode pRoot = null; /// The max number of points we can return through this iterator. private int iMaxPointsReturned = 0; /// The number of points we can still test before conclusion. private int iPointsRemaining; /// Threshold to apply to tree iteration. Negative numbers mean no threshold applied. private double fThreshold; /// Current value distance. private double _CurrentDistance = -1; /// Current value reference. private T _Current = default(T); /// /// Construct a new nearest neighbour iterator. /// /// The root of the tree to begin searching from. /// The point in n-dimensional space to search. /// The distance function used to evaluate the points. /// The max number of points which can be returned by this iterator. Capped to max in tree. /// Threshold to apply to the search space. Negative numbers indicate that no threshold is applied. public NearestNeighbour(KDNode pRoot, double[] tSearchPoint, DistanceFunctions kDistance, int iMaxPoints, double fThreshold) { // Check the dimensionality of the search point. if (tSearchPoint.Length != pRoot.iDimensions) throw new Exception("Dimensionality of search point and kd-tree are not the same."); // Store the search point. this.tSearchPoint = new double[tSearchPoint.Length]; Array.Copy(tSearchPoint, this.tSearchPoint, tSearchPoint.Length); // Store the point count, distance function and tree root. this.iPointsRemaining = Math.Min(iMaxPoints, pRoot.Size); this.fThreshold = fThreshold; this.kDistanceFunction = kDistance; this.pRoot = pRoot; this.iMaxPointsReturned = iMaxPoints; _CurrentDistance = -1; // Create an interval heap for the points we check. this.pEvaluated = new IntervalHeap(); // Create a min heap for the things we need to check. this.pPending = new MinHeap>(); this.pPending.Insert(0, pRoot); } /// /// Check for the next iterator item. /// /// True if we have one, false if not. public bool MoveNext() { // Bail if we are finished. if (iPointsRemaining == 0) { _Current = default(T); return false; } // While we still have paths to evaluate. while (pPending.Size > 0 && (pEvaluated.Size == 0 || (pPending.MinKey < pEvaluated.MinKey))) { // If there are pending paths possibly closer than the nearest evaluated point, check it out KDNode pCursor = pPending.Min; pPending.RemoveMin(); // Descend the tree, recording paths not taken while (!pCursor.IsLeaf) { KDNode pNotTaken; // If the seach point is larger, select the right path. if (tSearchPoint[pCursor.iSplitDimension] > pCursor.fSplitValue) { pNotTaken = pCursor.pLeft; pCursor = pCursor.pRight; } else { pNotTaken = pCursor.pRight; pCursor = pCursor.pLeft; } // Calculate the shortest distance between the search point and the min and max bounds of the kd-node. double fDistance = kDistanceFunction.DistanceToRectangle(tSearchPoint, pNotTaken.tMinBound, pNotTaken.tMaxBound); // If it is greater than the threshold, skip. if (fThreshold >= 0 && fDistance > fThreshold) { continue; } // Only add the path we need more points or the node is closer than furthest point on list so far. if (pEvaluated.Size < iPointsRemaining || fDistance <= pEvaluated.MaxKey) { pPending.Insert(fDistance, pNotTaken); } } // If all the points in this KD node are in one place. if (pCursor.bSinglePoint) { // Work out the distance between this point and the search point. double fDistance = kDistanceFunction.Distance(pCursor.tPoints[0], tSearchPoint); // Skip if the point exceeds the threshold. // Technically this should never happen, but be prescise. if (fThreshold >= 0 && fDistance >= fThreshold) continue; // Add the point if either need more points or it's closer than furthest on list so far. if (pEvaluated.Size < iPointsRemaining || fDistance <= pEvaluated.MaxKey) { for (int i = 0; i < pCursor.Size; ++i) { // If we don't need any more, replace max if (pEvaluated.Size == iPointsRemaining) pEvaluated.ReplaceMax(fDistance, pCursor.tData[i]); // Otherwise insert. else pEvaluated.Insert(fDistance, pCursor.tData[i]); } } } // If the points in the KD node are spread out. else { // Treat the distance of each point seperately. for (int i = 0; i < pCursor.Size; ++i) { // Compute the distance between the points. double fDistance = kDistanceFunction.Distance(pCursor.tPoints[i], tSearchPoint); // Skip if it exceeds the threshold. if (fThreshold >= 0 && fDistance >= fThreshold) continue; // Insert the point if we have more to take. if (pEvaluated.Size < iPointsRemaining) pEvaluated.Insert(fDistance, pCursor.tData[i]); // Otherwise replace the max. else if (fDistance < pEvaluated.MaxKey) pEvaluated.ReplaceMax(fDistance, pCursor.tData[i]); } } } // Select the point with the smallest distance. if (pEvaluated.Size == 0) return false; iPointsRemaining--; _CurrentDistance = pEvaluated.MinKey; _Current = pEvaluated.Min; pEvaluated.RemoveMin(); return true; } /// /// Reset the iterator. /// public void Reset() { // Store the point count and the distance function. this.iPointsRemaining = Math.Min(iMaxPointsReturned, pRoot.Size); _CurrentDistance = -1; // Create an interval heap for the points we check. this.pEvaluated = new IntervalHeap(); // Create a min heap for the things we need to check. this.pPending = new MinHeap>(); this.pPending.Insert(0, pRoot); } public T Current { get { return _Current; } } /// /// Return the distance of the current value to the search point. /// public double CurrentDistance { get { return _CurrentDistance; } } /// /// Return the current value referenced by the iterator as an object. /// object IEnumerator.Current { get { return _Current; } } /// /// Return the current value referenced by the iterator. /// T IEnumerator.Current { get { return _Current; } } public void Dispose() { } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator GetEnumerator() { return this; } } }