/* XXL: The eXtensible and fleXible Library for data processing
Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger
Head of the Database Research Group
Department of Mathematics and Computer Science
University of Marburg
Germany
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; If not, see <http://www.gnu.org/licenses/>.
http://code.google.com/p/xxl/
*/
package xxl.core.indexStructures;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import xxl.core.collections.ReversedList;
import xxl.core.collections.queues.Queue;
import xxl.core.collections.queues.Queues;
import xxl.core.comparators.ComparableComparator;
import xxl.core.comparators.FeatureComparator;
import xxl.core.cursors.Cursors;
import xxl.core.cursors.filters.Filter;
import xxl.core.cursors.identities.TeeCursor;
import xxl.core.cursors.mappers.Mapper;
import xxl.core.cursors.sources.Enumerator;
import xxl.core.functions.AbstractFunction;
import xxl.core.functions.Function;
import xxl.core.io.converters.ConvertableConverter;
import xxl.core.io.converters.Converter;
import xxl.core.predicates.AbstractPredicate;
import xxl.core.predicates.Predicate;
import xxl.core.spatial.points.DoublePoint;
import xxl.core.spatial.rectangles.DoublePointRectangle;
import xxl.core.spatial.rectangles.Rectangle;
import xxl.core.spatial.rectangles.Rectangles;
/** An <tt>ORTree</tt> for objects with bounding rectangles as regions.
* This implementation of a member of the R-Tree family uses the
* split-strategy of the R*-Tree.
*
* For a detailed discussion see
* Norbert Beckmann, Hans-Peter Kriegel, Ralf Schneider, Bernhard Seeger:
* "The R*-tree: An Efficient and Robust Access Method for Points and Rectangles",
* ACM-SIGMOD (1990)322-331.
*
* @see Tree
* @see ORTree
* @see LinearRTree
* @see QuadraticRTree
* @see GreenesRTree
*/
public class RTree extends ORTree {
/** Returns the bounding rectangle of <tt>entry</tt>.
*
* @param entry an entry
* @return the bounding rectangle of <tt>entry</tt>
*/
public Rectangle rectangle (Object entry) {
return (Rectangle)descriptor(entry);
}
/* (non-Javadoc)
* @see xxl.core.indexStructures.Tree#createNode(int)
*/
public Tree.Node createNode (int level) {
return new Node().initialize(level, new LinkedList());
}
/** <tt>Node</tt> is the class used to represent leaf- and non-leaf nodes of <tt>RTree</tt>.
* Nodes are stored in containers.
* @see Tree.Node
* @see ORTree.Node
*/
public class Node extends ORTree.Node {
/** SplitInfo contains information about a split. The enclosing
* Object of this SplitInfo-Object (i.e. Node.this) is the new node
* that was created by the split.
*/
public class SplitInfo extends ORTree.Node.SplitInfo {
/** The distribution of rectangles for the split.
*/
protected Distribution distribution;
/** Creates a new <tt>SplitInfo</tt> with a given path.
* @param path the path from the root to the splitted node
*/
public SplitInfo (Stack path) {
super(path);
}
/** Initializes the SplitInfo by setting the distribution of
* the split.
*
* @param distribution the distribution for the split
* @return the initialized <tt>SplitInfo</tt>
*/
public ORTree.Node.SplitInfo initialize (Distribution distribution) {
this.distribution = distribution;
return initialize(distribution.descriptor(true));
}
/** Returns the distribution of the <tt>SplitInfo</tt>.
*
* @return the distribution of the <tt>SplitInfo</tt>
*/
public Distribution distribution(){
return distribution;
}
}
/* (non-Javadoc)
* @see xxl.core.indexStructures.ORTree.Node#chooseSubtree(xxl.core.indexStructures.Descriptor, java.util.Iterator)
*/
protected ORTree.IndexEntry chooseSubtree (Descriptor descriptor, Iterator minima) {
final Rectangle dataRectangle = (Rectangle)descriptor;
TeeCursor validEntries = new TeeCursor(minima);
minima = new Filter(validEntries,
new AbstractPredicate() {
public boolean invoke (Object object) {
return rectangle(object).contains(dataRectangle);
}
}
);
if (!minima.hasNext()) {
minima = Cursors.minima(validEntries.cursor(),
new AbstractFunction() {
public Object invoke (Object object) {
Rectangle oldRectangle = rectangle(object);
Rectangle newRectangle = Descriptors.union(oldRectangle, dataRectangle);
return new Double(newRectangle.area()-oldRectangle.area());
}
}
).iterator();
}
minima = Cursors.minima(minima,
new AbstractFunction() {
public Object invoke (Object object) {
return new Double(rectangle(object).area());
}
}
).iterator();
validEntries.close();
return (IndexEntry)minima.next();
}
/* (non-Javadoc)
* @see xxl.core.indexStructures.Tree.Node#split(java.util.Stack)
*/
protected Tree.Node.SplitInfo split (Stack path) {
final Node node = (Node) node(path);
Distribution distribution;
final int minEntries = node.splitMinNumber();
final int maxEntries = node.splitMaxNumber();
int dimensions = ((Rectangle)rootDescriptor()).dimensions();
// For each dimension generate a list of all possible distributions
Iterator distributionLists = new Mapper(
new AbstractFunction() {
public Object invoke (Object object) {
final int dim = ((Integer)object).intValue();
// list of distributions for this dimension
ArrayList distributionList = new ArrayList(2*(maxEntries-minEntries+1));
Rectangle [][] rectangles = new Rectangle[2][];
// Consider the entrys sorted by left or right borders
for (int i=0; i<2; i++) {
Object [] entryArray = node.entries.toArray();
final boolean right = i==1;
// Sort the entries by left or right border in the actual dimension
Arrays.sort(entryArray, new FeatureComparator(new ComparableComparator(),
new AbstractFunction() {
public Object invoke (Object entry) {
return new Double(rectangle(entry).getCorner(right).getValue(dim));
}
}
));
// Calculation of descriptors for all distributions (linear!)
for (int k = 0; k<2; k++) {
List entryList = Arrays.asList(entryArray);
Iterator entries = (k==0? entryList: new ReversedList(entryList)).iterator();
Rectangle rectangle = new DoublePointRectangle(rectangle(entries.next()));
for (int l = (k==0? minEntries: node.number()-maxEntries); --l>0;)
rectangle.union(rectangle(entries.next()));
(rectangles[k] = new Rectangle[maxEntries-minEntries+1])[0] = rectangle;
for (int j=1; j<=maxEntries-minEntries; rectangles[k][j++] = rectangle)
rectangle = Descriptors.union(rectangle, rectangle(entries.next()));
}
// Creation of the distributions for this dimension
for (int j = minEntries; j<=maxEntries; j++)
distributionList.add(new Distribution(entryArray, j, rectangles[0][j-minEntries], rectangles[1][maxEntries-j], dim));
}
return distributionList;
}
}
,new Enumerator(dimensions));
// Return the distributionList of the dimension for which the margin-sum of all
// of its distributions is minimal (i.e. choose the dimension)
List distributionList = (List)Cursors.minima(distributionLists,
new AbstractFunction() {
public Object invoke (Object object) {
double marginValue = 0.0;
for (Iterator distributions = ((List)object).iterator(); distributions.hasNext();)
marginValue += ((Distribution)distributions.next()).marginValue();
return new Double(marginValue);
}
}
).getFirst();
// Choose the distributions of the chosen dimension with minimal overlap
distributionList = Cursors.minima(distributionList.iterator(),
new AbstractFunction() {
public Object invoke (Object object) {
return new Double(((Distribution)object).overlapValue());
}
}
);
// If still more than one distribution has to be considered, choose one
// with minimal area
distributionList = Cursors.minima(distributionList.iterator(),
new AbstractFunction() {
public Object invoke (Object object) {
return new Double(((Distribution)object).areaValue());
}
}
);
distribution = (Distribution)distributionList.get(0);
// Fill the pages with the entries according to the distribution
node.entries.clear();
node.entries.addAll(distribution.entries(false));
entries.addAll(distribution.entries(true));
// update the descriptor of the old index entry
((IndexEntry)indexEntry(path)).descriptor = distribution.descriptor(false);
return new SplitInfo(path).initialize(distribution);
}
/** <tt>Distribution</tt> is the class used to represent the distribution of
* entries of a node of the <tt>RTree</tt> into two partitions used for a split.
*/
protected class Distribution {
/** Entries stored in this distribution.
*/
protected Object [] entries;
/** Start index of the second part of the distribution.
*/
protected int secondStart;
/** Bounding Rectangle of the first part of the distribution.
*/
protected Rectangle firstDescriptor;
/** Bounding Rectangle of the first part of the distribution.
*/
protected Rectangle secondDescriptor;
/** Number of dimensions.
*/
protected int dim;
/**
* @param entries an array containing all entries to be distributed
* @param secondStart the start index of the second partition
* @param firstDescriptor the descriptor for the first partition
* @param secondDescriptor the descriptor for the second partition
* @param dim the number of dimensions
*/
protected Distribution (Object [] entries, int secondStart, Rectangle firstDescriptor, Rectangle secondDescriptor, int dim) {
this.entries = entries;
this.secondStart = secondStart;
this.firstDescriptor = firstDescriptor;
this.secondDescriptor = secondDescriptor;
this.dim = dim;
}
/** Returns one of the partitions of this distribution.
*
* @param second a <tt>boolean</tt> value determining if the second partition
* should be returned
* @return the entries of the first (if <tt>second == false</tt>) or of the
* second partition (if <tt>second == true</tt>)
*/
protected List entries (boolean second) {
return Arrays.asList(entries).subList(second? secondStart: 0, second? entries.length: secondStart);
}
/** Returns a descriptor of one of the partitions of this distribution.
*
* @param second a <tt>boolean</tt> value determining if the descriptor of
* second partition should be returned
* @return the descriptor of the first (if <tt>second == false</tt>) or of the
* second partition (if <tt>second == true</tt>)
*/
protected Descriptor descriptor (boolean second) {
return second? secondDescriptor: firstDescriptor;
}
/** Returns the number of dimenssions.
*
* @return the number of dimenssions
*/
protected int getDim(){
return dim;
}
/** Returns the sum of the margins of the two partitions.
*
* @return the sum of the margins of the two partitions
*/
protected double marginValue () {
return firstDescriptor.margin()+secondDescriptor.margin();
}
/** Returns the overlap of the two partitions.
*
* @return the overlap of the two partitions
*/
protected double overlapValue () {
return firstDescriptor.overlap(secondDescriptor);
}
/** Returns the sum of the areas of the two partitions.
*
* @return the sum of the areas of the two partitions
*/
protected double areaValue () {
return firstDescriptor.area()+secondDescriptor.area();
}
}
}
/** Gets a suitable Converter to serialize the tree's nodes.
*
* @param objectConverter a converter to convert the data objects stored in the tree
* @param dimensions the dimensions of the bounding rectangles
* @return a NodeConverter
*/
public Converter nodeConverter (Converter objectConverter, final int dimensions) {
return nodeConverter(objectConverter, indexEntryConverter(
new ConvertableConverter(
new AbstractFunction() {
public Object invoke () {
return new DoublePointRectangle(dimensions);
}
}
)
));
}
public class AsymmetricTwoDimensionalSkylineQuery extends Query {
protected DoublePoint queryPoint;
protected Set<DoublePoint>[][] skylines;
protected DoublePointRectangle[][] quadrants;
protected Predicate<DoublePoint>[][] dominates;
protected Function<DoublePointRectangle, DoublePoint> determineCorner[][];
public AsymmetricTwoDimensionalSkylineQuery(Queue queue, DoublePoint queryPoint) {
super(queue, 0);
this.queryPoint = queryPoint;
if (((Rectangle)rootDescriptor()).dimensions()!=2)
throw new IllegalStateException();
if (queryPoint.dimensions()!=2)
throw new IllegalArgumentException();
this.skylines = new Set[2][2];
this.dominates = new Predicate[2][2];
this.determineCorner = new Function[2][2];
for (int x=0; x<skylines.length; x++)
for (int y=0; y<skylines[x].length; y++) {
final boolean xup = x==1;
final boolean yup = y==1;
skylines[x][y] = new HashSet<DoublePoint>();
dominates[x][y] = new AbstractPredicate<DoublePoint>() {
public boolean invoke(DoublePoint a, DoublePoint b) {
if ((b.getValue(0)>=a.getValue(0)) != xup)
return false;
if ((b.getValue(1)>=a.getValue(1)) != yup)
return false;
return true;
}
};
}
determineCorner[0][0] = new AbstractFunction<DoublePointRectangle, DoublePoint>() {
public DoublePoint invoke(DoublePointRectangle r) {
return (DoublePoint)r.getCorner(true);
}
};
determineCorner[0][1] = new AbstractFunction<DoublePointRectangle, DoublePoint>() {
public DoublePoint invoke(DoublePointRectangle r) {
return Rectangles.lowerRightCorner(r);
}
};
determineCorner[1][0] = new AbstractFunction<DoublePointRectangle, DoublePoint>() {
public DoublePoint invoke(DoublePointRectangle r) {
return Rectangles.upperLeftCorner(r);
}
};
determineCorner[1][1] = new AbstractFunction<DoublePointRectangle, DoublePoint>() {
public DoublePoint invoke(DoublePointRectangle r) {
return (DoublePoint)r.getCorner(false);
}
};
this.quadrants = new DoublePointRectangle[2][2];
DoublePointRectangle universe = (DoublePointRectangle)rootDescriptor();
DoublePoint universeLowerLeft = (DoublePoint)universe.getCorner(false);
DoublePoint universeUpperRight = (DoublePoint)universe.getCorner(true);
quadrants[0][0] = new DoublePointRectangle(universeLowerLeft, queryPoint);
quadrants[0][1] = new DoublePointRectangle(
new double[] { universeLowerLeft.getValue(0), queryPoint.getValue(1) },
new double[] { queryPoint.getValue(0), universeUpperRight.getValue(1) }
);
quadrants[1][0] = new DoublePointRectangle(
new double[] { queryPoint.getValue(0), universeLowerLeft.getValue(1) },
new double[] { universeUpperRight.getValue(0), queryPoint.getValue(1) }
);
quadrants[1][1] = new DoublePointRectangle(queryPoint, universeUpperRight);
}
public boolean hasNextObject () {
queue : while (!queue.isEmpty()) {
Candidate candidate = (Candidate)queue.dequeue();
// inner node
if (candidate.parentLevel>targetLevel) {
DoublePointRectangle rect = (DoublePointRectangle) candidate.descriptor();
xloop : for (int x=0; x<skylines.length; x++)
for (int y=0; y<skylines[x].length; y++)
if(quadrants[x][y].contains(rect)) {
DoublePoint corner = determineCorner[x][y].invoke(rect);
for (DoublePoint sp : skylines[x][y])
if (dominates[x][y].invoke(sp,corner))
continue queue;
break xloop;
}
Node node = (RTree.Node)((IndexEntry)candidate.entry()).get(true);
Queues.enqueueAll(queue, expand(candidate, node));
}
// leaf node
else {
DoublePoint po = (DoublePoint) candidate.entry;
xloop: for (int x=0; x<skylines.length; x++)
for (int y=0; y<skylines[x].length; y++)
if (quadrants[x][y].contains(po)) {
for (DoublePoint sp : skylines[x][y])
if (dominates[x][y].invoke(sp,po))
continue queue;
skylines[x][y].add(po);
break xloop;
}
nextCandidate = candidate;
return true;
}
}
return false;
}
}
static Function<double[], double[]> absNorm(final DoublePoint queryPoint) {
return new AbstractFunction<double[], double[]>() {
double[] qp = (double[])queryPoint.getPoint();
public double[] invoke(double[] point) {
double [] res = new double[point.length];
for (int i=0; i<res.length; i++)
res[i] = point[i]<qp[i] ? 2*qp[i]-point[i] : point[i];
return res;
}
};
}
public class SymmetricSkylineQuery extends Query {
protected DoublePoint queryPoint;
protected Set<double[]> skyline;
protected Predicate<double[]> dominates;
protected Function<double[], double[]> norm;
protected final int dim;
public SymmetricSkylineQuery(Queue queue, DoublePoint queryPoint, Function<double[], double[]> norm) {
super(queue, 0);
this.queryPoint = queryPoint;
dim = ((Rectangle)rootDescriptor()).dimensions();
if (queryPoint.dimensions()!=dim)
throw new IllegalArgumentException();
this.norm = norm;
skyline = new HashSet<double[]>();
dominates = new AbstractPredicate<double[]>() {
public boolean invoke(double[] a, double[] b) {
for (int i=0; i<dim; i++)
if (b[i]<a[i])
return false;
return true;
}
};
}
public SymmetricSkylineQuery(Queue queue, DoublePoint queryPoint) {
this(queue, queryPoint, absNorm(queryPoint));
}
public boolean hasNextObject () {
double [] qp = (double[])queryPoint.getPoint();
queue : while (!queue.isEmpty()) {
Candidate candidate = (Candidate)queue.dequeue();
// inner node
if (candidate.parentLevel>targetLevel) {
DoublePointRectangle rect = (DoublePointRectangle) candidate.descriptor();
double[] ll = (double[])((DoublePoint)rect.getCorner(false)).getPoint();
double[] up = (double[])((DoublePoint)rect.getCorner(true)).getPoint();
boolean inCorner = true;
for (int i=0; inCorner && i<ll.length; i++)
inCorner &= (ll[i]<qp[i] == up[i]<qp[i]);
if(inCorner) {
double[] nllp = norm.invoke((double[])((DoublePoint)rect.getCorner(false)).getPoint());
double[] nurp = norm.invoke((double[])((DoublePoint)rect.getCorner(true)).getPoint());
for (int i=0; i<nllp.length; i++)
if (nurp[i]<nllp[i])
nllp[i]=nurp[i];
for (double[] sp : skyline)
if (dominates.invoke(sp,nllp))
continue queue;
}
Node node = (RTree.Node)((IndexEntry)candidate.entry()).get(true);
Queues.enqueueAll(queue, expand(candidate, node));
}
// leaf node
else {
double[] np = norm.invoke((double[])((DoublePoint)candidate.entry).getPoint());
for (double[] sp : skyline)
if (dominates.invoke(sp,np))
continue queue;
skyline.add(np);
nextCandidate = candidate;
return true;
}
}
return false;
}
}
/*********************************************************************/
/* DEBUG FUNCTIONALITY */
/*********************************************************************/
/* (non-Javadoc)
* @see xxl.core.indexStructures.ORTree#checkDescriptors(xxl.core.indexStructures.ORTree.IndexEntry)
*/
public boolean checkDescriptors (IndexEntry indexEntry) {
boolean returnValue = true;
Node node = (Node)indexEntry.get(true);
Descriptor descriptor = computeDescriptor(node.entries);
if (!descriptor.equals(indexEntry.descriptor))
System.out.println("Level "+node.level+": expected: "+descriptor+" actually:"+indexEntry.descriptor);
if (node.level>0)
for (Iterator entries = node.entries(); entries.hasNext();)
if (!checkDescriptors((IndexEntry)entries.next()))
returnValue = false;
return returnValue;
}
}