/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-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.index.quadtree.fs; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.data.shapefile.shp.IndexFile; import org.geotools.index.quadtree.IndexStore; import org.geotools.index.quadtree.Node; import org.geotools.index.quadtree.QuadTree; import org.geotools.index.quadtree.StoreException; import com.vividsolutions.jts.geom.Envelope; /** * DOCUMENT ME! * * @author Tommaso Nolli * * @source $URL$ */ public class FileSystemIndexStore implements IndexStore { private static final Logger LOGGER = org.geotools.util.logging.Logging .getLogger("org.geotools.index.quadtree"); private File file; private byte byteOrder; /** * Constructor. The byte order defaults to NEW_MSB_ORDER * * @param file */ public FileSystemIndexStore(File file) { this.file = file; this.byteOrder = IndexHeader.NEW_MSB_ORDER; } /** * Constructor * * @param file * @param byteOrder */ public FileSystemIndexStore(File file, byte byteOrder) { this.file = file; this.byteOrder = byteOrder; } /** * @see org.geotools.index.quadtree.IndexStore#store(org.geotools.index.quadtree.QuadTree) */ public void store(QuadTree tree) throws StoreException { // For efficiency, trim the tree tree.trim(); // Open the stream... FileOutputStream fos = null; FileChannel channel = null; try { fos = new FileOutputStream(file); channel = fos.getChannel(); ByteBuffer buf = ByteBuffer.allocate(8); if (this.byteOrder > IndexHeader.NATIVE_ORDER) { LOGGER.finest("Writing file header"); IndexHeader header = new IndexHeader(byteOrder); header.writeTo(buf); buf.flip(); channel.write(buf); } ByteOrder order = byteToOrder(this.byteOrder); buf.clear(); buf.order(order); buf.putInt(tree.getNumShapes()); buf.putInt(tree.getMaxDepth()); buf.flip(); channel.write(buf); this.writeNode(tree.getRoot(), channel, order); } catch (IOException e) { throw new StoreException(e); } finally { try { channel.close(); } catch (Exception e) { } try { fos.close(); } catch (Exception e) { } } } /** * Wites a tree node to the qix file * * @param node * The node * @param channel * DOCUMENT ME! * @param order * byte order * * @throws IOException * @throws StoreException * DOCUMENT ME! */ private void writeNode(Node node, FileChannel channel, ByteOrder order) throws IOException, StoreException { int offset = this.getSubNodeOffset(node); ByteBuffer buf = ByteBuffer.allocate((4 * 8) + (3 * 4) + (node.getNumShapeIds() * 4)); buf.order(order); buf.putInt(offset); Envelope env = node.getBounds(); buf.putDouble(env.getMinX()); buf.putDouble(env.getMinY()); buf.putDouble(env.getMaxX()); buf.putDouble(env.getMaxY()); buf.putInt(node.getNumShapeIds()); for (int i = 0; i < node.getNumShapeIds(); i++) { buf.putInt(node.getShapeId(i)); } buf.putInt(node.getNumSubNodes()); buf.flip(); channel.write(buf); for (int i = 0; i < node.getNumSubNodes(); i++) { this.writeNode(node.getSubNode(i), channel, order); } } /** * Calculates the offset * * @param node * * * @throws StoreException * DOCUMENT ME! */ private int getSubNodeOffset(Node node) throws StoreException { int offset = 0; Node tmp = null; for (int i = 0; i < node.getNumSubNodes(); i++) { tmp = node.getSubNode(i); offset += (4 * 8); // Envelope size offset += ((tmp.getNumShapeIds() + 3) * 4); // Entries size + 3 offset += this.getSubNodeOffset(tmp); } return offset; } /** * Loads a quadrtee stored in a '.qix' file. <b>WARNING:</b> The resulting * quadtree will be immutable; if you perform an insert, an * <code>UnsupportedOperationException</code> will be thrown. * * @see org.geotools.index.quadtree.IndexStore#load() */ public QuadTree load(IndexFile indexfile, boolean useMemoryMapping) throws StoreException { QuadTree tree = null; try { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Opening QuadTree " + this.file.getCanonicalPath()); } final FileInputStream fis = new FileInputStream(file); final FileChannel channel = fis.getChannel(); IndexHeader header = new IndexHeader(channel); ByteOrder order = byteToOrder(header.getByteOrder()); ByteBuffer buf = ByteBuffer.allocate(8); buf.order(order); channel.read(buf); buf.flip(); tree = new QuadTree(buf.getInt(), buf.getInt(), indexfile) { public void insert(int recno, Envelope bounds) { throw new UnsupportedOperationException( "File quadtrees are immutable"); } public boolean trim() { return false; } public void close() throws StoreException { super.close(); try { channel.close(); fis.close(); } catch (IOException e) { throw new StoreException(e); } } }; tree.setRoot(FileSystemNode.readNode(0, null, channel, order, useMemoryMapping)); LOGGER.finest("QuadTree opened"); } catch (IOException e) { throw new StoreException(e); } return tree; } /** * DOCUMENT ME! * * @param order * */ private static ByteOrder byteToOrder(byte order) { ByteOrder ret = null; switch (order) { case IndexHeader.NATIVE_ORDER: ret = ByteOrder.nativeOrder(); break; case IndexHeader.LSB_ORDER: case IndexHeader.NEW_LSB_ORDER: ret = ByteOrder.LITTLE_ENDIAN; break; case IndexHeader.MSB_ORDER: case IndexHeader.NEW_MSB_ORDER: ret = ByteOrder.BIG_ENDIAN; break; } return ret; } /** * DOCUMENT ME! * * @return Returns the byteOrder. */ public int getByteOrder() { return this.byteOrder; } /** * DOCUMENT ME! * * @param byteOrder * The byteOrder to set. */ public void setByteOrder(byte byteOrder) { this.byteOrder = byteOrder; } }