com.roklenarcic.tree.KDTreeDouble Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of small-world Show documentation
Show all versions of small-world Show documentation
A library for finding nearest neighbour in a small dataset
The newest version!
package com.roklenarcic.tree;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
/**
* 2-D tree with coordinates of the double type.
*
* It is instantiated with a list of points, and the coordinates of the area of the map. The area information
* is used in wrapping.
*
* The maximum area coordinates are limited to [-3.7E153...3.7E153].
*
*
* @author Rok Lenarcic
*
* @param
* Value to be stored in points.
*/
public class KDTreeDouble {
private static final double MAX_COORD_VAL = (Math.sqrt(Double.MAX_VALUE) / 1.81) / 2;
@SuppressWarnings("unchecked")
private final Comparator>[] comparators = (Comparator>[]) new Comparator>[] { Point.createComparator(0), Point.createComparator(1) };
private final Point root;
private final double xMax;
private final double xMin;
private final double yMax;
private final double yMin;
/**
* Build a tree from list of points, where points are given by the objects of the Point class. The map
* area is limited by the limits given, inclusive.
*
* @param points
* list of points with coordinates and values
* @param xMin
* minimum value of x coordinate of a point, inclusive
* @param yMin
* minimum value of y coordinate of a point, inclusive
* @param xMax
* maximum value of x coordinate of a point, inclusive
* @param yMax
* maximum value of y coordinate of a point, inclusive
*/
public KDTreeDouble(List> points, double xMin, double yMin, double xMax, double yMax) {
if (xMin < xMax && yMin < yMax) {
this.xMin = xMin;
this.yMin = yMin;
this.xMax = xMax;
this.yMax = yMax;
if (xMax > MAX_COORD_VAL || xMin < -MAX_COORD_VAL || yMax > MAX_COORD_VAL || yMin < -MAX_COORD_VAL) {
throw new IllegalArgumentException("Area limits too big, out of [-3.7E153...3.7E153] interval.");
}
root = buildTree(points, 0);
} else {
throw new IllegalArgumentException("Area limits are not correctly ordered: " + xMin + " < " + xMax + " " + yMin + " < " + yMax);
}
}
/**
* Find the nearest point to the coordinates given, that is within the maximum distance given, inclusive.
* The smaller the maximum distance, the faster the query.
*
* @param x
* x coordinate of the query point
* @param y
* y coordinate of the query point
* @param maxDistance
* maximum distance from the point of the query to search
* @return the point in the tree closest to the coordinates given within max distance
*/
public Point findNearest(double x, double y, double maxDistance) {
NearestPoint nearest = new NearestPoint();
if (root != null) {
double md = maxDistance;
nearest.distance = md * md;
root.findNearest(x, y, nearest);
}
return nearest.p;
}
/**
* Find a number of nearest points to the coordinates given that are within the maximum distance given,
* inclusive. The smaller the maximum distance, the faster the query. The points returned are sorted from
* the closest to the farthest.
*
* @param x
* x coordinate of the query point
* @param y
* y coordinate of the query point
* @param maxDistance
* maximum distance from the point of the query to search
* @param numberOfNearest
* number of points to return
* @return an iterable of points in the tree closest to the coordinates given, in order of ascending
* distance
*/
public Iterable> findNearest(double x, double y, double maxDistance, int numberOfNearest) {
if (root != null) {
double md = maxDistance;
LinkedList nearestPoints = LinkedList.constructChain(numberOfNearest, md * md);
nearestPoints = root.findNearest(x, y, nearestPoints).dropEmptyPrefix();
if (nearestPoints != null) {
return nearestPoints.reverse();
} else {
return Collections.emptyList();
}
} else {
return Collections.emptyList();
}
}
/**
* Find the nearest point to the coordinates given, that is within the maximum distance given, inclusive.
* The smaller the maximum distance, the faster the query. The search wraps across the x axis, where
* borders used are the ones given in the constructor. Borders are inclusive.
*
* @param x
* x coordinate of the query point
* @param y
* y coordinate of the query point
* @param maxDistance
* maximum distance from the point of the query to search
* @return the point in the tree closest to the coordinates given within max distance
*/
public Point findNearestWithWrapping(double x, double y, double maxDistance) {
NearestPoint nearest = new NearestPoint();
if (root != null) {
nearest.distance = maxDistance * maxDistance;
root.findNearest(x, y, nearest);
if (nearest.distance > 0) {
// No point found within max distance, see if point + max distance crosses the wrap
// It can only wrap around the nearest border.
if (xMin + xMax > x / 2) {
double distanceToLeftBorder = x - xMin;
if (distanceToLeftBorder * distanceToLeftBorder < nearest.distance) {
root.findNearest(xMax + distanceToLeftBorder + 1, y, nearest);
}
} else {
double distanceToRightBorder = xMax - x;
if (distanceToRightBorder * distanceToRightBorder < nearest.distance) {
root.findNearest(xMin - distanceToRightBorder - 1, y, nearest);
}
}
}
}
return nearest.p;
}
/**
* Find a number of nearest points to the coordinates given that are within the maximum distance given,
* inclusive. The smaller the maximum distance, the faster the query. The points returned are sorted from
* the closest to the farthest.
*
* The search wraps across the x axis, where borders used are the ones given in the constructor. Borders
* are inclusive.
*
* @param x
* x coordinate of the query point
* @param y
* y coordinate of the query point
* @param maxDistance
* maximum distance from the point of the query to search
* @param numberOfNearest
* number of points to return
* @return an iterable of points in the tree closest to the coordinates given, in order of ascending
* distance
*/
public Iterable> findNearestWithWrapping(double x, double y, double maxDistance, int numberOfNearest) {
if (root != null) {
double md = maxDistance;
LinkedList nearestPoints = LinkedList.constructChain(numberOfNearest, md * md);
nearestPoints = root.findNearest(x, y, nearestPoints);
if (nearestPoints.distance > 0) {
// No point found within max distance, see if point + max distance crosses the wrap
// It can only wrap around the nearest border.
if (xMin + xMax > x / 2) {
double distanceToLeftBorder = x - xMin;
if (distanceToLeftBorder * distanceToLeftBorder < nearestPoints.distance) {
nearestPoints = root.findNearest(xMax + distanceToLeftBorder + 1, y, nearestPoints);
}
} else {
double distanceToRightBorder = xMax - x;
if (distanceToRightBorder * distanceToRightBorder < nearestPoints.distance) {
nearestPoints = root.findNearest(xMin - distanceToRightBorder - 1, y, nearestPoints);
}
}
}
nearestPoints = nearestPoints.dropEmptyPrefix();
if (nearestPoints != null) {
return nearestPoints.reverse();
} else {
return Collections.emptyList();
}
} else {
return Collections.emptyList();
}
}
private Point buildTree(List> points, int axis) {
if (points.size() == 0) {
return null;
} else {
// Sort by axis.
Collections.sort(points, comparators[axis]);
int pivotIdx = points.size() >> 1;
if ((points.size() & 1) == 0) { // If odd size
// Shift pivot to the left every second level so for lists of size 4
// the pivot is idx 1 and 2 every other level.
pivotIdx -= axis;
}
Point p = points.get(pivotIdx);
p.rotate(axis);
if (p.x > xMax || p.x < xMin || p.y > yMax || p.y < yMin) {
throw new IllegalArgumentException("Point " + p + " has coordinates out of the tree area.");
}
axis = axis ^ 1;
// Build subtree. Bigger branch also contains points that has equal axis value to the pivot.
p.smaller = buildTree(points.subList(0, pivotIdx), axis);
p.bigger = buildTree(points.subList(pivotIdx + 1, points.size()), axis);
return p;
}
}
/**
* Point in the 2-D space with a user specified value attached to it.
*
* @author Rok Lenarcic
*
* @param
* type of value
*/
public static class Point {
private static Comparator> createComparator(final int axis) {
return new Comparator>() {
public int compare(Point> o1, Point> o2) {
double d = axis == 0 ? o1.x - o2.x : o1.y - o2.y;
if (d > 0) {
return 1;
} else if (d < 0) {
return -1;
} else {
return 0;
}
}
};
}
// Axis value other value contain x and y, but in such order
// that the value that is used as axis for this point is in axisValue variable.
private double axisValue;
private double otherValue;
private Point smaller, bigger;
private final T value;
private final double x, y;
/**
* New point with the specified coordinates and the value.
*
* @param x
* @param y
* @param value
*/
public Point(double x, double y, T value) {
super();
this.x = x;
this.y = y;
this.axisValue = x;
this.otherValue = y;
this.value = value;
}
/**
*
* @return the value of the point
*/
public T getValue() {
return value;
}
/**
*
* @return the x coordinate of the point
*/
public double getX() {
return x;
}
/**
*
* @return the y coordinate of the point
*/
public double getY() {
return y;
}
private LinkedList findNearest(double queryAxis, double queryOther, LinkedList currentBest) {
// Negative number means this point is on the left to the query point.
double diffAxis = queryAxis - axisValue;
Point closerChild, fartherChild;
if (diffAxis >= 0) {
closerChild = bigger;
fartherChild = smaller;
} else {
closerChild = smaller;
fartherChild = bigger;
}
// First check the closer side
if (closerChild != null) {
currentBest = closerChild.findNearest(queryOther, queryAxis, currentBest);
}
// Now let's see it the other side is still relevant. Since that search
// might have narrowed the circle.
// Calculate distance to axis.
double distanceToHyperplane = diffAxis * diffAxis;
// See if line intersects circle
if (distanceToHyperplane <= currentBest.distance) {
// If it does then this point might be the best one.
double diffOther = queryOther - otherValue;
double d = distanceToHyperplane + diffOther * diffOther;
if (d <= currentBest.distance) {
// Start with the farthest point in the list
// This point is farther than the this point
LinkedList farther = currentBest;
LinkedList newHead = currentBest;
// Scroll down the list to find the last node that is farther than this node
while (farther.tail != null && d <= farther.tail.distance) {
farther = farther.tail;
newHead = currentBest.tail;
}
currentBest.head = this;
currentBest.distance = d;
// Here's a bit of a trickeroo. We've got 2 scenarios:
// - The farthest (first) point in the list is the one being replaced. In that case
// it's really easy, we're done already.
// - In other cases we need assign first point (currentBest) into the chain as tail of
// "farther" then we need to update currentBest as tail of currentBest to keep the
// currentBest as the start of the chain.
//
// The trick both cases can be solved by the same code.
LinkedList tail = farther.tail;
farther.tail = currentBest;
currentBest.tail = tail;
currentBest = newHead;
}
// Search the other side.
if (fartherChild != null) {
currentBest = fartherChild.findNearest(queryOther, queryAxis, currentBest);
}
}
return currentBest;
}
private void findNearest(double queryAxis, double queryOther, NearestPoint currentBest) {
// Negative number means this point is on the left to the query point.
double diffAxis = queryAxis - axisValue;
Point closerChild, fartherChild;
if (diffAxis >= 0) {
closerChild = bigger;
fartherChild = smaller;
} else {
closerChild = smaller;
fartherChild = bigger;
}
// First check the closer side
if (closerChild != null) {
closerChild.findNearest(queryOther, queryAxis, currentBest);
}
// Now let's see it the other side is still relevant. Since that search
// might have narrowed the circle.
// Calculate distance to axis.
double distanceToHyperplane = diffAxis * diffAxis;
// See if line intersects circle
if (distanceToHyperplane <= currentBest.distance) {
// If it does then this point might be the best one.
double diffOther = queryOther - otherValue;
double d = distanceToHyperplane + diffOther * diffOther;
if (d <= currentBest.distance) {
currentBest.p = this;
currentBest.distance = d;
}
// Search the other side.
if (fartherChild != null) {
fartherChild.findNearest(queryOther, queryAxis, currentBest);
}
}
}
private void rotate(int axis) {
if (axis == 1) {
double temp = axisValue;
axisValue = otherValue;
otherValue = temp;
}
}
}
private static class LinkedList implements Iterable> {
private static LinkedList constructChain(int length, double distance) {
LinkedList ret = new LinkedList(distance);
for (int i = 1; i < length; i++) {
LinkedList nextNode = new LinkedList(distance);
nextNode.tail = ret;
ret = nextNode;
}
return ret;
}
private double distance;
private Point head;
private LinkedList tail;
private LinkedList(double distance) {
this.distance = distance;
}
public LinkedList dropEmptyPrefix() {
LinkedList c = LinkedList.this;
while (c != null && c.head == null) {
c = c.tail;
}
return c;
}
public Iterator> iterator() {
return new Iterator>() {
private LinkedList cursor = LinkedList.this;
public boolean hasNext() {
return cursor != null;
}
public Point next() {
Point p = cursor.head;
cursor = cursor.tail;
return p;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public LinkedList reverse() {
LinkedList c = LinkedList.this;
LinkedList prev = null;
while (c != null) {
LinkedList tmp = c;
c = c.tail;
tmp.tail = prev;
prev = tmp;
}
return prev;
}
}
private static class NearestPoint {
private double distance;
private Point p;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy