package ags.utils.dataStructures.trees.thirdGenKD; import java.io.Serializable; import java.util.Arrays; /** * */ class KdNode<T> implements Serializable { // All types protected int dimensions; protected int bucketCapacity; protected int size; // Leaf only protected double[][] points; protected Object[] data; // Stem only protected KdNode<T> left, right; protected int splitDimension; protected double splitValue; // Bounds protected double[] minBound, maxBound; protected boolean singlePoint; protected KdNode(int dimensions, int bucketCapacity) { // Init base this.dimensions = dimensions; this.bucketCapacity = bucketCapacity; this.size = 0; this.singlePoint = true; // Init leaf elements this.points = new double[bucketCapacity+1][]; this.data = new Object[bucketCapacity+1]; } /* -------- SIMPLE GETTERS -------- */ public int size() { return size; } public boolean isLeaf() { return points != null; } /* -------- OPERATIONS -------- */ public void addPoint(double[] point, T value) { KdNode<T> cursor = this; while (!cursor.isLeaf()) { cursor.extendBounds(point); cursor.size++; if (point[cursor.splitDimension] > cursor.splitValue) { cursor = cursor.right; } else { cursor = cursor.left; } } cursor.addLeafPoint(point, value); } /* -------- INTERNAL OPERATIONS -------- */ public void addLeafPoint(double[] point, T value) { // Add the data point points[size] = point; data[size] = value; extendBounds(point); size++; if (size == points.length - 1) { // If the node is getting too 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 increaseLeafCapacity(); } } } private boolean checkBounds(double[] point) { for (int i = 0; i < dimensions; i++) { if (point[i] > maxBound[i]) return false; if (point[i] < minBound[i]) return false; } return true; } private void extendBounds(double[] point) { if (minBound == null) { minBound = Arrays.copyOf(point, dimensions); maxBound = Arrays.copyOf(point, dimensions); return; } for (int i = 0; i < dimensions; i++) { if (Double.isNaN(point[i])) { if (!Double.isNaN(minBound[i]) || !Double.isNaN(maxBound[i])) { singlePoint = false; } minBound[i] = Double.NaN; maxBound[i] = Double.NaN; } else if (minBound[i] > point[i]) { minBound[i] = point[i]; singlePoint = false; } else if (maxBound[i] < point[i]) { maxBound[i] = point[i]; singlePoint = false; } } } private void increaseLeafCapacity() { points = Arrays.copyOf(points, points.length*2); data = Arrays.copyOf(data, data.length*2); } private boolean calculateSplit() { if (singlePoint) return false; double width = 0; for (int i = 0; i < dimensions; i++) { double dwidth = (maxBound[i] - minBound[i]); if (Double.isNaN(dwidth)) dwidth = 0; if (dwidth > width) { splitDimension = i; width = dwidth; } } if (width == 0) { return false; } // Start the split in the middle of the variance splitValue = (minBound[splitDimension] + maxBound[splitDimension]) * 0.5; // Never split on infinity or NaN if (splitValue == Double.POSITIVE_INFINITY) { splitValue = Double.MAX_VALUE; } else if (splitValue == Double.NEGATIVE_INFINITY) { splitValue = -Double.MAX_VALUE; } // Don't let the split value be the same as the upper value as // can happen due to rounding errors! if (splitValue == maxBound[splitDimension]) { splitValue = minBound[splitDimension]; } // Success return true; } private void splitLeafNode() { right = new KdNode<T>(dimensions, bucketCapacity); left = new KdNode<T>(dimensions, bucketCapacity); // Move locations into children for (int i = 0; i < size; i++) { double[] oldLocation = points[i]; Object oldData = data[i]; if (oldLocation[splitDimension] > splitValue) { right.addLeafPoint(oldLocation, (T) oldData); } else { left.addLeafPoint(oldLocation, (T) oldData); } } points = null; data = null; } }