package com.github.davidmoten.rtree;
import static com.github.davidmoten.guavamini.Optional.absent;
import static com.github.davidmoten.guavamini.Optional.of;
import static com.github.davidmoten.rtree.geometry.Geometries.rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.github.davidmoten.guavamini.Lists;
import com.github.davidmoten.guavamini.Optional;
import com.github.davidmoten.guavamini.annotations.VisibleForTesting;
import com.github.davidmoten.rtree.geometry.Circle;
import com.github.davidmoten.rtree.geometry.HasGeometry;
import com.github.davidmoten.rtree.geometry.Geometry;
import com.github.davidmoten.rtree.geometry.Intersects;
import com.github.davidmoten.rtree.geometry.Line;
import com.github.davidmoten.rtree.geometry.Point;
import com.github.davidmoten.rtree.geometry.Rectangle;
import com.github.davidmoten.rtree.internal.Comparators;
import com.github.davidmoten.rtree.internal.NodeAndEntries;
import com.github.davidmoten.rtree.internal.operators.OperatorBoundedPriorityQueue;
import rx.Observable;
import rx.functions.Func1;
import rx.functions.Func2;
/**
* Immutable in-memory 2D R-Tree with configurable splitter heuristic.
*
* @param <T>
* the entry value type
* @param <S>
* the entry geometry type
*/
public final class RTree<T, S extends Geometry> {
private final Optional<? extends Node<T, S>> root;
private final Context<T, S> context;
/**
* Benchmarks show that this is a good choice for up to O(10,000) entries
* when using Quadratic splitter (Guttman).
*/
public static final int MAX_CHILDREN_DEFAULT_GUTTMAN = 4;
/**
* Benchmarks show that this is the sweet spot for up to O(10,000) entries
* when using R*-tree heuristics.
*/
public static final int MAX_CHILDREN_DEFAULT_STAR = 4;
/**
* Current size in Entries of the RTree.
*/
private final int size;
/**
* Constructor.
*
* @param root
* the root node of the tree if present
* @param context
* options for the R-tree
*/
private RTree(Optional<? extends Node<T, S>> root, int size, Context<T, S> context) {
this.root = root;
this.size = size;
this.context = context;
}
private RTree() {
this(Optional.<Node<T, S>> absent(), 0, null);
}
/**
* Constructor.
*
* @param root
* the root node of the R-tree
* @param context
* options for the R-tree
*/
private RTree(Node<T, S> root, int size, Context<T, S> context) {
this(of(root), size, context);
}
static <T, S extends Geometry> RTree<T, S> create(Optional<? extends Node<T, S>> root, int size,
Context<T, S> context) {
return new RTree<T, S>(root, size, context);
}
/**
* Returns a new Builder instance for {@link RTree}. Defaults to
* maxChildren=128, minChildren=64, splitter=QuadraticSplitter.
*
* @param <T>
* the value type of the entries in the tree
* @param <S>
* the geometry type of the entries in the tree
* @return a new RTree instance
*/
public static <T, S extends Geometry> RTree<T, S> create() {
return new Builder().create();
}
/**
* Construct an Rtree through STR bulk loading. Default to
* maxChildren=128, minChildren=64 and fill nodes by a factor of 0.7
*
* @param <T>
* the value type of the entries in the tree
* @param <S>
* the geometry type of the entries in the tree
* @return a new RTree instance
*/
public static <T, S extends Geometry> RTree<T, S> create(List<Entry<T, S>> entries) {
return new Builder().create(entries);
}
/**
* The tree is scanned for depth and the depth returned. This involves
* recursing down to the leaf level of the tree to get the current depth.
* Should be <code>log(n)</code> in complexity.
*
* @return depth of the R-tree
*/
public int calculateDepth() {
return calculateDepth(root);
}
private static <T, S extends Geometry> int calculateDepth(Optional<? extends Node<T, S>> root) {
if (!root.isPresent())
return 0;
else
return calculateDepth(root.get(), 0);
}
private static <T, S extends Geometry> int calculateDepth(Node<T, S> node, int depth) {
if (node instanceof Leaf)
return depth + 1;
else
return calculateDepth(((NonLeaf<T, S>) node).child(0), depth + 1);
}
/**
* When the number of children in an R-tree node drops below this number the
* node is deleted and the children are added on to the R-tree again.
*
* @param minChildren
* less than this number of children in a node triggers a node
* deletion and redistribution of its members
* @return builder
*/
public static Builder minChildren(int minChildren) {
return new Builder().minChildren(minChildren);
}
/**
* Sets the max number of children in an R-tree node.
*
* @param maxChildren
* max number of children in an R-tree node
* @return builder
*/
public static Builder maxChildren(int maxChildren) {
return new Builder().maxChildren(maxChildren);
}
/**
* Sets the {@link Splitter} to use when maxChildren is reached.
*
* @param splitter
* the splitter algorithm to use
* @return builder
*/
public static Builder splitter(Splitter splitter) {
return new Builder().splitter(splitter);
}
/**
* Sets the node {@link Selector} which decides which branches to follow
* when inserting or searching.
*
* @param selector
* determines which branches to follow when inserting or
* searching
* @return builder
*/
public static Builder selector(Selector selector) {
return new Builder().selector(selector);
}
/**
* Sets the splitter to {@link SplitterRStar} and selector to
* {@link SelectorRStar} and defaults to minChildren=10.
*
* @return builder
*/
public static Builder star() {
return new Builder().star();
}
/**
* RTree Builder.
*/
public static class Builder {
/**
* According to
* http://dbs.mathematik.uni-marburg.de/publications/myPapers
* /1990/BKSS90.pdf (R*-tree paper), best filling ratio is 0.4 for both
* quadratic split and R*-tree split.
*/
private static final double DEFAULT_FILLING_FACTOR = 0.4;
private static final double DEFAULT_LOADING_FACTOR = 0.7;
private Optional<Integer> maxChildren = absent();
private Optional<Integer> minChildren = absent();
private Splitter splitter = new SplitterQuadratic();
private Selector selector = new SelectorMinimalAreaIncrease();
private double loadingFactor;
private boolean star = false;
private Factory<Object, Geometry> factory = Factories.defaultFactory();
private Builder() {
loadingFactor = DEFAULT_LOADING_FACTOR;
}
/**
* The factor is used as the fill ratio during bulk loading.
*/
public Builder loadingFactor(double factor) {
this.loadingFactor = factor;
return this;
}
/**
* When the number of children in an R-tree node drops below this number
* the node is deleted and the children are added on to the R-tree
* again.
*
* @param minChildren
* less than this number of children in a node triggers a
* redistribution of its children.
* @return builder
*/
public Builder minChildren(int minChildren) {
this.minChildren = of(minChildren);
return this;
}
/**
* Sets the max number of children in an R-tree node.
*
* @param maxChildren
* max number of children in R-tree node.
* @return builder
*/
public Builder maxChildren(int maxChildren) {
this.maxChildren = of(maxChildren);
return this;
}
/**
* Sets the {@link Splitter} to use when maxChildren is reached.
*
* @param splitter
* node splitting method to use
* @return builder
*/
public Builder splitter(Splitter splitter) {
this.splitter = splitter;
return this;
}
/**
* Sets the node {@link Selector} which decides which branches to follow
* when inserting or searching.
*
* @param selector
* selects the branch to follow when inserting or searching
* @return builder
*/
public Builder selector(Selector selector) {
this.selector = selector;
return this;
}
/**
* Sets the splitter to {@link SplitterRStar} and selector to
* {@link SelectorRStar} and defaults to minChildren=10.
*
* @return builder
*/
public Builder star() {
selector = new SelectorRStar();
splitter = new SplitterRStar();
star = true;
return this;
}
@SuppressWarnings("unchecked")
public Builder factory(Factory<?, ? extends Geometry> factory) {
// TODO could change the signature of Builder to have types to
// support this method but would be breaking change for existing
// clients
this.factory = (Factory<Object, Geometry>) factory;
return this;
}
/**
* Builds the {@link RTree}.
*
* @param <T>
* value type
* @param <S>
* geometry type
* @return RTree
*/
@SuppressWarnings("unchecked")
public <T, S extends Geometry> RTree<T, S> create() {
setDefaultCapacity();
return new RTree<T, S>(Optional.<Node<T, S>> absent(), 0,
new Context<T, S>(minChildren.get(), maxChildren.get(), selector, splitter,
(Factory<T, S>) factory));
}
/**
* Create an RTree by bulk loading, using the STR method.
* STR: a simple and efficient algorithm for R-tree packing
* http://ieeexplore.ieee.org/abstract/document/582015/
* <p>
* Note: this method mutates the input entries, the internal order of the List may be changed.
* </p>
*
*/
@SuppressWarnings("unchecked")
public <T, S extends Geometry> RTree<T, S> create(List<Entry<T, S>> entries) {
setDefaultCapacity();
Context<T, S> context = new Context<T, S>(minChildren.get(), maxChildren.get(),
selector, splitter, (Factory<T, S>) factory);
return packingSTR(entries, true, entries.size(), context);
}
private void setDefaultCapacity() {
if (!maxChildren.isPresent())
if (star)
maxChildren = of(MAX_CHILDREN_DEFAULT_STAR);
else
maxChildren = of(MAX_CHILDREN_DEFAULT_GUTTMAN);
if (!minChildren.isPresent())
minChildren = of((int) Math.round(maxChildren.get() * DEFAULT_FILLING_FACTOR));
}
@SuppressWarnings("unchecked")
private <T, S extends Geometry> RTree<T, S> packingSTR(List<? extends HasGeometry> objects,
boolean isLeaf, int size,
Context<T, S> context) {
int capacity = (int) Math.round(maxChildren.get() * loadingFactor);
int nodeCount = (int) Math.ceil(1.0 * objects.size() / capacity);
if (nodeCount == 0) {
return create();
} else if (nodeCount == 1) {
Node<T, S> root;
if (isLeaf) {
root = context.factory().createLeaf((List<Entry<T, S>>) objects, context);
} else {
root = context.factory().createNonLeaf((List<Node<T, S>>) objects, context);
}
return new RTree<T, S>(of(root), size, context);
}
int nodePerSlice = (int) Math.ceil(Math.sqrt(nodeCount));
int sliceCapacity = nodePerSlice * capacity;
int sliceCount = (int) Math.ceil(1.0 * objects.size() / sliceCapacity);
Collections.sort(objects, new MidComparator((short)0));
List<Node<T, S>> nodes = new ArrayList<Node<T, S>>(nodeCount);
for (int s = 0; s < sliceCount; s++) {
List slice = objects.subList(s * sliceCapacity, Math.min((s + 1) * sliceCapacity, objects.size()));
Collections.sort(slice, new MidComparator((short)1));
for (int i = 0; i < slice.size(); i += capacity) {
if (isLeaf) {
List<Entry<T, S>> entries =
slice.subList(i, Math.min(slice.size(), i + capacity));
Node<T, S> leaf = context.factory().createLeaf(entries, context);
nodes.add(leaf);
} else {
List<Node<T, S>> children =
slice.subList(i, Math.min(slice.size(), i + capacity));
Node<T, S> nonleaf = context.factory().createNonLeaf(children, context);
nodes.add(nonleaf);
}
}
}
return packingSTR(nodes, false, size, context);
}
private static final class MidComparator implements Comparator<HasGeometry> {
private final short dimension; // leave space for multiple dimensions, 0 for x, 1 for y, ...
public MidComparator(short dim) {
dimension = dim;
}
@Override
public int compare(HasGeometry o1, HasGeometry o2) {
return Float.compare(mid(o1), mid(o2));
}
private float mid(HasGeometry o) {
Rectangle mbr = o.geometry().mbr();
if (dimension == 0) return (mbr.x1() + mbr.x2()) / 2;
else return (mbr.y1() + mbr.y2()) / 2;
}
}
}
/**
* Returns an immutable copy of the RTree with the addition of given entry.
*
* @param entry
* item to add to the R-tree.
* @return a new immutable R-tree including the new entry
*/
@SuppressWarnings("unchecked")
public RTree<T, S> add(Entry<? extends T, ? extends S> entry) {
if (root.isPresent()) {
List<Node<T, S>> nodes = root.get().add(entry);
Node<T, S> node;
if (nodes.size() == 1)
node = nodes.get(0);
else {
node = context.factory().createNonLeaf(nodes, context);
}
return new RTree<T, S>(node, size + 1, context);
} else {
Leaf<T, S> node = context.factory().createLeaf(Lists.newArrayList((Entry<T, S>) entry),
context);
return new RTree<T, S>(node, size + 1, context);
}
}
/**
* Returns an immutable copy of the RTree with the addition of an entry
* comprised of the given value and Geometry.
*
* @param value
* the value of the {@link Entry} to be added
* @param geometry
* the geometry of the {@link Entry} to be added
* @return a new immutable R-tree including the new entry
*/
public RTree<T, S> add(T value, S geometry) {
return add(context.factory().createEntry(value, geometry));
}
/**
* Returns an immutable RTree with the current entries and the additional
* entries supplied as a parameter.
*
* @param entries
* entries to add
* @return R-tree with entries added
*/
public RTree<T, S> add(Iterable<Entry<T, S>> entries) {
RTree<T, S> tree = this;
for (Entry<T, S> entry : entries)
tree = tree.add(entry);
return tree;
}
/**
* Returns the Observable sequence of trees created by progressively adding
* entries.
*
* @param entries
* the entries to add
* @return a sequence of trees
*/
public Observable<RTree<T, S>> add(Observable<Entry<T, S>> entries) {
return entries.scan(this, new Func2<RTree<T, S>, Entry<T, S>, RTree<T, S>>() {
@Override
public RTree<T, S> call(RTree<T, S> tree, Entry<T, S> entry) {
return tree.add(entry);
}
});
}
/**
* Returns the Observable sequence of trees created by progressively
* deleting entries.
*
* @param entries
* the entries to add
* @param all
* if true delete all matching otherwise just first matching
* @return a sequence of trees
*/
public Observable<RTree<T, S>> delete(Observable<Entry<T, S>> entries, final boolean all) {
return entries.scan(this, new Func2<RTree<T, S>, Entry<T, S>, RTree<T, S>>() {
@Override
public RTree<T, S> call(RTree<T, S> tree, Entry<T, S> entry) {
return tree.delete(entry, all);
}
});
}
/**
* Returns a new R-tree with the given entries deleted. If <code>all</code>
* is false deletes only one if exists. If <code>all</code> is true deletes
* all matching entries.
*
* @param entries
* entries to delete
* @param all
* if false deletes one if exists else deletes all
* @return R-tree with entries deleted
*/
public RTree<T, S> delete(Iterable<Entry<T, S>> entries, boolean all) {
RTree<T, S> tree = this;
for (Entry<T, S> entry : entries)
tree = tree.delete(entry, all);
return tree;
}
/**
* Returns a new R-tree with the given entries deleted but only one matching
* occurence of each entry is deleted.
*
* @param entries
* entries to delete
* @return R-tree with entries deleted up to one matching occurence per
* entry
*/
public RTree<T, S> delete(Iterable<Entry<T, S>> entries) {
RTree<T, S> tree = this;
for (Entry<T, S> entry : entries)
tree = tree.delete(entry);
return tree;
}
/**
* If <code>all</code> is false deletes one entry matching the given value
* and Geometry. If <code>all</code> is true deletes all entries matching
* the given value and geometry. This method has no effect if the entry is
* not present. The entry must match on both value and geometry to be
* deleted.
*
* @param value
* the value of the {@link Entry} to be deleted
* @param geometry
* the geometry of the {@link Entry} to be deleted
* @param all
* if false deletes one if exists else deletes all
* @return a new immutable R-tree without one or many instances of the
* specified entry if it exists otherwise returns the original RTree
* object
*/
public RTree<T, S> delete(T value, S geometry, boolean all) {
return delete(context.factory().createEntry(value, geometry), all);
}
/**
* Deletes maximum one entry matching the given value and geometry. This
* method has no effect if the entry is not present. The entry must match on
* both value and geometry to be deleted.
*
* @param value
* the value to be matched for deletion
* @param geometry
* the geometry to be matched for deletion
* @return an immutable RTree without one entry (if found) matching the
* given value and geometry
*/
public RTree<T, S> delete(T value, S geometry) {
return delete(context.factory().createEntry(value, geometry), false);
}
/**
* Deletes one or all matching entries depending on the value of
* <code>all</code>. If multiple copies of the entry are in the R-tree only
* one will be deleted if all is false otherwise all matching entries will
* be deleted. The entry must match on both value and geometry to be
* deleted.
*
* @param entry
* the {@link Entry} to be deleted
* @param all
* if true deletes all matches otherwise deletes first found
* @return a new immutable R-tree without one instance of the specified
* entry
*/
public RTree<T, S> delete(Entry<? extends T, ? extends S> entry, boolean all) {
if (root.isPresent()) {
NodeAndEntries<T, S> nodeAndEntries = root.get().delete(entry, all);
if (nodeAndEntries.node().isPresent() && nodeAndEntries.node().get() == root.get())
return this;
else
return new RTree<T, S>(nodeAndEntries.node(),
size - nodeAndEntries.countDeleted() - nodeAndEntries.entriesToAdd().size(),
context).add(nodeAndEntries.entriesToAdd());
} else
return this;
}
/**
* Deletes one entry if it exists, returning an immutable copy of the RTree
* without that entry. If multiple copies of the entry are in the R-tree
* only one will be deleted. The entry must match on both value and geometry
* to be deleted.
*
* @param entry
* the {@link Entry} to be deleted
* @return a new immutable R-tree without one instance of the specified
* entry
*/
public RTree<T, S> delete(Entry<? extends T, ? extends S> entry) {
return delete(entry, false);
}
/**
* <p>
* Returns an Observable sequence of {@link Entry} that satisfy the given
* condition. Note that this method is well-behaved only if:
*
*
* <p>
* {@code condition(g)} is true for {@link Geometry} g implies
* {@code condition(r)} is true for the minimum bounding rectangles of the
* ancestor nodes.
*
* <p>
* {@code distance(g) < D} is an example of such a condition.
*
*
* @param condition
* return Entries whose geometry satisfies the given condition
* @return sequence of matching entries
*/
@VisibleForTesting
Observable<Entry<T, S>> search(Func1<? super Geometry, Boolean> condition) {
if (root.isPresent())
return Observable.create(new OnSubscribeSearch<T, S>(root.get(), condition));
else
return Observable.empty();
}
/**
* Returns a predicate function that indicates if {@link Geometry}
* intersects with a given rectangle.
*
* @param r
* the rectangle to check intersection with
* @return whether the geometry and the rectangle intersect
*/
public static Func1<Geometry, Boolean> intersects(final Rectangle r) {
return new Func1<Geometry, Boolean>() {
@Override
public Boolean call(Geometry g) {
return g.intersects(r);
}
};
}
/**
* Returns the always true predicate. See {@link RTree#entries()} for
* example use.
*/
private static final Func1<Geometry, Boolean> ALWAYS_TRUE = new Func1<Geometry, Boolean>() {
@Override
public Boolean call(Geometry rectangle) {
return true;
}
};
/**
* Returns an {@link Observable} sequence of all {@link Entry}s in the
* R-tree whose minimum bounding rectangle intersects with the given
* rectangle.
*
* @param r
* rectangle to check intersection with the entry mbr
* @return entries that intersect with the rectangle r
*/
public Observable<Entry<T, S>> search(final Rectangle r) {
return search(intersects(r));
}
/**
* Returns an {@link Observable} sequence of all {@link Entry}s in the
* R-tree whose minimum bounding rectangle intersects with the given point.
*
* @param p
* point to check intersection with the entry mbr
* @return entries that intersect with the point p
*/
public Observable<Entry<T, S>> search(final Point p) {
return search(p.mbr());
}
public Observable<Entry<T, S>> search(Circle circle) {
return search(circle, Intersects.geometryIntersectsCircle);
}
public Observable<Entry<T, S>> search(Line line) {
return search(line, Intersects.geometryIntersectsLine);
}
/**
* Returns an {@link Observable} sequence of all {@link Entry}s in the
* R-tree whose minimum bounding rectangles are strictly less than
* maxDistance from the given rectangle.
*
* @param r
* rectangle to measure distance from
* @param maxDistance
* entries returned must be within this distance from rectangle r
* @return the sequence of matching entries
*/
public Observable<Entry<T, S>> search(final Rectangle r, final double maxDistance) {
return search(new Func1<Geometry, Boolean>() {
@Override
public Boolean call(Geometry g) {
return g.distance(r) < maxDistance;
}
});
}
/**
* Returns the intersections with the the given (arbitrary) geometry using
* an intersection function to filter the search results returned from a
* search of the mbr of <code>g</code>.
*
* @param <R>
* type of geometry being searched for intersection with
* @param g
* geometry being searched for intersection with
* @param intersects
* function to determine if the two geometries intersect
* @return a sequence of entries that intersect with g
*/
public <R extends Geometry> Observable<Entry<T, S>> search(final R g,
final Func2<? super S, ? super R, Boolean> intersects) {
return search(g.mbr()).filter(new Func1<Entry<T, S>, Boolean>() {
@Override
public Boolean call(Entry<T, S> entry) {
return intersects.call(entry.geometry(), g);
}
});
}
/**
* Returns all entries strictly less than <code>maxDistance</code> from the
* given geometry. Because the geometry may be of an arbitrary type it is
* necessary to also pass a distance function.
*
* @param <R>
* type of the geometry being searched for
* @param g
* geometry to search for entries within maxDistance of
* @param maxDistance
* strict max distance that entries must be from g
* @param distance
* function to calculate the distance between geometries of type
* S and R.
* @return entries strictly less than maxDistance from g
*/
public <R extends Geometry> Observable<Entry<T, S>> search(final R g, final double maxDistance,
final Func2<? super S, ? super R, Double> distance) {
return search(new Func1<Geometry, Boolean>() {
@Override
public Boolean call(Geometry entry) {
// just use the mbr initially
return entry.distance(g.mbr()) < maxDistance;
}
})
// refine with distance function
.filter(new Func1<Entry<T, S>, Boolean>() {
@Override
public Boolean call(Entry<T, S> entry) {
return distance.call(entry.geometry(), g) < maxDistance;
}
});
}
/**
* Returns an {@link Observable} sequence of all {@link Entry}s in the
* R-tree whose minimum bounding rectangles are within maxDistance from the
* given point.
*
* @param p
* point to measure distance from
* @param maxDistance
* entries returned must be within this distance from point p
* @return the sequence of matching entries
*/
public Observable<Entry<T, S>> search(final Point p, final double maxDistance) {
return search(p.mbr(), maxDistance);
}
/**
* Returns the nearest k entries (k=maxCount) to the given rectangle where
* the entries are strictly less than a given maximum distance from the
* rectangle.
*
* @param r
* rectangle
* @param maxDistance
* max distance of returned entries from the rectangle
* @param maxCount
* max number of entries to return
* @return nearest entries to maxCount, in ascending order of distance
*/
public Observable<Entry<T, S>> nearest(final Rectangle r, final double maxDistance,
int maxCount) {
return search(r, maxDistance).lift(new OperatorBoundedPriorityQueue<Entry<T, S>>(maxCount,
Comparators.<T, S> ascendingDistance(r)));
}
/**
* Returns the nearest k entries (k=maxCount) to the given point where the
* entries are strictly less than a given maximum distance from the point.
*
* @param p
* point
* @param maxDistance
* max distance of returned entries from the point
* @param maxCount
* max number of entries to return
* @return nearest entries to maxCount, in ascending order of distance
*/
public Observable<Entry<T, S>> nearest(final Point p, final double maxDistance, int maxCount) {
return nearest(p.mbr(), maxDistance, maxCount);
}
/**
* Returns all entries in the tree as an {@link Observable} sequence.
*
* @return all entries in the R-tree
*/
public Observable<Entry<T, S>> entries() {
return search(ALWAYS_TRUE);
}
/**
* Returns a {@link Visualizer} for an image of given width and height and
* restricted to the given view of the coordinates. The points in the view
* are scaled to match the aspect ratio defined by the width and height.
*
* @param width
* of the image in pixels
* @param height
* of the image in pixels
* @param view
* using the coordinate system of the entries
* @return visualizer
*/
@SuppressWarnings("unchecked")
public Visualizer visualize(int width, int height, Rectangle view) {
return new Visualizer((RTree<?, Geometry>) this, width, height, view);
}
/**
* Returns a {@link Visualizer} for an image of given width and height and
* restricted to the the smallest view that fully contains the coordinates.
* The points in the view are scaled to match the aspect ratio defined by
* the width and height.
*
* @param width
* of the image in pixels
* @param height
* of the image in pixels
* @return visualizer
*/
public Visualizer visualize(int width, int height) {
return visualize(width, height, calculateMaxView(this));
}
private Rectangle calculateMaxView(RTree<T, S> tree) {
return tree.entries().reduce(Optional.<Rectangle> absent(),
new Func2<Optional<Rectangle>, Entry<T, S>, Optional<Rectangle>>() {
@Override
public Optional<Rectangle> call(Optional<Rectangle> r, Entry<T, S> entry) {
if (r.isPresent())
return of(r.get().add(entry.geometry().mbr()));
else
return of(entry.geometry().mbr());
}
}).toBlocking().single().or(rectangle(0, 0, 0, 0));
}
public Optional<? extends Node<T, S>> root() {
return root;
}
/**
* If the RTree has no entries returns {@link Optional#absent} otherwise
* returns the minimum bounding rectangle of all entries in the RTree.
*
* @return minimum bounding rectangle of all entries in RTree
*/
public Optional<Rectangle> mbr() {
if (!root.isPresent())
return absent();
else
return of(root.get().geometry().mbr());
}
/**
* Returns true if and only if the R-tree is empty of entries.
*
* @return is R-tree empty
*/
public boolean isEmpty() {
return size == 0;
}
/**
* Returns the number of entries in the RTree.
*
* @return the number of entries
*/
public int size() {
return size;
}
/**
* Returns a {@link Context} containing the configuration of the RTree at
* the time of instantiation.
*
* @return the configuration of the RTree prior to instantiation
*/
public Context<T, S> context() {
return context;
}
/**
* Returns a human readable form of the RTree. Here's an example:
*
* <pre>
* mbr=Rectangle [x1=10.0, y1=4.0, x2=62.0, y2=85.0]
* mbr=Rectangle [x1=28.0, y1=4.0, x2=34.0, y2=85.0]
* entry=Entry [value=2, geometry=Point [x=29.0, y=4.0]]
* entry=Entry [value=1, geometry=Point [x=28.0, y=19.0]]
* entry=Entry [value=4, geometry=Point [x=34.0, y=85.0]]
* mbr=Rectangle [x1=10.0, y1=45.0, x2=62.0, y2=63.0]
* entry=Entry [value=5, geometry=Point [x=62.0, y=45.0]]
* entry=Entry [value=3, geometry=Point [x=10.0, y=63.0]]
* </pre>
*
* @return a string representation of the RTree
*/
public String asString() {
if (!root.isPresent())
return "";
else
return asString(root.get(), "");
}
private final static String marginIncrement = " ";
private String asString(Node<T, S> node, String margin) {
StringBuilder s = new StringBuilder();
s.append(margin);
s.append("mbr=");
s.append(node.geometry());
s.append('\n');
if (node instanceof NonLeaf) {
NonLeaf<T, S> n = (NonLeaf<T, S>) node;
for (int i = 0; i < n.count(); i++) {
Node<T, S> child = n.child(i);
s.append(asString(child, margin + marginIncrement));
}
} else {
Leaf<T, S> leaf = (Leaf<T, S>) node;
for (Entry<T, S> entry : leaf.entries()) {
s.append(margin);
s.append(marginIncrement);
s.append("entry=");
s.append(entry);
s.append('\n');
}
}
return s.toString();
}
}