package com.jwetherell.algorithms.data_structures;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
/**
* A quadtree is a tree data structure in which each internal node has exactly four children. Quadtrees
* are most often used to partition a two dimensional space by recursively subdividing it into four
* quadrants or regions. The regions may be square or rectangular, or may have arbitrary shapes.
*
* http://en.wikipedia.org/wiki/Quadtree
*
* @author Justin Wetherell <phishman3579@gmail.com>
*/
@SuppressWarnings("unchecked")
public abstract class QuadTree<G extends QuadTree.XYPoint> {
/**
* Get the root node.
*
* @return Root QuadNode.
*/
protected abstract QuadNode<G> getRoot();
/**
* Range query of the quadtree.
*/
public abstract Collection<G> queryRange(double x, double y, double width, double height);
/**
* Insert point at X,Y into tree.
*
* @param x X position of point.
* @param y Y position of point.
*/
public abstract boolean insert(double x, double y);
/**
* Remove point at X,Y from tree.
*
* @param x X position of point.
* @param y Y position of point.
*/
public abstract boolean remove(double x, double y);
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return TreePrinter.getString(this);
}
/**
* A PR (Point Region) Quadtree is a four-way search trie. This means that each node has either
* four (internal guide node) or zero (leaf node) children. Keys are only stored in the leaf nodes,
* all internal nodes act as guides towards the keys.
*
* This implementation is a PR QuadTree which uses "Buckets" to prevent stalky trees.
*/
public static class PointRegionQuadTree<P extends QuadTree.XYPoint> extends QuadTree<P> {
private static final XYPoint XY_POINT = new XYPoint();
private static final AxisAlignedBoundingBox RANGE = new AxisAlignedBoundingBox();
private PointRegionQuadNode<P> root = null;
/**
* Create a quadtree who's upper left coordinate is located at x,y and it's bounding box is described
* by the height and width. This uses a default leafCapacity of 4 and a maxTreeHeight of 20.
*
* @param x Upper left X coordinate
* @param y Upper left Y coordinate
* @param width Width of the bounding box containing all points
* @param height Height of the bounding box containing all points
*/
public PointRegionQuadTree(double x, double y, double width, double height) {
this(x,y,width,height,4,20);
}
/**
* Create a quadtree who's upper left coordinate is located at x,y and it's bounding box is described
* by the height and width.
*
* @param x Upper left X coordinate
* @param y Upper left Y coordinate
* @param width Width of the bounding box containing all points
* @param height Height of the bounding box containing all points
* @param leafCapacity Max capacity of leaf nodes. (Note: All data is stored in leaf nodes)
*/
public PointRegionQuadTree(double x, double y, double width, double height, int leafCapacity) {
this(x,y,width,height,leafCapacity,20);
}
/**
* Create a quadtree who's upper left coordinate is located at x,y and it's bounding box is described
* by the height and width.
*
* @param x Upper left X coordinate
* @param y Upper left Y coordinate
* @param width Width of the bounding box containing all points
* @param height Height of the bounding box containing all points
* @param leafCapacity Max capacity of leaf nodes. (Note: All data is stored in leaf nodes)
* @param maxTreeHeight Max height of the quadtree. (Note: If this is defined, the tree will ignore the
* max capacity defined by leafCapacity)
*/
public PointRegionQuadTree(double x, double y, double width, double height, int leafCapacity, int maxTreeHeight) {
XYPoint xyPoint = new XYPoint(x,y);
AxisAlignedBoundingBox aabb = new AxisAlignedBoundingBox(xyPoint,width,height);
PointRegionQuadNode.maxCapacity = leafCapacity;
PointRegionQuadNode.maxHeight = maxTreeHeight;
root = new PointRegionQuadNode<P>(aabb);
}
/**
* {@inheritDoc}
*/
@Override
public QuadTree.QuadNode<P> getRoot() {
return root;
}
/**
* {@inheritDoc}
*/
@Override
public boolean insert(double x, double y) {
XYPoint xyPoint = new XYPoint(x,y);
return root.insert((P)xyPoint);
}
/**
* {@inheritDoc}
*/
@Override
public boolean remove(double x, double y) {
XY_POINT.set(x,y);
return root.remove((P)XY_POINT);
}
/**
* {@inheritDoc}
*/
@Override
public Collection<P> queryRange(double x, double y, double width, double height) {
if (root == null)
return Collections.EMPTY_LIST;
XY_POINT.set(x,y);
RANGE.set(XY_POINT,width,height);
List<P> pointsInRange = new LinkedList<P>();
root.queryRange(RANGE,pointsInRange);
return pointsInRange;
}
protected static class PointRegionQuadNode<XY extends QuadTree.XYPoint> extends QuadNode<XY> {
// max number of children before sub-dividing
protected static int maxCapacity = 0;
// max height of the tree (will over-ride maxCapacity when height==maxHeight)
protected static int maxHeight = 0;
protected List<XY> points = new LinkedList<XY>();
protected int height = 1;
protected PointRegionQuadNode(AxisAlignedBoundingBox aabb) {
super(aabb);
}
/**
* {@inheritDoc}
*
* returns True if inserted.
* returns False if not in bounds of tree OR tree already contains point.
*/
@Override
protected boolean insert(XY p) {
// Ignore objects which do not belong in this quad tree
if (!aabb.containsPoint(p) || (isLeaf() && points.contains(p)))
return false; // object cannot be added
// If there is space in this quad tree, add the object here
if ((height==maxHeight) || (isLeaf() && points.size() < maxCapacity)) {
points.add(p);
return true;
}
// Otherwise, we need to subdivide then add the point to whichever node will accept it
if (isLeaf() && height<maxHeight)
subdivide();
return insertIntoChildren(p);
}
/**
* {@inheritDoc}
*
* This method will merge children into self if it can without overflowing the maxCapacity param.
*/
@Override
protected boolean remove(XY p) {
// If not in this AABB, don't do anything
if (!aabb.containsPoint(p))
return false;
// If in this AABB and in this node
if (points.remove(p))
return true;
// If this node has children
if (!isLeaf()) {
// If in this AABB but in a child branch
boolean removed = removeFromChildren(p);
if (!removed)
return false;
// Try to merge children
merge();
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
protected int size() {
return points.size();
}
private void subdivide() {
double h = aabb.height/2d;
double w = aabb.width/2d;
AxisAlignedBoundingBox aabbNW = new AxisAlignedBoundingBox(aabb,w,h);
northWest = new PointRegionQuadNode<XY>(aabbNW);
((PointRegionQuadNode<XY>)northWest).height = height+1;
XYPoint xyNE = new XYPoint(aabb.x+w,aabb.y);
AxisAlignedBoundingBox aabbNE = new AxisAlignedBoundingBox(xyNE,w,h);
northEast = new PointRegionQuadNode<XY>(aabbNE);
((PointRegionQuadNode<XY>)northEast).height = height+1;
XYPoint xySW = new XYPoint(aabb.x,aabb.y+h);
AxisAlignedBoundingBox aabbSW = new AxisAlignedBoundingBox(xySW,w,h);
southWest = new PointRegionQuadNode<XY>(aabbSW);
((PointRegionQuadNode<XY>)southWest).height = height+1;
XYPoint xySE = new XYPoint(aabb.x+w,aabb.y+h);
AxisAlignedBoundingBox aabbSE = new AxisAlignedBoundingBox(xySE,w,h);
southEast = new PointRegionQuadNode<XY>(aabbSE);
((PointRegionQuadNode<XY>)southEast).height = height+1;
// points live in leaf nodes, so distribute
for (XY p : points)
insertIntoChildren(p);
points.clear();
}
private void merge() {
// If the children aren't leafs, you cannot merge
if (!northWest.isLeaf() || !northEast.isLeaf() || !southWest.isLeaf() || !southEast.isLeaf())
return;
// Children and leafs, see if you can remove point and merge into this node
int nw = northWest.size();
int ne = northEast.size();
int sw = southWest.size();
int se = southEast.size();
int total = nw+ne+sw+se;
// If all the children's point can be merged into this node
if ((size()+total) < maxCapacity) {
this.points.addAll(((PointRegionQuadNode<XY>)northWest).points);
this.points.addAll(((PointRegionQuadNode<XY>)northEast).points);
this.points.addAll(((PointRegionQuadNode<XY>)southWest).points);
this.points.addAll(((PointRegionQuadNode<XY>)southEast).points);
this.northWest = null;
this.northEast = null;
this.southWest = null;
this.southEast = null;
}
}
private boolean insertIntoChildren(XY p) {
// A point can only live in one child.
if (northWest.insert(p)) return true;
if (northEast.insert(p)) return true;
if (southWest.insert(p)) return true;
if (southEast.insert(p)) return true;
return false; // should never happen
}
private boolean removeFromChildren(XY p) {
// A point can only live in one child.
if (northWest.remove(p)) return true;
if (northEast.remove(p)) return true;
if (southWest.remove(p)) return true;
if (southEast.remove(p)) return true;
return false; // should never happen
}
/**
* {@inheritDoc}
*/
@Override
protected void queryRange(AxisAlignedBoundingBox range, List<XY> pointsInRange) {
// Automatically abort if the range does not collide with this quad
if (!aabb.intersectsBox(range))
return;
// If leaf, check objects at this level
if (isLeaf()) {
for (XY xyPoint : points) {
if (range.containsPoint(xyPoint))
pointsInRange.add(xyPoint);
}
return;
}
// Otherwise, add the points from the children
northWest.queryRange(range,pointsInRange);
northEast.queryRange(range,pointsInRange);
southWest.queryRange(range,pointsInRange);
southEast.queryRange(range,pointsInRange);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(super.toString()).append(", ");
builder.append("[");
for (XYPoint p : points) {
builder.append(p).append(", ");
}
builder.append("]");
return builder.toString();
}
}
}
/**
* MX-CIF quadtree is a variant of quadtree data structure which supports area-based query. It is designed for storing a
* set of rectangles (axis-aligned bounded box) in a dynamic environment.
*/
public static class MxCifQuadTree<B extends QuadTree.AxisAlignedBoundingBox> extends QuadTree<B> {
private static final XYPoint XY_POINT = new XYPoint();
private static final AxisAlignedBoundingBox RANGE = new AxisAlignedBoundingBox();
private MxCifQuadNode<B> root = null;
/**
* Create a quadtree who's upper left coordinate is located at x,y and it's bounding box is described
* by the height and width. This uses a default leafCapacity of 4 and a maxTreeHeight of 20.
*
* @param x Upper left X coordinate
* @param y Upper left Y coordinate
* @param width Width of the bounding box containing all points
* @param height Height of the bounding box containing all points
*/
public MxCifQuadTree(double x, double y, double width, double height) {
this(x,y,width,height,0,0);
}
/**
* Create a quadtree who's upper left coordinate is located at x,y and it's bounding box is described
* by the height and width. This uses a default leafCapacity of 4 and a maxTreeHeight of 20.
*
* @param x Upper left X coordinate
* @param y Upper left Y coordinate
* @param width Width of the bounding box containing all points
* @param height Height of the bounding box containing all points
* @param minWidth The tree will stop splitting when leaf node's width <= minWidth
* @param minHeight The tree will stop splitting when leaf node's height <= minHeight
*/
public MxCifQuadTree(double x, double y, double width, double height, double minWidth, double minHeight) {
XYPoint xyPoint = new XYPoint(x,y);
AxisAlignedBoundingBox aabb = new AxisAlignedBoundingBox(xyPoint,width,height);
MxCifQuadNode.minWidth = minWidth;
MxCifQuadNode.minHeight = minHeight;
root = new MxCifQuadNode<B>(aabb);
}
/**
* {@inheritDoc}
*/
@Override
public QuadTree.QuadNode<B> getRoot() {
return root;
}
/**
* {@inheritDoc}
*
* Assumes height and width of 1
*/
@Override
public boolean insert(double x, double y) {
return insert(x,y,1,1);
}
/**
* Insert rectangle whose upper-left point is located at X,Y and has a height and width into tree.
*
* @param x X position of upper-left hand corner.
* @param y Y position of upper-left hand corner.
* @param width Width of the rectangle.
* @param height Height of the rectangle.
*/
public boolean insert(double x, double y, double width, double height) {
XYPoint xyPoint = new XYPoint(x,y);
AxisAlignedBoundingBox range = new AxisAlignedBoundingBox(xyPoint,width,height);
return root.insert((B)range);
}
/**
* {@inheritDoc}
*
* Assumes height and width of 1
*/
@Override
public boolean remove(double x, double y) {
return remove(x,y,1,1);
}
/**
* Remove rectangle whose upper-left point is located at X,Y and has a height and width into tree.
*
* @param x X position of upper-left hand corner.
* @param y Y position of upper-left hand corner.
* @param width Width of the rectangle.
* @param height Height of the rectangle.
*/
public boolean remove(double x, double y, double width, double height) {
XY_POINT.set(x,y);
RANGE.set(XY_POINT,width,height);
return root.remove((B)RANGE);
}
/**
* {@inheritDoc}
*/
@Override
public Collection<B> queryRange(double x, double y, double width, double height) {
if (root == null)
return Collections.EMPTY_LIST;
XY_POINT.set(x,y);
RANGE.set(XY_POINT,width,height);
List<B> geometricObjectsInRange = new LinkedList<B>();
root.queryRange(RANGE,geometricObjectsInRange);
return geometricObjectsInRange;
}
protected static class MxCifQuadNode<AABB extends QuadTree.AxisAlignedBoundingBox> extends QuadNode<AABB> {
protected static double minWidth = 1;
protected static double minHeight = 1;
protected List<AABB> aabbs = new LinkedList<AABB>();
protected MxCifQuadNode(AxisAlignedBoundingBox aabb) {
super(aabb);
}
/**
* {@inheritDoc}
*
* returns True if inserted or already contains.
*/
@Override
protected boolean insert(AABB b) {
// Ignore objects which do not belong in this quad tree
if (!aabb.intersectsBox(b))
return false; // object cannot be added
if (aabbs.contains(b))
return true; // already exists
// Subdivide then add the objects to whichever node will accept it
if (isLeaf())
subdivide(b);
boolean inserted = false;
if (isLeaf()) {
aabbs.add(b);
inserted = true;
} else {
inserted = insertIntoChildren(b);
}
if (!inserted) {
// Couldn't insert into children (it could strattle the bounds of the box)
aabbs.add(b);
return true;
}
return true;
}
/**
* {@inheritDoc}
*
* This method does not merge children.
*/
@Override
protected boolean remove(AABB b) {
// If not in this AABB, don't do anything
if (!aabb.intersectsBox(b))
return false;
// If in this AABB and in this node
if (aabbs.remove(b))
return true;
// If this node has children
if (!isLeaf()) {
// If in this AABB but in a child branch
return removeFromChildren(b);
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
protected int size() {
return aabbs.size();
}
private boolean subdivide(AABB b) {
double w = aabb.width/2d;
double h = aabb.height/2d;
if (w<minWidth || h<minHeight) return false;
AxisAlignedBoundingBox aabbNW = new AxisAlignedBoundingBox(aabb,w,h);
northWest = new MxCifQuadNode<AABB>(aabbNW);
XYPoint xyNE = new XYPoint(aabb.x+w,aabb.y);
AxisAlignedBoundingBox aabbNE = new AxisAlignedBoundingBox(xyNE,w,h);
northEast = new MxCifQuadNode<AABB>(aabbNE);
XYPoint xySW = new XYPoint(aabb.x,aabb.y+h);
AxisAlignedBoundingBox aabbSW = new AxisAlignedBoundingBox(xySW,w,h);
southWest = new MxCifQuadNode<AABB>(aabbSW);
XYPoint xySE = new XYPoint(aabb.x+w,aabb.y+h);
AxisAlignedBoundingBox aabbSE = new AxisAlignedBoundingBox(xySE,w,h);
southEast = new MxCifQuadNode<AABB>(aabbSE);
return insertIntoChildren(b);
}
private boolean insertIntoChildren(AABB b) {
//Try to insert into all children
if (northWest.aabb.insideThis(b) && northWest.insert(b)) return true;
if (northEast.aabb.insideThis(b) && northEast.insert(b)) return true;
if (southWest.aabb.insideThis(b) && southWest.insert(b)) return true;
if (southEast.aabb.insideThis(b) && southEast.insert(b)) return true;
return false;
}
private boolean removeFromChildren(AABB b) {
// A AABB can only live in one child.
if (northWest.remove(b)) return true;
if (northEast.remove(b)) return true;
if (southWest.remove(b)) return true;
if (southEast.remove(b)) return true;
return false; // should never happen
}
/**
* {@inheritDoc}
*/
@Override
protected void queryRange(AxisAlignedBoundingBox range, List<AABB> geometricObjectsInRange) {
// Automatically abort if the range does not collide with this quad
if (!aabb.intersectsBox(range))
return;
// Check objects at this level
for (AABB b : aabbs) {
if (range.intersectsBox(b))
geometricObjectsInRange.add(b);
}
// Otherwise, add the objects from the children
if (!isLeaf()) {
northWest.queryRange(range,geometricObjectsInRange);
northEast.queryRange(range,geometricObjectsInRange);
southWest.queryRange(range,geometricObjectsInRange);
southEast.queryRange(range,geometricObjectsInRange);
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(super.toString()).append(", ");
builder.append("[");
for (AABB p : aabbs)
builder.append(p).append(", ");
builder.append("]");
return builder.toString();
}
}
}
protected static abstract class QuadNode<G extends QuadTree.XYPoint> implements Comparable<QuadNode<G>> {
protected final AxisAlignedBoundingBox aabb;
protected QuadNode<G> northWest = null;
protected QuadNode<G> northEast = null;
protected QuadNode<G> southWest = null;
protected QuadNode<G> southEast = null;
protected QuadNode(AxisAlignedBoundingBox aabb) {
this.aabb = aabb;
}
/**
* Insert object into tree.
*
* @param g Geometric object to insert into tree.
* @return True if successfully inserted.
*/
protected abstract boolean insert(G g);
/**
* Remove object from tree.
*
* @param g Geometric object to remove from tree.
* @return True if successfully removed.
*/
protected abstract boolean remove(G g);
/**
* How many GeometricObjects this node contains.
*
* @return Number of GeometricObjects this node contains.
*/
protected abstract int size();
/**
* Find all objects which appear within a range.
*
* @param range Upper-left and width,height of a axis-aligned bounding box.
* @param geometricObjectsInRange Geometric objects inside the bounding box.
*/
protected abstract void queryRange(AxisAlignedBoundingBox range, List<G> geometricObjectsInRange);
/**
* Is current node a leaf node.
* @return True if node is a leaf node.
*/
protected boolean isLeaf() {
return (northWest==null && northEast==null && southWest==null && southEast==null);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int hash = aabb.hashCode();
hash = hash * 13 + ((northWest!=null)?northWest.hashCode():1);
hash = hash * 17 + ((northEast!=null)?northEast.hashCode():1);
hash = hash * 19 + ((southWest!=null)?southWest.hashCode():1);
hash = hash * 23 + ((southEast!=null)?southEast.hashCode():1);
return hash;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof QuadNode))
return false;
QuadNode<G> qNode = (QuadNode<G>) obj;
if (this.compareTo(qNode) == 0)
return true;
return false;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("rawtypes")
@Override
public int compareTo(QuadNode o) {
return this.aabb.compareTo(o.aabb);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(aabb.toString());
return builder.toString();
}
}
public static class XYPoint implements Comparable<Object> {
protected double x = Float.MIN_VALUE;
protected double y = Float.MIN_VALUE;
public XYPoint() { }
public XYPoint(double x, double y) {
this.x = x;
this.y = y;
}
public void set(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int hash = 1;
hash = hash * 13 + (int)x;
hash = hash * 19 + (int)y;
return hash;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof XYPoint))
return false;
XYPoint xyzPoint = (XYPoint) obj;
return compareTo(xyzPoint) == 0;
}
/**
* {@inheritDoc}
*/
@Override
public int compareTo(Object o) {
if ((o instanceof XYPoint)==false)
throw new RuntimeException("Cannot compare object.");
XYPoint p = (XYPoint) o;
int xComp = X_COMPARATOR.compare(this, p);
if (xComp != 0)
return xComp;
return Y_COMPARATOR.compare(this, p);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("(");
builder.append(x).append(", ");
builder.append(y);
builder.append(")");
return builder.toString();
}
}
public static class AxisAlignedBoundingBox extends XYPoint {
private double height = 0;
private double width = 0;
private double minX = 0;
private double minY = 0;
private double maxX = 0;
private double maxY = 0;
public AxisAlignedBoundingBox() { }
public AxisAlignedBoundingBox(XYPoint upperLeft, double width, double height) {
super(upperLeft.x, upperLeft.y);
this.width = width;
this.height = height;
minX = upperLeft.x;
minY = upperLeft.y;
maxX = upperLeft.x+width;
maxY = upperLeft.y+height;
}
public void set(XYPoint upperLeft, double width, double height) {
set(upperLeft.x, upperLeft.y);
this.width = width;
this.height = height;
minX = upperLeft.x;
minY = upperLeft.y;
maxX = upperLeft.x+width;
maxY = upperLeft.y+height;
}
public double getHeight() {
return height;
}
public double getWidth() {
return width;
}
public boolean containsPoint(XYPoint p) {
if (p.x>=maxX) return false;
if (p.x<minX) return false;
if (p.y>=maxY) return false;
if (p.y<minY) return false;
return true;
}
/**
* Is the inputted AxisAlignedBoundingBox completely inside this AxisAlignedBoundingBox.
*
* @param b AxisAlignedBoundingBox to test.
* @return True if the AxisAlignedBoundingBox is completely inside this AxisAlignedBoundingBox.
*/
public boolean insideThis(AxisAlignedBoundingBox b) {
if (b.minX >= minX && b.maxX <= maxX && b.minY >= minY && b.maxY <= maxY) {
// INSIDE
return true;
}
return false;
}
/**
* Is the inputted AxisAlignedBoundingBox intersecting this AxisAlignedBoundingBox.
*
* @param b AxisAlignedBoundingBox to test.
* @return True if the AxisAlignedBoundingBox is intersecting this AxisAlignedBoundingBox.
*/
public boolean intersectsBox(AxisAlignedBoundingBox b) {
if (insideThis(b) || b.insideThis(this)) {
// INSIDE
return true;
}
// OUTSIDE
if (maxX < b.minX || minX > b.maxX) return false;
if (maxY < b.minY || minY > b.maxY) return false;
// INTERSECTS
return true;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int hash = super.hashCode();
hash = hash * 13 + (int)height;
hash = hash * 19 + (int)width;
return hash;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof AxisAlignedBoundingBox))
return false;
AxisAlignedBoundingBox aabb = (AxisAlignedBoundingBox) obj;
return compareTo(aabb) == 0;
}
/**
* {@inheritDoc}
*/
@Override
public int compareTo(Object o) {
if ((o instanceof AxisAlignedBoundingBox)==false)
throw new RuntimeException("Cannot compare object.");
AxisAlignedBoundingBox a = (AxisAlignedBoundingBox) o;
int p = super.compareTo(a);
if (p!=0) return p;
if (height>a.height) return 1;
if (height<a.height) return -1;
if (width>a.width) return 1;
if (width<a.width) return -1;
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("(");
builder.append(super.toString()).append(", ");
builder.append("height").append("=").append(height).append(", ");
builder.append("width").append("=").append(width);
builder.append(")");
return builder.toString();
}
}
private static final Comparator<XYPoint> X_COMPARATOR = new Comparator<XYPoint>() {
/**
* {@inheritDoc}
*/
@Override
public int compare(XYPoint o1, XYPoint o2) {
if (o1.x < o2.x)
return -1;
if (o1.x > o2.x)
return 1;
return 0;
}
};
private static final Comparator<XYPoint> Y_COMPARATOR = new Comparator<XYPoint>() {
/**
* {@inheritDoc}
*/
@Override
public int compare(XYPoint o1, XYPoint o2) {
if (o1.y < o2.y)
return -1;
if (o1.y > o2.y)
return 1;
return 0;
}
};
protected static class TreePrinter {
public static <T extends XYPoint> String getString(QuadTree<T> tree) {
if (tree.getRoot() == null) return "Tree has no nodes.";
return getString(tree.getRoot(), "", true);
}
private static <T extends XYPoint> String getString(QuadNode<T> node, String prefix, boolean isTail) {
StringBuilder builder = new StringBuilder();
builder.append(prefix + (isTail ? "└── " : "├── ") + " node={" + node.toString() + "}\n");
List<QuadNode<T>> children = null;
if (node.northWest != null || node.northEast != null || node.southWest != null || node.southEast != null) {
children = new ArrayList<QuadNode<T>>(4);
if (node.northWest != null) children.add(node.northWest);
if (node.northEast != null) children.add(node.northEast);
if (node.southWest != null) children.add(node.southWest);
if (node.southEast != null) children.add(node.southEast);
}
if (children != null) {
for (int i = 0; i < children.size() - 1; i++) {
builder.append(getString(children.get(i), prefix + (isTail ? " " : "│ "), false));
}
if (children.size() >= 1) {
builder.append(getString(children.get(children.size() - 1), prefix + (isTail ? " " : "│ "), true));
}
}
return builder.toString();
}
}
}