//RTree.java // //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. package rtree; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import rtree.join.IntersectPred; import rtree.join.PairElmt; import rtree.join.SweepLine; /****************************************************************************************************** *
1:Call flush
after single or multiple inserts. In affect before the application
* shuts down, the flush method flushes all the rtrees.
*
2:Do not run more than one instance of this application. The package * cannot handle more than one instance of the application as all the locking * is done in the code itself(Monitors) and not on the hard-disk file. *
3:The file name (of the rtree) is case insensitive. *
4:The package is thread proof, i.e you can run as many threads as * you want, not worrying about concurrent updations. The package will handle the * situation of concurrent reads and writes. Again, it can take care of threads * of only one process(Application) and not for more than one process of the program. *
5:For immediate effects of tree writes, give the thread that needs * to write to the file a high priority. This will further speed up tree writes. *
6:Give messages like "Updating, please wait..." to the user when * calling any of the methods that modifies the tree as the thread may have to * wait on a lock variable. *
7:The package maintains a queue of all threads that need to wait on some lock.
* The sequence of excecution of the threads is dependent on that queue. All the actions are fired in
* the order in which they were received.
In short the tree is perfectly concurrent.
*
8:For developers: Always obtain a lock from the lockRead
or
* lockWrite
method before going into a public
method of the
* RTree
class. Unlock by calling the unlock
method.
* See any existing method to understand the mechanism.
*
9:To adjust the cache buffer size, see the There may
be some objects in the tree which come within the region but are so big
that their size makes them extend much beyond the given region.
Such objects would also be considered.
If you do not want any limit then give Node
class documentation.
* @author Prachuryya Barua
******************************************************************************************************/
public class RTree //the tree that would be made
{
/**Caution: The file name (of the rtree) is case insensitive in spite
of the fact that this package was developed on a Linux(RH7.0) platform.
*/
protected String fileName;
static Map fileList;//the no. of files open
// static for the other way
protected FileHdr fileHdr;
public static CachedNodes chdNodes;
/**Inner class for the fileList vector - A List of files*/
class Header
{
FileHdr flHdr;
String fileName;
Header(FileHdr flH,String name)
{
fileName = name;
flHdr = flH;
}
}
public static void clearCache(){
chdNodes = new CachedNodes();
fileList = new HashMap();
CachedNodes.clearFileNamesMap();
}
public RTree(String fileName)
throws RTreeException
{
try{
this.fileName = fileName;
if(fileList == null)
fileList = new HashMap();
synchronized(fileList){//this may give problem
if(fileList.get(fileName) != null){
fileHdr = ((Header)fileList.get(fileName)).flHdr;
return;
}
//a new file
fileList.put(fileName, new Header(new FileHdr(Node.FREE_LIST_LIMIT, fileName),fileName));
fileHdr = ((Header)fileList.get(fileName)).flHdr;
//the cache of nodes - one cache for all the tree files.
if(chdNodes == null)
chdNodes = new CachedNodes();
}
}
catch(Exception e){
throw new RTreeException("RTree.RTree: " +e.getMessage());
}
}
/**
This method is used to ask the fileHdr to update itself. This method is package parivate used by
Pack
class only.
*/
void updateHdr()
throws RTreeException, IOException, FileNotFoundException, NodeWriteException
{
Header tmp = (Header)fileList.get(fileName);
if(tmp != null){
//chdNodes.removeAll();//XXX check this out
fileHdr.update(fileName);
}
}
public Node getReadNode(long index) throws RTreeException
{
try{
return chdNodes.getReadNode(fileHdr.getFile(), fileName, index, fileHdr);
}catch(Exception e){
throw new RTreeException ("RTree.getSortedNode : " + e.getMessage());
}
}
public String getFileName()
{
return fileName;
}
/**
Another package private method for getting the file header
*/
public FileHdr getFileHdr()
{
return fileHdr;
}
public void flush()
throws RTreeException
{
fileHdr.lockWrite();
try{
fileHdr.flush();
chdNodes.flush();
}catch(Exception e){
throw new RTreeException(e.getMessage());
}finally{
fileHdr.unlock();
}
}
/**
* Adjust Tree from Guttman the Great.
* @param Node[] The nodes that was has the new element and also the element
* that resulted from the splt(if any).i.e N-node[0], NN-nodes[1]
* Note:- This method is functionally very much coupled with Node.spliNode()
* @param node The two new nodes caused by split.
* @param slotIndex The index of the slot of this tree if any, else give NOT_DEFINED.
* @return The new root (if it was created). If no new root was created then returns null.
*/
protected Node adjustTree(Node[] nodes, long slotIndex)
throws RTreeException
{
try{
//if(nodes[0].getParent() == Node.NOT_DEFINED){//original
if(nodes[0].getParent() == slotIndex){
if(nodes[1] != null){//if root is split
Node newRoot;
if(fileHdr.isWriteThr())
newRoot = new Node(fileHdr.getFile(),fileName, slotIndex, Node.NONLEAF_NODE,
((Header)fileList.get(fileName)).flHdr);
else
newRoot = chdNodes.getNode(fileHdr.getFile(),fileName, slotIndex, Node.NONLEAF_NODE,
((Header)fileList.get(fileName)).flHdr, nodes[0]);
NonLeafElement branchA = new NonLeafElement(nodes[0].getNodeMBR(),nodes[0].getNodeIndex());
NonLeafElement branchB = new NonLeafElement(nodes[1].getNodeMBR(),nodes[1].getNodeIndex());
newRoot.insertElement(branchB);
newRoot.insertElement(branchA);
return newRoot;
}
return null;
}else{
//where the new element is inserted
Node[] insertedNode = new Node[2];
/*
set the parent element's MBR equal to the nodeA's MBR.
*/
//the parent of this node
Node parentN;
if(fileHdr.isWriteThr())
parentN = new Node(fileHdr.getFile(),fileName,nodes[0].getParent(),fileHdr);
else
parentN = chdNodes.getNode(fileHdr.getFile(),fileName,nodes[0].getParent(),fileHdr);
//get the parent element of nodes[0]
//Integer intValue = new Integer(nodes[0].getNodeIndex());
int parentElmtIndex = parentN.getElementIndex(nodes[0].getNodeIndex()/*intValue*/);
//adjust the parent element's MBR
parentN.modifyElement(parentElmtIndex,nodes[0].getNodeMBR());
insertedNode[0] = parentN;
insertedNode[1] = null;
//if it is an split node add its entry in the parent
if(nodes[1] != null){
NonLeafElement elmtNN = new NonLeafElement(nodes[1].getNodeMBR(),nodes[1].getNodeIndex());
try{//if another insert is possible
parentN.insertElement(elmtNN);
insertedNode[0] = parentN;
insertedNode[1] = null;
}catch(NodeFullException e){
insertedNode = parentN.splitNode(elmtNN, Node.NOT_DEFINED);
}
}
return adjustTree(insertedNode, slotIndex);
}
}
catch(Exception e){
e.printStackTrace();
throw new RTreeException("RTree.adjustTree: "+e.getMessage());
}
}
/**
Pass a LeafElement
object.
*/
public void insert(Element elmt)//Leaf
throws RTreeInsertException
{
fileHdr.lockWrite();
Node node = null;
try{
//the node into which to insert
node = chooseLeaf(elmt);
//System.out.println("Returned:"+node.getNodeIndex());
Node[] newNodes = new Node[2];
try{
node.insertElement(elmt);//if another insert is possible
newNodes[0] = node;
newNodes[1] = null;
}
//if another insert is not possible
catch(NodeFullException e){
newNodes = node.splitNode(elmt, Node.NOT_DEFINED);
}
adjustTree(newNodes, Node.NOT_DEFINED);
}
catch(Exception e){
//e.printStackTrace();
throw new RTreeInsertException("RTree.insert: " + e.getMessage() + " for " +
Thread.currentThread());
}
finally{
fileHdr.unlock();
}
}
/**
See Guttman the Great.
*/
private Node chooseLeaf(Element elmt)
throws IllegalValueException, RTreeException
{
try{
//get the root node
long root = fileHdr.getRootIndex();
Node node = null;
if(fileHdr.isWriteThr())
node = new Node(fileHdr.getFile(), fileName,root,fileHdr);
else
node = chdNodes.getNode(fileHdr.getFile(), fileName,root,fileHdr);
switch (node.getElementType()){
case Node.LEAF_NODE :
break;
case Node.NONLEAF_NODE :
//repeat till you reach a leaf node
while(true){
//get the best fitting rect from the node
Element nextElmt = node.getLeastEnlargement(elmt);
if(nextElmt.getElementType() == Node.LEAF_NODE)
break;
if(fileHdr.isWriteThr())
node = new Node(fileHdr.getFile(), fileName, nextElmt.getPtr(), fileHdr);
else
node = chdNodes.getNode(fileHdr.getFile(), fileName, nextElmt.getPtr(), fileHdr);
}
break;
default :
throw new IllegalValueException("RTree.chooseLeaf: Node corrupt, Illegal element type in "
+"node");
}
return node;
}
catch( IllegalValueException e){
throw new IllegalValueException("RTree.chooseLeaf: "+e.getMessage());
}
catch(Exception e){
e.printStackTrace();
throw new RTreeException("RTree.chooseLeaf: " + e.getMessage());
}
}
/**
given the leaf element, this method deletes the element from the tree
compairing its Rect and pointer with the similar entries in the tree.
@throws ElementNotException when the given element is not found.
*/
public void delete(LeafElement elmt)
throws RTreeException,ElementNotFoundException
{
fileHdr.lockWrite();
long root;
if(elmt == null)
throw new RTreeException("RTree.delete: Rect is null");
try{
if(fileHdr.isWriteThr())
chdNodes.removeAll();
root = fileHdr.getRootIndex();//FileHdr.getRootIndex(fileName);
//find the leaf that contains the element
Node delNode;
if(fileHdr.isWriteThr())
delNode = findLeaf(new Node(fileHdr.getFile(),fileName,root,fileHdr),elmt);
else
delNode = findLeaf(chdNodes.getNode(fileHdr.getFile(),fileName,root,fileHdr),elmt);
//if found...
if(delNode != null){
Element[] elmts = delNode.getAllElements();//all the elements
int totElmts = delNode.getTotalElements();//total elements
//index of the desired element
int childIndex = Node.NOT_DEFINED;
//find the index of the element that has to be deleted
for(int i=0;idel
as
false
for all other cases.
*/
private List trvsRPost(Node node,boolean del)
throws IllegalValueException,FileNotFoundException,NodeReadException,IOException, NodeWriteException
{
if((node == null))
throw new IllegalValueException("RTree.getRPostOvrlap: Node is null");
List list = new ArrayList();
Element[] elmts = node.getAllElements();
int totElements = node.getTotalElements();
//System.out.println("Nodes visited:"+node.getNodeIndex())
for(int i=0; i
The Nearest Neighbour(NN) search from Roussopoulos and Cheung.
The returned leaf elements are sorted in ascending order of their
differences from the query point.
If the no. of leaf elements found
are less then n
, then the rest of the values in the returend array
are null.
Give the limit(distance) within which you want to search.
The limit is in the same unit as the coordinates of the MBRs. Long.MAX_VALUE
in range
.
You can also search for presence of any object at the given point by giving
range
as 0
.
Also required are the no. of objects that need to be fetched(N-Nearest objects).
The value of range
is actually square of the distance you want.
Therefor if you want to search in an area of 10cm, give the range
as 100.
@param pt the query point.
@param range the region within which you want to search.
@param n the number of objects required.
@return the leaf elements found near the point.The length of the returned
array would be equal to n
.
*/
public ABL[] nearestSearch(Point pt,long range,int n)
throws RTreeException, IllegalValueException
{
fileHdr.lockRead();
if((pt == null) || (range < 0) || (n <= 0))
throw new IllegalValueException("RTree.nearestSearch: Illegal arguments");
try{
long root = fileHdr.getRootIndex();
ABL[] elmts = new ABL[n];
Nearest nrstDist = new Nearest();
nrstDist.value = range;
return INNSearch(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr),pt, elmts,nrstDist);
}
catch( IllegalValueException e){
throw new IllegalValueException(e.getMessage());
}
catch(Exception e){
throw new RTreeException("RTree.nearestSearch: " +e.getMessage());
}
finally{
fileHdr.unlock();
}
}
/**
Improved Nearest Neighbour Search - Cheung, theory Roussopoulos
*/
private ABL[] INNSearch(Node node, Point pt,ABL[] nrstElements,Nearest nrstDist)
throws IllegalValueException,FileNotFoundException,IOException,NodeReadException,
RTreeException,NodeWriteException
{
if((node == null))
throw new IllegalValueException("RTree.INNSearch: Node is null");
Element[] elmts = node.getAllElements();
int totElements = node.getTotalElements();
if(totElements == 0)//no elements
return null;
//System.out.println("In Node:"+node.getNodeIndex());
//if leaf
if(node.getElementType() == Node.LEAF_NODE){
//at leaf level compute distance to actual objects
for(int i=0; inearestSearch
method.
When the no. of objects required is less then a few thousands, this
method would be many times slower than the above method.
If possible use the other method.
@return vector of ABL objects within the given range.
*/
public List nearestSearch(Point pt,long range)
throws RTreeException, IllegalValueException
{
fileHdr.lockRead();
if((pt == null) || (range < 0))
throw new IllegalValueException("RTree.nearestSearch: "
+"Point null or int less than one");
try{
long root = fileHdr.getRootIndex();
List elmts = new ArrayList();
elmts = INNSearch(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr),pt,elmts,range);
return elmts;
}
catch( IllegalValueException e){
throw new IllegalValueException(e.getMessage());
}
catch(Exception e){
throw new RTreeException("RTree.nearestSearch: " + e.getMessage());
}
finally{
fileHdr.unlock();
}
}
private List INNSearch(Node node, Point pt, List nrstElements,long nrstDist)
throws IllegalValueException, FileNotFoundException,IOException,NodeReadException, Exception
{
if((node == null))
throw new IllegalValueException("RTree.INNSearch: Node is null");
Element[] elmts = node.getAllElements();
int totElements = node.getTotalElements();
if(totElements == 0)//no elements
return null;
//System.out.println("In Node:"+node.getNodeIndex());
//if leaf
if(node.getElementType() == Node.LEAF_NODE){
//at leaf level compute distance to actual objects
for(int i=0; ijava.lang.Long
wrapper class.
*/
protected class Nearest//a wrapper class for long
{
public long value;
}
//--------------------------------get height------------------------------
/**Upto 5 lakh objectes we can have a maximum height of 3
TODO : Calculate other levels as well. One may need to know whether the tree is packed or unpacked for
this.
`*/
public synchronized int getHeight()
{
int totNodes = fileHdr.getTotalNodes();
if(totNodes <= 1)
return 1;
else if(totNodes <= 170)
return 2;
else //if((totNodes > (84*84)) && (totNodes < (169*169+1)))
return 3;
// else
//return 4;
}
}
/* TODO:
Immediate Improvements
1)Take the common code from each query methods and put them in a single method.
2)Make a way of retuning a Integer objects other than LeafElement Objects.
*/