//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 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 LeafElementobject. */ 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;imay contain the required element. */ private Node findLeaf(Node node,LeafElement elmt) throws IllegalValueException,FileNotFoundException,IOException, NodeReadException, NodeWriteException { if((node == null) || (elmt == null)) throw new IllegalValueException("RTree.findLeaf: Node is null"); Node nd = null; Element[] allElmt = node.getAllElements(); int totElements = node.getTotalElements(); //Integer elmtPtr = (Integer)elmt.getPtr(); for(int i=0; iGuttman the Great. * An vector is returned. The tree is traversed recusively in post order. * @return If the file is empty or search rect. is out of scope then the method returns a zero size vector. */ public List overlaps(Rect rect) throws RTreeException,FileNotFoundException { fileHdr.lockRead(); //System.out.println("RTree.overlaps : in for thread " + Thread.currentThread().toString()); long root; if(rect == null) throw new RTreeException("RTree.overlaps: Rect is null"); root = fileHdr.getRootIndex(); try{ Node node = chdNodes.getReadNode(fileHdr.getFile(),fileName,root, fileHdr); List ret = getRPostOvrlap(node, rect); return ret; //return(getRPostOvrlap(chdNodes.getNode(fileHdr.getFile(),fileName,root, fileHdr), rect)); } catch(Exception e){ throw new RTreeException("RTree.overlaps: "+e.getMessage()); } finally{ //System.out.println("RTree.overlaps : out for thread " + Thread.currentThread().toString()); fileHdr.unlock(); } } /** Given any node it traverses a tree in a recursive post order manner fetching all overlaping elements in the leaves. */ private List getRPostOvrlap(Node node, Rect rect) throws IllegalValueException,FileNotFoundException,NodeReadException,IOException, NodeWriteException { if((node == null) || (rect == null)) throw new IllegalValueException("RTree.getRPostOvrlap: Node is null"); List list = new ArrayList(); //System.out.println("Nodes visited:"+node.getNodeIndex()); Element[] elmts = node.getAllElements(); int totElements = node.getTotalElements(); for(int i=0; ipostorder */ public List getAllElements() throws RTreeException, FileNotFoundException { //fileHdr.enter(Node.READ); fileHdr.lockRead(); //System.out.println("RTree.getAllElements : in for thread " + Thread.currentThread().toString()); long root; root = fileHdr.getRootIndex(); try{ return(trvsRPost(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr),false)); } catch(Exception e){ //e.printStackTrace(); throw new RTreeException("RTree.getAllElements: " +e.getMessage()); } finally{ //fileHdr.leave(); //System.out.println("RTree.getAllElements : out for thread " + Thread.currentThread().toString()); fileHdr.unlock(); } } /** Traverses the tree recursively in post order. The second parameter is for the delete method. As it goes through the nodes returning the leafelements it also deletes the nodes. This feature is not required for any other methods hence set del 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; ipreorder manner. */ public void printTree() throws RTreeException,FileNotFoundException { fileHdr.lockRead(); long root; root = fileHdr.getRootIndex(); try{ trvsRPrePrint(chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr)); } catch(Exception e){ throw new RTreeException("RTree.printTree: "+e.getMessage()); } finally{ fileHdr.unlock(); } } /** This method will return MBR of the whole tree. */ public Rect getTreeMBR() { fileHdr.lockRead(); long root; try{ root = fileHdr.getRootIndex(); Node node = chdNodes.getReadNode(fileHdr.getFile(),fileName,root,fileHdr); return node.getNodeMBR(); } catch(Exception e){ return null; } finally{ fileHdr.unlock(); } } public void deleteAllElements() throws RTreeException { fileHdr.lockWrite(); try{ chdNodes.removeAll(); fileHdr.resetHeader(); }catch(Exception e){ throw new RTreeException("RTree.deleteAllElements : " + e.getMessage()); } finally{ fileHdr.unlock(); } } /** Traverses the tree recursively in post order. */ private void trvsRPrePrint(Node node) throws IllegalValueException, FileNotFoundException,NodeReadException, IOException,NodeWriteException { if((node == null)) throw new IllegalValueException("RTree.trvsRPrePrint: Node is null"); Element[] elmts = node.getAllElements(); int totElements = node.getTotalElements(); System.out.println(node.toString()); for(int i=0; iRead Me Well.
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.

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 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; i= 0)){ if(arr[--i] != null){ if((arr[i].minDist <= elmt.minDist) || ((i == 0)&&(elmt.minDistnearestSearch method(not overloaded). This method finds all the objects within the given range. To understand this method please refer to the other nearestSearch 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; ilong value. There is no way to overwrite the value contained in the java.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. */