/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo) * * 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; * version 2.1 of the License. * * 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 org.geotools.data.shapefile.indexed; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.util.logging.Logger; import org.geotools.data.shapefile.FileWriter; import org.geotools.data.shapefile.ShpFileType; import org.geotools.data.shapefile.ShpFiles; import org.geotools.data.shapefile.StorageFile; import org.geotools.data.shapefile.shp.IndexFile; import org.geotools.data.shapefile.shp.ShapefileHeader; import org.geotools.data.shapefile.shp.ShapefileReader; import org.geotools.data.shapefile.shp.ShapefileReader.Record; import org.geotools.index.LockTimeoutException; import org.geotools.index.TreeException; import org.geotools.index.quadtree.QuadTree; import org.geotools.index.quadtree.StoreException; import org.geotools.index.quadtree.fs.FileSystemIndexStore; import org.geotools.index.quadtree.fs.IndexHeader; import org.geotools.util.NullProgressListener; import org.geotools.util.logging.Logging; import org.opengis.util.ProgressListener; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.GeometryFactory; /** * Utility class for Shapefile spatial indexing * * @author Tommaso Nolli * @source $URL: * http://svn.geotools.org/geotools/trunk/gt/modules/plugin/shapefile/src/main/java/org/geotools/data/shapefile/indexed/ShapeFileIndexer.java $ */ public class ShapeFileIndexer implements FileWriter { private static final Logger LOGGER = Logging.getLogger(ShapeFileIndexer.class); private int max = -1; private String byteOrder; private boolean interactive = false; private ShpFiles shpFiles; public static void main(String[] args) throws IOException { if ((args.length < 1) || (((args.length - 1) % 2) != 0)) { usage(); } long start = System.currentTimeMillis(); ShapeFileIndexer idx = new ShapeFileIndexer(); idx.interactive = true; for (int i = 0; i < args.length; i++) { if (args[i].equals("-t")) { // idx.setIdxType(IndexType.valueOf(args[++i])); // just skip it for backwards compatibility i++; } else if (args[i].equals("-M")) { idx.setMax(Integer.parseInt(args[++i])); } else if (args[i].equals("-b")) { idx.setByteOrder(args[++i]); } else { if (!args[i].toLowerCase().endsWith(".shp")) { System.out.println("File extension must be '.shp'"); System.exit(1); } idx.setShapeFileName(new ShpFiles(args[i])); } } try { System.out.print("Indexing "); int cnt = idx.index(true, new NullProgressListener()); System.out.println(); System.out.print(cnt + " features indexed "); System.out.println("in " + (System.currentTimeMillis() - start) + "ms."); System.out.println(); } catch (Exception e) { e.printStackTrace(); usage(); System.exit(1); } } private static void usage() { System.out.println("Usage: ShapeFileIndexer " + "-t <QIX> " + "[-M <max entries per node>] " + "[-s <split algorithm>] " + "[-b <byte order NL | NM>] " + "<shape file>"); System.out.println(); System.out.println("Options:"); System.out.println("\t-t Index type: RTREE or QUADTREE"); System.out.println(); System.out.println("Following options apllies only to RTREE:"); System.out.println("\t-M maximum number of entries per node"); System.out.println("\t-m minimum number of entries per node"); System.out.println("\t-s split algorithm to use"); System.out.println(); System.out.println("Following options apllies only to QUADTREE:"); System.out.println("\t-b byte order to use: NL = LSB; " + "NM = MSB (default)"); System.exit(1); } /** * Index the shapefile denoted by setShapeFileName(String fileName) If when * a thread starts, another thread is indexing the same file, this thread * will wait that the first thread ends indexing; in this case <b>zero</b> * is reurned as result of the indexing process. * * @param verbose * enable/disable printing of dots every 500 indexed records * @param listener * DOCUMENT ME! * * @return The number of indexed records (or zero) * * @throws MalformedURLException * @throws IOException * @throws TreeException * @throws StoreException * DOCUMENT ME! * @throws LockTimeoutException */ public int index(boolean verbose, ProgressListener listener) throws MalformedURLException, IOException, TreeException, StoreException, LockTimeoutException { if (this.shpFiles == null) { throw new IOException("You have to set a shape file name!"); } int cnt = 0; ShapefileReader reader = null; // Temporary file for building... StorageFile storage = shpFiles.getStorageFile(ShpFileType.QIX); File treeFile = storage.getFile(); try { reader = new ShapefileReader(shpFiles, true, false, new GeometryFactory()); if(max == -1) { // compute a reasonable index max depth, considering a fully developed // 10 levels one already contains 200k index nodes, good for indexing up // to 3M features without consuming too much memory int features = reader.getCount(0); max = 1; int nodes = 1; while(nodes * 8 < features) { max++; nodes *= 4; } reader.close(); reader = new ShapefileReader(shpFiles, true, false, new GeometryFactory()); } cnt = this.buildQuadTree(reader, treeFile, verbose); } finally { if (reader != null) reader.close(); } // Final index file storage.replaceOriginal(); return cnt; } private int buildQuadTree(ShapefileReader reader, File file, boolean verbose) throws IOException, StoreException { LOGGER.fine("Building quadtree spatial index with depth " + max + " for file " + file.getAbsolutePath()); byte order = 0; if ((this.byteOrder == null) || this.byteOrder.equalsIgnoreCase("NM")) { order = IndexHeader.NEW_MSB_ORDER; } else if (this.byteOrder.equalsIgnoreCase("NL")) { order = IndexHeader.NEW_LSB_ORDER; } else { throw new StoreException("Asked byte order '" + this.byteOrder + "' must be 'NL' or 'NM'!"); } IndexFile shpIndex = new IndexFile(shpFiles, false); QuadTree tree = null; int cnt = 0; int numRecs = shpIndex.getRecordCount(); ShapefileHeader header = reader.getHeader(); Envelope bounds = new Envelope(header.minX(), header.maxX(), header .minY(), header.maxY()); tree = new QuadTree(numRecs, max, bounds, shpIndex); try { Record rec = null; while (reader.hasNext()) { rec = reader.nextRecord(); tree.insert(cnt++, new Envelope(rec.minX, rec.maxX, rec.minY, rec.maxY)); if (verbose && ((cnt % 1000) == 0)) { System.out.print('.'); } if (cnt % 100000 == 0) System.out.print('\n'); } if (verbose) System.out.println("done"); FileSystemIndexStore store = new FileSystemIndexStore(file, order); store.store(tree); } finally { tree.close(); } return cnt; } /** * For quad tree this is the max depth. I don't know what it is for RTree * * @param i */ public void setMax(int i) { max = i; } /** * DOCUMENT ME! * * @param shpFiles */ public void setShapeFileName(ShpFiles shpFiles) { this.shpFiles = shpFiles; } /** * DOCUMENT ME! * * @param byteOrder * The byteOrder to set. */ public void setByteOrder(String byteOrder) { this.byteOrder = byteOrder; } public String id() { return getClass().getName(); } }