package org.geotoolkit.pending.demo.tree; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.HashMap; import java.util.Map; import org.apache.sis.geometry.GeneralEnvelope; import org.geotoolkit.index.tree.StoreIndexException; import org.geotoolkit.index.tree.Tree; import org.geotoolkit.index.tree.TreeElementMapper; import org.geotoolkit.index.tree.TreeIdentifierIterator; import org.geotoolkit.index.tree.star.FileStarRTree; import org.geotoolkit.index.tree.star.MemoryStarRTree; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.referencing.CommonCRS; /** * R-tree uses. * Exist : R-Tree (BasicRTree), R*Tree (StarRTree), Hilbert R-Tree (HilbertRTree). * * R-Tree : fast indexing time, slow search query time.(Adapted for many update use). * R*Tree : moderate indexing time, moderate search query time. * Hilbert R-Tree : slow indexing time, fast search query time.(Adapted for less update and many search query uses). * * @author Rémi Maréchal (Geomatys). * @see Tree */ public class TreeDemo { /* * CoordinateReferenceSystem used in this demo. */ private static CoordinateReferenceSystem DEMO_CRS = CommonCRS.WGS84.normalizedGeographic(); public static void main(String[] args) throws StoreIndexException, IOException, ClassNotFoundException { /* * In these examples we'll show how to build and create a RTree according to our use case. * 2 RTree storage are made, the first where RTree is stored in computer memory * and the second where RTree is stored in file on hard drive at a path stipulated by the user. * * RTree which is stored in computer memory : * - advantages : all RTree action are faster than the others because do not require hard drive access. * - inconvenients : computer RAM memory limits data number which should be inserted. * * RTree which is stored on hard drive : * - advantages : there is no data number limit of insertions. * - inconvenients : all RTree action are slower than the others because require hard drive access. */ /* * First example RTree stored in our computer memory. * In this example user has to know he can't insert too many elements, to not overflow memory. * For this example we choose StarRTree. */ /* * In each RTree we need another object called TreeElementMapper. * This Object allows to link Integer identifier stored in RTree and datas. * (It is an internal operation). User should just override some methods from TreeElementMapper. * TreeElementMapper uses Generic template and user may specify the type. * In the present example we are working with Envelope type object. Moreover to store Envelope object we use an Envelope table. * User can choose List or Map or other object according to his needs. */ final TreeElementMapper<Envelope> demoMemoryTreeEltMapper = new TreeElementMapper<Envelope>() { private int currentSize = 100; private Envelope[] envelopes = new Envelope[currentSize]; private boolean isClose = false; /** * {@inheritDoc } */ @Override public int getTreeIdentifier(final Envelope object) throws IOException { int i = 0; for (Envelope env : envelopes) { if (env.equals(object)) return i+1; i++; } throw new IllegalStateException("getTreeIdentifier : impossible to find tree identifier from Envelope object."); } /** * {@inheritDoc } */ @Override public Envelope getEnvelope(final Envelope object) throws IOException { return object; } /** * {@inheritDoc } */ @Override public void setTreeIdentifier(final Envelope object, final int treeIdentifier) throws IOException { final int envID = treeIdentifier - 1; if (envID >= currentSize) { currentSize = currentSize << 1; Arrays.copyOf(envelopes, currentSize); } envelopes[envID] = object; } /** * {@inheritDoc } */ @Override public Envelope getObjectFromTreeIdentifier(final int treeIdentifier) throws IOException { final int envID = treeIdentifier - 1; if (envID >= currentSize) throw new IllegalStateException("getObjectFromTreeIdentifier : impossible to find object from identifier."); return envelopes[envID]; } /** * {@inheritDoc } */ @Override public void clear() throws IOException { currentSize = 100; envelopes = new Envelope[currentSize]; } /** * {@inheritDoc } */ @Override public void close() throws IOException { // no stream to close. isClose = true; } /** * {@inheritDoc } */ @Override public boolean isClosed() { return isClose; } @Override public void flush() throws IOException { //do nothing } @Override public Map<Integer, Envelope> getFullMap() throws IOException { return new HashMap<>(); } }; /* * After creating TreeElementMapper object we create RTree. */ final Tree demoMemoryRTree = new MemoryStarRTree<Envelope>(5, DEMO_CRS, demoMemoryTreeEltMapper); /* * get datas. */ Envelope[] insertedEnvelope = createData(20); /* * Now insert data in RTree. */ for (Envelope data : insertedEnvelope) { demoMemoryRTree.insert(data); } System.out.println("Tree Demo : "+demoMemoryRTree.toString()); /* * Now we should search. */ final GeneralEnvelope envelopeSearch = new GeneralEnvelope(DEMO_CRS); envelopeSearch.setEnvelope(-45, -50, 110, 75); /* * There is two way to search. * First, we can get a table of data identifiers which matches with the search area. */ int[] treeIdentifierResult = demoMemoryRTree.searchID(envelopeSearch); System.out.println("identifiers which match : "+Arrays.toString(treeIdentifierResult)); /* * To obtain data you have to ask data from TreeElementMapper as below. */ final Envelope[] resultData = new Envelope[treeIdentifierResult.length]; for (int id = 0; id < treeIdentifierResult.length; id++) { resultData[id] = demoMemoryTreeEltMapper.getObjectFromTreeIdentifier(treeIdentifierResult[id]); } /* * Secondly, we can get an iterator which iterates on each treeIdentifier search result. */ TreeIdentifierIterator treeIterator = demoMemoryRTree.search(envelopeSearch); /* * And we can get data object result as below. */ List<Envelope> listDataResult = new ArrayList<Envelope>(); while (treeIterator.hasNext()) { listDataResult.add(demoMemoryTreeEltMapper.getObjectFromTreeIdentifier(treeIterator.nextInt())); } /* * Moreover we can also remove some elements in index RTree. */ int idEnv = 1; for (Envelope env : insertedEnvelope) { demoMemoryRTree.remove(env); if (idEnv % 5 == 0) { System.out.println("RTree : during remove action after "+idEnv+" elements : "+demoMemoryRTree.toString()); } idEnv++; } /***********************************/ /* * Second example: RTree stored on user computer hard disk. * If the user doesn't know if there is enought memory on his computer or * if he knows that all the data overflow memory, he may choose to store RTree * on his computer hard drive. */ /* * Build the 2 files to store RTree. * One to store RTree architecture. * The other to store TreeElementMapper architecture. */ final File treeFile = File.createTempFile("tree", "bin"); final File treeELTMapperFile = File.createTempFile("mapper", "bin"); /* * First, like previously, we have to begin creating an appropriate * TreeElementMapper to link treeIdentifier and stored object. * In our case we would store elements on hard drive, then we would override * a TreeElementMapper implementation called FileTreeElementMapper. * See DemoFileTreeElementMapper class. */ TreeElementMapper<Envelope> demoFileTreeEltMapper = new DemoFileTreeElementMapper(treeELTMapperFile, DEMO_CRS); /* * Be carefull it exists 2 ways to open a FileRTree. * If tree has never been built previously, the user should open RTree in writing action.(see Javadoc) * Unlike if a tree has already been saved on a filled file, * the user should open RTree in reading/writing and use constructor in accordance with it. */ /* * First, we make an RTree as if it has never been created before. */ Tree<Envelope> demoFileRTree = new FileStarRTree<Envelope>(treeFile.toPath(), 4, DEMO_CRS, demoFileTreeEltMapper); /* * At this step the RTree is empty and we can fill it like previously. */ /* * get datas. */ insertedEnvelope = createData(20); /* * Now insert data in RTree. */ for (Envelope data : insertedEnvelope) { demoFileRTree.insert(data); } /* * At this step we can use search or remove action freely. * But if we want to save the RTree to re-open it later we MUST call close method * on RTree and TreeElementMapper to finish to write Tree informations. * * Be carefull not to open an RTree in reading if it has not been closed before. * * In this example we close RTree and TreeElementMapper to re-open it after. */ demoFileRTree.close(); demoFileTreeEltMapper.close(); /* * Re-open RTree. */ /* * First we open again TreeElementMapper. */ demoFileTreeEltMapper = new DemoFileTreeElementMapper(treeELTMapperFile, DEMO_CRS); /* * Then RTree. */ demoFileRTree = new FileStarRTree<Envelope>(treeFile.toPath(), demoFileTreeEltMapper); /* * At this step we can use search or remove action freely. * Moreover we can also add some data as below. */ insertedEnvelope = createData(5);// five new datas. System.out.println("RTree : before re-inserting datas : "+demoFileRTree.toString()); /* * Now insert data in RTree. */ for (Envelope data : insertedEnvelope) { demoFileRTree.insert(data); } System.out.println("RTree : after re-inserting datas : "+demoFileRTree.toString()); /* * Like previously if we want to save changes and re-open RTree afterwards we call close() methods. */ demoFileRTree.close(); demoFileTreeEltMapper.close(); } /** * Build some data to insert in RTree. * * @param dataNumber data number ask by user. * @return data table. */ private static Envelope[] createData(final int dataNumber) { final Envelope[] result = new Envelope[dataNumber]; for (int id = 0; id < dataNumber; id++) { final GeneralEnvelope genv = new GeneralEnvelope(DEMO_CRS); final double longCentroid = Math.random() * Math.random() * Math.random() * 360 - 180; final double latCentroid = Math.random() * Math.random() * Math.random() * 180 - 90; genv.setEnvelope(longCentroid, latCentroid, longCentroid, latCentroid); result[id] = genv; } return result; } }