/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.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.geotoolkit.data.shapefile.indexed;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Path;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.data.shapefile.lock.ShpFiles;
import org.geotoolkit.data.shapefile.lock.StorageFile;
import org.geotoolkit.data.shapefile.lock.AccessManager;
import org.geotoolkit.data.shapefile.shx.ShxReader;
import org.geotoolkit.data.shapefile.shp.ShapefileHeader;
import org.geotoolkit.data.shapefile.shp.ShapefileReader;
import org.geotoolkit.data.shapefile.shp.ShapefileReader.Record;
import org.geotoolkit.index.TreeException;
import org.geotoolkit.index.quadtree.DataReader;
import org.geotoolkit.index.quadtree.QuadTree;
import org.geotoolkit.index.quadtree.StoreException;
import org.geotoolkit.index.quadtree.fs.FileSystemIndexStore;
import org.geotoolkit.index.quadtree.fs.IndexHeader;
import org.geotoolkit.util.NullProgressListener;
import org.geotoolkit.process.ProgressController;
import com.vividsolutions.jts.geom.Envelope;
/**
* Utility class for Shapefile spatial indexing
*
* @author Tommaso Nolli
* @module
*/
public class ShapeFileIndexer {
private IndexType idxType;
private int max = 50;
private int min = 25;
private String byteOrder;
private ShpFiles shpFiles;
/**
* 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(final boolean verbose, final ProgressController listener)
throws MalformedURLException, IOException, TreeException,
StoreException {
if (this.shpFiles == null) {
throw new IOException("You have to set a shape file name!");
}
final AccessManager locker = shpFiles.createLocker();
int cnt = 0;
// Temporary file for building...
final StorageFile storage = locker.getStorageFile(this.idxType.shpFileType);
final Path treeFile = storage.getFile();
try (ShapefileReader reader = locker.getSHPReader(true, false, false, null)) {
switch (idxType) {
case QIX:
cnt = this.buildQuadTree(locker,reader, treeFile, verbose);
break;
default:
throw new IllegalArgumentException("NONE is not a legal index choice");
}
} catch(DataStoreException ex){
//do nothing
}
// Final index file
locker.disposeReaderAndWriters();
locker.replaceStorageFiles();
return cnt;
}
private int buildQuadTree(final AccessManager locker, final ShapefileReader reader,
final Path file, final boolean verbose)
throws IOException, StoreException {
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'!");
}
final ShxReader shpIndex = locker.getSHXReader(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());
DataReader dr = new IndexDataReader(shpIndex);
tree = new QuadTree(numRecs, max, bounds);
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(final int i) {
max = i;
}
/**
* DOCUMENT ME!
*
* @param i
*/
public void setMin(final int i) {
min = i;
}
/**
* DOCUMENT ME!
*
* @param shpFiles
*/
public void setShapeFileName(final ShpFiles shpFiles) {
this.shpFiles = shpFiles;
}
/**
* Sets the type of index to create
*
* @param indexType
* The idxType to set.
*/
public void setIdxType(final IndexType indexType) {
this.idxType = indexType;
}
/**
* DOCUMENT ME!
*
* @param byteOrder The byteOrder to set.
*/
public void setByteOrder(final String byteOrder) {
this.byteOrder = byteOrder;
}
public static void main(final String[] args) throws IOException {
if ((args.length < 1) || (((args.length - 1) % 2) != 0)) {
usage();
}
long start = System.currentTimeMillis();
ShapeFileIndexer idx = new ShapeFileIndexer();
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-t")) {
idx.setIdxType(IndexType.valueOf(args[++i]));
} else if (args[i].equals("-M")) {
idx.setMax(Integer.parseInt(args[++i]));
} else if (args[i].equals("-m")) {
idx.setMin(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 | GRX> "
+ "[-M <max entries per node>] "
+ "[-m <min 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);
}
}