package org.khelekore.prtree;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/** A Priority R-Tree, a spatial index.
* This tree only supports bulk loading.
*
* <pre>{@code
* PRTree<Rectangle2D> tree =
* new PRTree<Rectangle2D> (new Rectangle2DConverter (), 10);
* Rectangle2D rx = new Rectangle2D.Double (0, 0, 1, 1);
* tree.load (Collections.singletonList (rx));
* for (Rectangle2D r : tree.find (0, 0, 1, 1)) {
* System.out.println ("found a rectangle: " + r);
* }
* }</pre>
*
* @param <T> the data type stored in the PRTree
*/
public class PRTree<T> {
private MBRConverter<T> converter;
private int branchFactor;
private Node<T> root;
private int numLeafs;
private int height;
/** Create a new PRTree using the specified branch factor.
* @param branchFactor the number of child nodes for each internal node.
*/
public PRTree (MBRConverter<T> converter, int branchFactor) {
this.converter = converter;
this.branchFactor = branchFactor;
}
private int estimateSize (int dataSize) {
return (int)(1.0 / (branchFactor - 1) * dataSize);
}
/** Bulk load data into this tree.
*
* Create the leaf nodes that each hold (up to) branchFactor data entries.
* Then use the leaf nodes as data until we can fit all nodes into
* the root node.
*
* @param data the collection of data to store in the tree.
* @throws IllegalStateException if the tree is already loaded
*/
public void load (List<? extends T> data) {
if (root != null)
throw new IllegalStateException ("Tree is already loaded");
numLeafs = data.size ();
XMinComparator<T> xMinSorter = new XMinComparator<T> (converter);
YMinComparator<T> yMinSorter = new YMinComparator<T> (converter);
XMaxComparator<T> xMaxSorter = new XMaxComparator<T> (converter);
YMaxComparator<T> yMaxSorter = new YMaxComparator<T> (converter);
List<LeafNode<T>> leafNodes =
new ArrayList<LeafNode<T>> (estimateSize (numLeafs));
LeafBuilder lb = new LeafBuilder (branchFactor);
lb.buildLeafs (data, leafNodes, xMinSorter, yMinSorter,
xMaxSorter, yMaxSorter, new LeafNodeFactory ());
height = 1;
if (leafNodes.size () < branchFactor) {
setRoot (leafNodes);
} else {
XMinNodeComparator<T> xMins = new XMinNodeComparator<T> (converter);
YMinNodeComparator<T> yMins = new YMinNodeComparator<T> (converter);
XMaxNodeComparator<T> xMaxs = new XMaxNodeComparator<T> (converter);
YMaxNodeComparator<T> yMaxs = new YMaxNodeComparator<T> (converter);
List<? extends Node<T>> nodes = leafNodes;
do {
height++;
int es = estimateSize (nodes.size ());
List<InternalNode<T>> internalNodes =
new ArrayList<InternalNode<T>> (es);
lb.buildLeafs (nodes, internalNodes, xMins, yMins,
xMaxs, yMaxs, new InternalNodeFactory ());
nodes = internalNodes;
} while (nodes.size () > branchFactor);
setRoot (nodes);
}
}
/** Get a minimum bounding rectangle of the data stored in this tree.
*/
public MBR getMBR () {
return root.getMBR (converter);
}
/** Get the number of data leafs in this tree.
*/
public int getNumberOfLeaves () {
return numLeafs;
}
/** Get the height of this tree.
*/
public int getHeight () {
return height;
}
private <N extends Node<T>> void setRoot (List<N> nodes) {
if (nodes.size () == 0)
root = new InternalNode<T> (new Object[0]);
else if (nodes.size () == 1) {
root = nodes.get (0);
} else {
height++;
root = new InternalNode<T> (nodes.toArray ());
}
}
private class LeafNodeFactory
implements NodeFactory<LeafNode<T>> {
public LeafNode<T> create (Object[] data) {
return new LeafNode<T> (data);
}
}
private class InternalNodeFactory
implements NodeFactory<InternalNode<T>> {
public InternalNode<T> create (Object[] data) {
return new InternalNode<T> (data);
}
}
private void validateRect (final double xmin, final double ymin,
final double xmax, final double ymax) {
if (xmax < xmin)
throw new IllegalArgumentException ("xmax: " + xmax +
" < xmin: " + xmin);
if (ymax < ymin)
throw new IllegalArgumentException ("ymax: " + ymax +
" < ymin: " + ymin);
}
/** Finds all objects that intersect the given rectangle and stores
* the found node in the given list.
* @param resultNodes the list that will be filled with the result
*/
public void find (double xmin, double ymin, double xmax, double ymax,
List<T> resultNodes) {
MBR mbr = new SimpleMBR (xmin, ymin, xmax, ymax);
find (mbr, resultNodes);
}
/** Finds all objects that intersect the given rectangle and stores
* the found node in the given list.
* @param resultNodes the list that will be filled with the result
*/
public void find (MBR query, List<T> resultNodes) {
validateRect (query.getMinX (), query.getMinY (),
query.getMaxX (), query.getMaxY ());
root.find (query, converter, resultNodes);
}
/** Find all objects that intersect the given rectangle.
* @throws IllegalArgumentException if xmin > xmax or ymin > ymax
*/
public Iterable<T> find (final MBR query) {
validateRect (query.getMinX (), query.getMinY (),
query.getMaxX (), query.getMaxY ());
return new Iterable<T> () {
public Iterator<T> iterator () {
return new Finder (query);
}
};
}
/** Find all objects that intersect the given rectangle.
* @throws IllegalArgumentException if xmin > xmax or ymin > ymax
*/
public Iterable<T> find (double xmin, double ymin,
double xmax, double ymax) {
MBR mbr = new SimpleMBR (xmin, ymin, xmax, ymax);
return find (mbr);
}
private class Finder implements Iterator<T> {
private MBR mbr;
private List<T> ts = new ArrayList<T> ();
private List<Node<T>> toVisit = new ArrayList<Node<T>> ();
private T next;
private int visitedNodes = 0;
private int dataNodesVisited = 0;
public Finder (MBR mbr) {
this.mbr = mbr;
toVisit.add (root);
findNext ();
}
public boolean hasNext () {
return next != null;
}
public T next () {
T toReturn = next;
findNext ();
return toReturn;
}
private void findNext () {
while (ts.isEmpty () && !toVisit.isEmpty ()) {
Node<T> n = toVisit.remove (toVisit.size () - 1);
visitedNodes++;
n.expand (mbr, converter, ts, toVisit);
}
if (ts.isEmpty ()) {
next = null;
} else {
next = ts.remove (ts.size () - 1);
dataNodesVisited++;
}
}
public void remove () {
throw new UnsupportedOperationException ("Not implemented");
}
}
}