/* 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.rtrees;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import xxl.core.collections.containers.Container;
import xxl.core.collections.containers.CounterContainer;
import xxl.core.collections.containers.io.BlockFileContainer;
import xxl.core.collections.containers.io.BufferedContainer;
import xxl.core.collections.containers.io.ConverterContainer;
import xxl.core.collections.queues.DynamicHeap;
import xxl.core.cursors.Cursor;
import xxl.core.cursors.filters.Taker;
import xxl.core.functions.AbstractFunction;
import xxl.core.functions.Function;
import xxl.core.indexStructures.BPlusTree;
import xxl.core.indexStructures.Descriptor;
import xxl.core.indexStructures.HilbertRTree;
import xxl.core.indexStructures.Tree;
import xxl.core.indexStructures.BPlusTree.IndexEntry;
import xxl.core.indexStructures.HilbertRTree.ORKeyRange;
import xxl.core.indexStructures.HilbertRTree.ORSeparator;
import xxl.core.indexStructures.Tree.Query;
import xxl.core.indexStructures.Tree.Query.Candidate;
import xxl.core.io.LRUBuffer;
import xxl.core.io.converters.ConvertableConverter;
import xxl.core.io.converters.Converter;
import xxl.core.io.converters.IntegerConverter;
import xxl.core.io.converters.LongConverter;
import xxl.core.io.converters.MeasuredConverter;
import xxl.core.io.converters.MeasuredFixedSizeConverter;
import xxl.core.spatial.SpaceFillingCurves;
import xxl.core.spatial.points.DoublePoint;
import xxl.core.spatial.rectangles.DoublePointRectangle;
import xxl.core.spatial.rectangles.Rectangle;
/**
* Simple Test of the HilbertRTree with random DoublePoint ({@link DoublePoint})
*/
public class SimpleHilbertRTreeTest {
/**MinMaxFactor*/
static public double minMaxFactor = 1d/3d;
/**Dimension */
static public final int dimension = 2;
/**Block size */
static public final int blockSize = 1536;
/**Buffer size*/
static public final int bufferSize = 20;
/**Precision of the Hilbert Space filling curve*/
static public final int FILLING_CURVE_PRECISION = 1<<20;
/** Universe of the data space
* ( DoublePoints uniformly distributed in rectangle with xmin = 0 , ymin = 0, xmax = 1 , ymax = 1)
*/
static public final DoublePointRectangle universe = new DoublePointRectangle(new double[]{0,0},
new double[]{1.0,1.0});
/**
* Converter for the keys of the data. Keys are the java.long values(hilbert values of the MBRs middle points).
*/
static public MeasuredConverter keyConverter = new MeasuredFixedSizeConverter<Long>(LongConverter.DEFAULT_INSTANCE);
/**
* Measured converter for DoublePoints
*/
static public MeasuredConverter dataConverter = new MeasuredConverter(){
public int getMaxObjectSize() {
return dimension * 8;
}
public Object read(DataInput dataInput, Object object) throws IOException {
return ConvertableConverter.DEFAULT_INSTANCE.read(dataInput, new DoublePoint(dimension));
}
public void write(DataOutput dataOutput, Object object) throws IOException {
ConvertableConverter.DEFAULT_INSTANCE.write(dataOutput, (DoublePoint)object);
}
};
/**
* Function creating a MBR ({@link DoublePointRectangle}) for a given doublePoint object .
*/
static Function getEntryMBR = new AbstractFunction() {
public Object invoke (Object o) {
DoublePoint p = (DoublePoint)o;
return new DoublePointRectangle(p, p);
}
};
/**
* Returns a comparator which evaluates the distance of two candidate objects
* to the specified <tt>queryObject</tt>. This comparator
* is used for nearest neighbor queries and defines an order on candidate-
* descriptors. With the help of a priority queue (Min-heap) and this
* comparator the nearest neighbor query can be performed.
*
* @param queryObject a KPE to which the nearest neighbors should be determined
* @return a comparator defining an order on candidate objects
*/
public static Comparator getDistanceBasedComparator (DoublePoint queryObject) {
final Rectangle query = new DoublePointRectangle(queryObject, queryObject);
return new Comparator () {
public int compare (Object candidate1, Object candidate2) {
Rectangle r1 =( (HilbertRTree.ORSeparator) ((Candidate) candidate1).descriptor() ).getIndexEntryMBR();
Rectangle r2 = ( (HilbertRTree.ORSeparator) ((Candidate) candidate2).descriptor() ).getIndexEntryMBR();
double d1 = query.distance(r1, 2);
double d2 = query.distance(r2, 2);
return (d1<d2) ? -1 : ( (d1==d2) ? 0 : 1 );
}
};
}
/**
* This function computes hilbert curve value of the MBRs middle point
*/
static public Function getHilbertValue = new AbstractFunction(){
public Object invoke(Object point){
if (point instanceof DoublePoint){
DoublePoint middlePoint = (DoublePoint)point;
double x = middlePoint.getValue(0);
double y = middlePoint.getValue(1);
return new Long(SpaceFillingCurves.hilbert2d((int) (x*FILLING_CURVE_PRECISION),(int) (y*FILLING_CURVE_PRECISION)));
}
throw new IllegalArgumentException();
}
};
/**
* Factory function that creates ORSeparator of the HilbertRTree
*/
public static final Function createORSeparator =
new AbstractFunction() {
public Object invoke(Object key, Object mbr) {
return new DoublePointRectangleSep((Long)key, (DoublePointRectangle)mbr);
}
};
/**
* Factory function that creates ORKeyRange of the HilbertRTree
*/
public static final Function createORKeyRange =
new AbstractFunction() {
public Object invoke(List arguments) {
if(arguments.size() !=3 ) throw new IllegalArgumentException();
Long min = (Long)arguments.get(0);
Long max = (Long)arguments.get(1);
DoublePointRectangle entryMBR = (DoublePointRectangle)arguments.get(2);
return new LongRange(min, max, entryMBR);
}
};
/**
* This class represents the ORSeparator of the HilbertRTree
*
*
*/
public static class DoublePointRectangleSep extends HilbertRTree.ORSeparator{
public DoublePointRectangleSep(Long separatorValue, DoublePointRectangle entryMBR) {
super(separatorValue, entryMBR);
}
@Override
public Object clone() {
return new DoublePointRectangleSep(((Long)this.sepValue()).longValue(),
(DoublePointRectangle) ((Descriptor) this.entryMBR).clone()) ;
}
}
/**
* This class represents the ORKeyRange of the HilbertRTree
*
*
*/
public static class LongRange extends HilbertRTree.ORKeyRange{
public LongRange(Long min, Long max, DoublePointRectangle entryMBR) {
super(min, max, entryMBR);
}
@Override
public Object clone() {
return new LongRange(((Long)this.sepValue).longValue(), ((Long)this.maxBound).longValue(),
(DoublePointRectangle) ((Descriptor) this.entryMBR).clone());
}
}
/**
* This method saves the parameters of the HilbertRTree, this allows to make the tree persistent
* @param tree HilbertRTree
* @param pfad
* @throws IOException
*/
public static void saveParams(HilbertRTree tree, String pfad, Converter keyKonverter) throws IOException{
Tree.IndexEntry root = tree.rootEntry();
HilbertRTree.ORKeyRange desc = (HilbertRTree.ORKeyRange)tree.rootDescriptor();
int height = tree.height();
Long id = (Long)root.id();
Rectangle rec = (Rectangle)desc.getIndexEntryMBR();
Object minBound = desc.minBound();
Object maxBound = desc.maxBound();
DataOutputStream outPut = new DataOutputStream(new FileOutputStream(new File(pfad)));
LongConverter.DEFAULT_INSTANCE.write(outPut, id);
IntegerConverter.DEFAULT_INSTANCE.writeInt(outPut, height);
keyKonverter.write(outPut, minBound);
keyKonverter.write(outPut, maxBound);
ConvertableConverter.DEFAULT_INSTANCE.write(outPut, (DoublePointRectangle)rec);
outPut.close();
((Container)tree.getContainer.invoke()).flush();
((Container)tree.getContainer.invoke()).close();
}
/**
* Starts simpleHilbertRTree Test
* @param args path To HilbertRTree
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// check for command line argument
if (args.length!=1)
System.out.println("usage: java SimpleRTreeTest filename");
// test if RTree exists
boolean reopen = (new File(args[0]+".ctr")).canRead();
HilbertRTree tree = new HilbertRTree(blockSize, universe, minMaxFactor);
Container fileContainer = null;
BufferedContainer treeContainer = null;
/*********************************************************************/
/* INIT HILBERT TREE */
/*********************************************************************/
String treePath = args[0];
if (!reopen){
System.out.println("Init new HilbertRTree");
fileContainer = new CounterContainer(
new BlockFileContainer(treePath, blockSize));
treeContainer = new BufferedContainer(new ConverterContainer
(fileContainer, tree.nodeConverter()),
new LRUBuffer(bufferSize), false);
tree.initialize(getHilbertValue , getEntryMBR,
treeContainer,
keyConverter,
dataConverter,
createORSeparator,
createORKeyRange);
} else{
System.out.println("Load and Init persistent HilbertRTree");
fileContainer = new CounterContainer(
new BlockFileContainer(treePath));
treeContainer = new BufferedContainer(new ConverterContainer
( fileContainer, tree.nodeConverter()),
new LRUBuffer(bufferSize), false);
File parameters = new File(treePath+"_params.dat");
DataInputStream dos = new DataInputStream(new FileInputStream(parameters));
long rootsPageId = LongConverter.DEFAULT_INSTANCE.read(dos);
int height = IntegerConverter.DEFAULT_INSTANCE.read(dos);
Long minBound = (Long)keyConverter.read(dos);
Long maxBound = (Long)keyConverter.read(dos);
DoublePointRectangle mbr = (DoublePointRectangle)
ConvertableConverter.DEFAULT_INSTANCE.read(dos, new DoublePointRectangle(dimension));
BPlusTree.IndexEntry rootEntry = ((BPlusTree.IndexEntry)tree.createIndexEntry(height)).initialize
(rootsPageId, new DoublePointRectangleSep(minBound, mbr));
tree.initialize(rootEntry, new LongRange(minBound, maxBound, mbr),
getHilbertValue, getEntryMBR, treeContainer,
keyConverter, dataConverter,
createORSeparator, createORKeyRange);
}
/*********************************************************************/
/* INSERT RANDOM DATA */
/*********************************************************************/
if (!reopen){
System.out.println("Insert random data: ");
Random random = new Random(42);
int dataNumber = 100000;
for (int j=0; j<dataNumber; j++) {
// create random coordinates
double [] point = new double[dimension];
for (int i=0; i<dimension; i++)
point[i] = random.nextDouble();
// insert new point
tree.insert(new DoublePoint(point));
if (j % (dataNumber/10)==0)
System.out.print((j * 100)/dataNumber+"%, ");
}
System.out.print("100%\n");
}
/*********************************************************************/
/* RANGE QUERY */
/*********************************************************************/
// create query window
System.out.println("Range Queries");
double [] leftCorner = new double[dimension];
double [] rightCorner = new double[dimension];
for (int i=0; i<dimension; i++) {
leftCorner[i] = 0.4975;
rightCorner[i] = 0.5025;
}
DoublePointRectangle queryRange = new DoublePointRectangle(leftCorner, rightCorner);
// perform query
//
Cursor results = tree.queryOR(queryRange, 0);
// show results
int counter = 0;
System.out.println("Results for range query ("+queryRange+")");
while (results.hasNext()) {
DoublePoint next = (DoublePoint)results.next();
System.out.println("result no. "+(++counter)+": "+next);
}
results.close();
/*********************************************************************/
/* NEAREST NEIGHBOR QUERY */
/*********************************************************************/
System.out.println("kNN-Query");
// create query point
int resultsNumber = 10;
double [] point = new double[dimension];
for (int i=0; i<dimension; i++)
point[i] = 0.5;
DoublePoint queryObject = new DoublePoint(point);
// lazy Iterator of all nearest neighbors
Iterator nnIterator = tree.query(new DynamicHeap(getDistanceBasedComparator(queryObject)), 0);
results = new Taker(nnIterator,resultsNumber );
// show results
counter = 0;
System.out.println("\nResults for nearest neighbor query ("+queryObject+")");
while (results.hasNext()) {
DoublePoint next = (DoublePoint)((Candidate)results.next()).entry();
System.out.println("candidate no. "+(++counter)+": "+next+" (distance="+next.distanceTo(queryObject)+")");
}
results.close();
/*********************************************************************/
/* Save HilbertRTree */
/*********************************************************************/
if (!reopen){
saveParams(tree, args[0]+"_params.dat", keyConverter );
}
}
}