//
//RTree implementation.
//Copyright (C) 2002-2004 Wolfgang Baer - WBaer@gmx.de
//
//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 2.1 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, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package org.deegree.io.rtree;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Stack;
import java.util.Vector;
/**
* <br>
* Implementation of a R-Tree after the algorithms of Antonio Guttman.
* With nearest neighbour search after algorithm of Cheung & Fu
* <br>
* @author Wolfgang Baer - WBaer@gmx.de
*/
public class RTree implements Serializable {
private PageFile file;
/**
* Creates an empty R-Tree with a memory-mapped pagefile (MemoryPageFile)
* and an empty root node
* @param dimension - dimension of the data to store
* @param maxLoad - maximum load of a node
**/
public RTree( int dimension, int maxLoad ) throws RTreeException {
this.file = new MemoryPageFile();
try {
file.initialize( dimension, maxLoad + 1 );
Node rootNode = new LeafNode( 0, this.file );
file.writeNode( rootNode );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException in constructor occured" );
}
}
/**
* Creates an empty R-Tree with a persistent pagefile (PersistentPageFile)
* and an empty root node.
* @param dimension - dimension of the data to store
* @param maxLoad - maximum load of a node
* @param filename - name of the rtree file
*/
public RTree( int dimension, int maxLoad, String fileName ) throws RTreeException {
try {
this.file = new PersistentPageFile( fileName );
this.file.initialize( dimension, maxLoad + 1 );
Node rootNode = new LeafNode( 0, this.file );
file.writeNode( rootNode );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException in constructor occured" );
}
}
/**
* Creates an R-Tree from an EXISTING persistent pagefile (PersistentPageFile).
* @param filename - name of the existing rtree file
*/
public RTree( String fileName ) throws RTreeException {
this.file = new PersistentPageFile( fileName );
try {
this.file.initialize( -999, -999 );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException in constructor occured" );
}
}
/**
* Searches all entries in the R-Tree whose HyperBoundingBoxes intersect with the given.
* @param box - given test HyperBoundingBox
* @return Object[] - Array with retrieved Objects
*/
public Object[] intersects( HyperBoundingBox box )
throws RTreeException {
if ( box.getDimension() != file.getDimension() )
throw new IllegalArgumentException( "HyperBoundingBox has wrong dimension " + box.getDimension() + " != " + file.getDimension() );
Vector v = new Vector();
// calls the real search method
try {
intersectsSearch( file.readNode( 0 ), v, box );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException RTree.search() - readNode()" );
}
return v.toArray();
}
/**
* Searches all entries in the R-Tree whose HyperBoundingBoxes contain the given.
* @param box - given test HyperBoundingBox
* @return Object[] - Array with retrieved Objects
*/
public Object[] contains( HyperBoundingBox box )
throws RTreeException {
if ( box.getDimension() != file.getDimension() )
throw new IllegalArgumentException( "HyperBoundingBox has wrong dimension" );
Vector v = new Vector();
try {
containsSearch( file.readNode( 0 ), v, box );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException RTree.search() - readNode() " );
}
return v.toArray();
}
// private method for contains search
private void containsSearch( Node node1, Vector v, HyperBoundingBox box ) {
if ( node1 instanceof LeafNode ) {
LeafNode node = (LeafNode) node1;
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
// if box is contained put into Vector
if ( node.hyperBBs[i].contains( box ) )
v.addElement( node.getData( i ) );
}
return;
}
NoneLeafNode node = (NoneLeafNode) node1;
// node is no Leafnode - so search all entries for overlapping
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
if ( node.hyperBBs[i].contains( box ) )
containsSearch( (Node) node.getData( i ), v, box );
}
}
// private method for intersects search
private void intersectsSearch( Node node1, Vector v, HyperBoundingBox box ) {
if ( node1 instanceof LeafNode ) {
LeafNode node = (LeafNode) node1;
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
if ( node.hyperBBs[i].overlaps( box ) )
v.addElement( node.getData( i ) );
}
return;
}
NoneLeafNode node = (NoneLeafNode) node1;
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
if ( node.hyperBBs[i].overlaps( box ) )
intersectsSearch( (Node) node.getData( i ), v, box );
}
}
/**
* Inserts the given Object associated with the given HyperBoundingBox object into the R-Tree.
* @param obj - Object to insert
* @param box - associated HyperBoundingBox
* @return boolean - true if successfull
*/
public boolean insert( Object obj, HyperBoundingBox box )
throws RTreeException {
try {
Node[] newNodes = new Node[] { null, null };
//Find position for new record
LeafNode node;
node = chooseLeaf( file.readNode( 0 ), box );
// Add record to leaf node
if ( node.getUsedSpace() < ( file.getCapacity() - 1 ) ) {
node.insertData( obj, box );
file.writeNode( node );
} else {
// invoke SplitNode
node.insertData( obj, box );
file.writeNode( node );
newNodes = splitNode( node );
}
if ( newNodes[0] != null ) {
adjustTree( newNodes[0], newNodes[1] );
} else {
adjustTree( node, null );
}
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException occured" );
}
return true;
}
// algorithm to split a full node
private Node[] splitNode( Node node )
throws PageFileException {
// new node
Node newNode = null;
// temp help node
Node helpNode = null;
// compute the start entries
int[] seeds = pickSeeds( node );
if ( node instanceof LeafNode ) {
newNode = new LeafNode( this.file );
helpNode = new LeafNode( node.getPageNumber(), this.file );
} else {
newNode = new NoneLeafNode( -1, this.file );
helpNode = new NoneLeafNode( node.getPageNumber(), this.file );
}
// write the new node to pagefile
file.writeNode( newNode );
node.counter = 0;
node.unionMinBB = HyperBoundingBox.getNullHyperBoundingBox( file.getDimension() );
// insert the start entries
helpNode.insertData( node.getData( seeds[0] ), node.getHyperBoundingBox( seeds[0] ) );
newNode.insertData( node.getData( seeds[1] ), node.getHyperBoundingBox( seeds[1] ) );
// mark the inserted entries - first build a marker array
boolean[] marker = new boolean[file.getCapacity()];
for ( int i = 0; i < file.getCapacity(); i++ )
marker[i] = false;
// mark them
marker[seeds[0]] = true;
marker[seeds[1]] = true;
int doneCounter = file.getCapacity() - 2;
// do until all entries are put into one of the groups or until
// one group has so less entries that the remainder must be given to that group
while ( doneCounter > 0 ) {
int[] entry;
entry = pickNext( node, marker, helpNode, newNode );
doneCounter--;
if ( entry[0] == 1 )
helpNode.insertData( node.getData( entry[1] ), node.getHyperBoundingBox( entry[1] ) );
else
newNode.insertData( node.getData( entry[1] ), node.getHyperBoundingBox( entry[1] ) );
if ( ( file.getMinimum() - helpNode.getUsedSpace() ) == doneCounter ) {
for ( int i = 0; i < file.getCapacity(); i++ )
if ( marker[i] == false )
helpNode.insertData( node.getData( i ), node.getHyperBoundingBox( i ) );
break;
}
if ( ( file.getMinimum() - newNode.getUsedSpace() ) == doneCounter ) {
for ( int i = 0; i < file.getCapacity(); i++ )
if ( marker[i] == false )
newNode.insertData( node.getData( i ), node.getHyperBoundingBox( i ) );
break;
}
}
// put the entries from the temp node to current node
for ( int x = 0; x < helpNode.getUsedSpace(); x++ )
node.insertData( helpNode.getData( x ), helpNode.getHyperBoundingBox( x ) );
file.writeNode( node );
file.writeNode( newNode );
return new Node[] { node, newNode };
}
// picks the first to entries for the new nodes - returns the index of the entries
private int[] pickSeeds( Node node ) {
double max = 0.0;
int e1 = 0;
int e2 = 0;
// walks through all combinations and takes
// the combination with the largest area enlargement
for ( int i = 0; i < file.getCapacity(); i++ )
for ( int j = 0; j < file.getCapacity(); j++ ) {
if ( i != j ) {
double d = ( node.getHyperBoundingBox( i ) ).unionBoundingBox(
node.getHyperBoundingBox( j ) ).getArea()
- node.getHyperBoundingBox( i ).getArea()
- node.getHyperBoundingBox( j ).getArea();
if ( d > max ) {
max = d;
e1 = i;
e2 = j;
}
}
}
return new int[] { e1, e2 };
}
// int[0] = group, int[1] = entry
private int[] pickNext( Node node, boolean[] marker, Node group1, Node group2 ) {
double d0 = 0;
double d1 = 0;
double diff = -1;
double max = -1;
int entry = 99;
int group = 99;
for ( int i = 0; i < file.getCapacity(); i++ ) {
if ( marker[i] == false ) {
d0 = group1.getUnionMinBB().unionBoundingBox( node.getHyperBoundingBox( i ) ).getArea()
- group1.getUnionMinBB().getArea();
d1 = group2.getUnionMinBB().unionBoundingBox( node.getHyperBoundingBox( i ) ).getArea()
- group2.getUnionMinBB().getArea();
diff = Math.abs( d0 - d1 );
if ( diff > max ) {
if ( d0 < d1 )
group = 1;
else
group = 2;
max = diff;
entry = i;
}
if ( diff == max ) {
if ( d0 < d1 )
group = 1;
else
group = 2;
max = diff;
entry = i;
}
}
}
marker[entry] = true;
return new int[] { group, entry };
}
// searches the leafnode with LeastEnlargment criterium for insert
private LeafNode chooseLeaf( Node node, HyperBoundingBox box ) {
if ( node instanceof LeafNode ) {
return (LeafNode) node;
}
NoneLeafNode node1 = (NoneLeafNode) node;
int least = node1.getLeastEnlargement( box );
return chooseLeaf( (Node) node1.getData( least ), box );
}
/**
* Queries the nearest neighbour to given search HyperPoint
* @param point - search point
* @return double[] - Place 0 = Distance, Place 1 = data number (must be cast to int)
*/
public double[] nearestNeighbour( HyperPoint point )
throws RTreeException {
try {
return nearestNeighbour( file.readNode( 0 ), point,
new double[] { Double.POSITIVE_INFINITY, -1.0 } );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException - nearestNeighbour - readNode(0)" );
}
}
// private method for nearest neighbour query
private double[] nearestNeighbour( Node node, HyperPoint point, double[] temp ) {
if ( node instanceof LeafNode ) {
// if mindist this < tempDist
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
double dist = node.getHyperBoundingBox( i ).minDist( point );
if ( dist < temp[0] ) {
// then this = nearest Neighbour - update tempDist
temp[1] = ( (LeafNode) node ).data[i];
temp[0] = dist;
}
}
} else {
// inner class ABL
class ABL implements Comparable {
Node node;
double minDist;
public ABL( Node node, double minDist ) {
this.node = node;
this.minDist = minDist;
}
public int compareTo( Object obj ) {
ABL help = (ABL) obj;
if ( this.minDist < help.minDist )
return -1;
if ( this.minDist > help.minDist )
return 1;
return 0;
}
}
// generate ActiveBranchList of node
ABL[] abl = new ABL[node.getUsedSpace()];
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
Node help = (Node) node.getData( i );
abl[i] = new ABL( help, help.getUnionMinBB().minDist( point ) );
}
//sort activebranchlist
Arrays.sort( abl );
for ( int i = 0; i < abl.length; i++ ) {
// apply heuristic 3
if ( abl[i].minDist <= temp[0] ) {
temp = nearestNeighbour( abl[i].node, point, temp );
}
}
}
return temp;
}
/**
* Closes the rtree.
* @throws RTreeException - if an error occures.
*/
public void close()
throws RTreeException {
try {
file.close();
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException - close()" );
}
}
/**
* Deletes an entry from the RTree.
* @param box - HyperBoundingBox of the entry to deleted
* @param objID - Integer value of Object-ID to be deleted
* @return boolean - true if successfull
*/
public boolean delete( HyperBoundingBox box, int objID )
throws RTreeException {
Vector v = new Vector();
try {
findLeaf( file.readNode( 0 ), box, objID, v );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException - delete()" );
}
if ( v.size() < 1 )
return false;
if ( v.size() == 1 ) {
LeafNode leaf = (LeafNode) v.elementAt( 0 );
for ( int i = 0; i < leaf.getUsedSpace(); i++ ) {
if ( leaf.getHyperBoundingBox( i ).equals( box ) && leaf.data[i] == objID ) {
leaf.deleteData( i );
try {
file.writeNode( leaf );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException - delete()" );
}
}
}
Stack stack = new Stack();
try {
condenseTree( leaf, stack );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException - condenseTree()" );
}
while ( !stack.empty() ) {
Node node = (Node) stack.pop();
if ( node instanceof LeafNode ) {
for ( int i = 0; i < node.getUsedSpace(); i++ )
this.insert( ( (LeafNode) node ).getData( i ),
( (LeafNode) node ).getHyperBoundingBox( i ) );
} else {
for ( int i = 0; i < node.getUsedSpace(); i++ )
stack.push( ( (NoneLeafNode) node ).getData( i ) );
}
try {
file.deleteNode( node.pageNumber );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException - delete() - deleteNode(0)" );
}
}
}
return true;
}
/**
* Deletes all entries from the R-Tree with given HyperBoundingBox
* @param box - HyperBoundingBox
* @return boolean - true if successfull
*/
public boolean delete( HyperBoundingBox box )
throws RTreeException {
Vector v = new Vector();
try {
findLeaf( file.readNode( 0 ), box, v );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException - delete()" );
}
if ( v.size() < 1 )
return false;
LeafNode leaf;
for ( Enumeration en = v.elements(); en.hasMoreElements(); ) {
leaf = (LeafNode) en.nextElement();
for ( int i = 0; i < leaf.getUsedSpace(); i++ ) {
if ( leaf.getHyperBoundingBox( i ).equals( box ) ) {
leaf.deleteData( i );
try {
file.writeNode( leaf );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException - delete()" );
}
}
}
Stack stack = new Stack();
try {
condenseTree( leaf, stack );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException - condenseTree()" );
}
while ( !stack.empty() ) {
Node node = (Node) stack.pop();
if ( node instanceof LeafNode ) {
for ( int i = 0; i < node.getUsedSpace(); i++ )
this.insert( ( (LeafNode) node ).getData( i ),
( (LeafNode) node ).getHyperBoundingBox( i ) );
} else {
for ( int i = 0; i < node.getUsedSpace(); i++ )
stack.push( ( (NoneLeafNode) node ).getData( i ) );
}
try {
file.deleteNode( node.pageNumber );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException - delete() - deleteNode(0)" );
}
}
}
return true;
}
/**
* Retrieves all entries with the given HyperBoundingBox.
* @param box - HyperBoundingBox
* @return Object[] - array with retrieved objects
*/
public Object[] find( HyperBoundingBox box )
throws RTreeException {
if ( box.getDimension() != file.getDimension() )
throw new IllegalArgumentException( "HyperBoundingBox has wrong dimension" );
Vector v = new Vector();
// ruft die eigentliche suche auf
try {
findSearch( file.readNode( 0 ), v, box );
} catch ( PageFileException e ) {
e.fillInStackTrace();
throw new RTreeException( "PageFileException RTree.search() - readNode()" );
}
return v.toArray();
}
// F�hrt die eigentliche Suche durch - Aufruf von search(HyperBoundingBox box)
private void findSearch( Node node1, Vector v, HyperBoundingBox box ) {
if ( node1 instanceof LeafNode ) {
LeafNode node = (LeafNode) node1;
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
// wenn eintraege enthalten diese in Vechtor aufnehmen;
if ( node.hyperBBs[i].equals( box ) )
v.addElement( node.getData( i ) );
}
return;
}
NoneLeafNode node = (NoneLeafNode) node1;
// node ist kein LeafNode
// alle eintrraege auf �berlappung durchsuchen
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
// wenn enthalten rekursiv search mit diesem node aufrufen
if ( node.hyperBBs[i].contains( box ) ) {
findSearch( (Node) node.getData( i ), v, box );
}
}
}
// Retrieves all leaf nodes regardless of the id
private void findLeaf( Node node, HyperBoundingBox box, Vector v ) {
if ( node instanceof LeafNode ) {
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
if ( node.getHyperBoundingBox( i ).equals( box ) )
v.addElement( node );
}
} else {
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
if ( node.getHyperBoundingBox( i ).overlaps( box ) )
findLeaf( (Node) node.getData( i ), box, v );
}
}
}
// Retrieves all leaf nodes with correct box and id
private void findLeaf( Node node, HyperBoundingBox box, int objID, Vector v ) {
if ( node instanceof LeafNode ) {
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
if ( ( (LeafNode) node ).data[i] == objID
&& node.getHyperBoundingBox( i ).equals( box ) )
v.addElement( node );
}
} else {
for ( int i = 0; i < node.getUsedSpace(); i++ ) {
if ( node.getHyperBoundingBox( i ).overlaps( box ) )
findLeaf( (Node) node.getData( i ), box, objID, v );
}
}
}
// condenses the tree after remove of some entries
private void condenseTree( Node n, Stack stack )
throws PageFileException {
if ( !n.isRoot() ) {
Node p = n.getParent();
if ( n.getUsedSpace() < file.getMinimum() ) {
p.deleteData( n.place );
stack.push( n );
} else {
p.hyperBBs[n.place] = n.getUnionMinBB();
p.updateNodeBoundingBox();
}
file.writeNode( p );
condenseTree( p, stack );
} else {
if ( n.getUsedSpace() == 1 && ( n instanceof NoneLeafNode ) ) {
Node kind = (Node) n.getData( 0 );
Node newRoot = null;
if ( kind instanceof LeafNode ) {
newRoot = new LeafNode( 0, this.file );
for ( int i = 0; i < kind.getUsedSpace(); i++ )
newRoot.insertData( kind.getData( i ), kind.getHyperBoundingBox( i ) );
} else {
newRoot = new NoneLeafNode( 0, this.file );
for ( int i = 0; i < kind.getUsedSpace(); i++ )
newRoot.insertData( kind.getData( i ), kind.getHyperBoundingBox( i ) );
}
file.writeNode( newRoot );
}
}
}
// adjustes the Tree with the correct bounding boxes and
// propagates needed splits upwards
private void adjustTree( Node n1, Node n2 )
throws PageFileException {
// if n2 = null - only adjust boundingboxes
// if n2 != null a split occured - maybe propagate split
if ( n1.isRoot() ) {
// if n2 != null we need a new Root node - Root Split
if ( ( n2 != null ) && n1.isRoot() ) {
// Node must be written from page number 0 (root number) to other
n1.setPageNumber( -1 );
int pagenumber;
pagenumber = file.writeNode( n1 );
for ( int x = 0; x < n1.getUsedSpace(); x++ ) {
Object obj = n1.getData( x );
if ( obj instanceof Node ) {
Node node = (Node) obj;
node.parentNode = pagenumber;
file.writeNode( node );
}
obj = null;
}
NoneLeafNode newRoot = new NoneLeafNode( 0, this.file );
newRoot.insertData( n1, n1.getUnionMinBB() );
newRoot.insertData( n2, n2.getUnionMinBB() );
newRoot.parentNode = 0;
file.writeNode( newRoot );
}
return;
}
// adjust the bounding boxes in the parents for Node n1
NoneLeafNode p = (NoneLeafNode) n1.getParent();
p.hyperBBs[n1.place] = n1.getUnionMinBB();
p.unionMinBB = ( p.getUnionMinBB() ).unionBoundingBox( n1.getUnionMinBB() );
file.writeNode( p );
// propagate adjustment upwards
if ( n2 == null ) {
adjustTree( p, null );
} else {
// as there occured a split - the second node has to be inserted
Node[] newNodes = new Node[] { null, null };
if ( p.getUsedSpace() < ( file.getCapacity() - 1 ) ) {
// new split must happen
p.insertData( n2, n2.getUnionMinBB() );
file.writeNode( p );
newNodes[0] = p;
} else {
p.insertData( n2, n2.getUnionMinBB() );
file.writeNode( p );
newNodes = splitNode( p );
}
adjustTree( newNodes[0], newNodes[1] );
}
}
}/* ********************************************************************
Changes to this class. What the people have been up to:
$Log: RTree.java,v $
Revision 1.6 2006/07/12 14:46:17 poth
comment footer added
********************************************************************** */