/* * 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.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.nio.channels.FileChannel; import org.geotools.index.quadtree.Node; import org.geotools.index.quadtree.StoreException; import com.vividsolutions.jts.geom.Envelope; /** * DOCUMENT ME! * * @author Tommaso Nolli * @source $URL: * http://svn.geotools.org/geotools/trunk/gt/modules/plugin/shapefile/src/main/java/org/geotools/index/quadtree/fs/FileSystemNode.java $ */ public class FileSystemNode extends Node { private ScrollingBuffer buffer; private ByteOrder order; private int subNodeStartByte; private int subNodesLength; private int numSubNodes; /** * DOCUMENT ME! * * @param bounds * @param channel * DOCUMENT ME! * @param order * DOCUMENT ME! * @param startByte * DOCUMENT ME! * @param subNodesLength * DOCUMENT ME! */ FileSystemNode(Envelope bounds, int id, ScrollingBuffer buffer, int startByte, int subNodesLength) { super(bounds, id); this.buffer = buffer; this.subNodeStartByte = startByte; this.subNodesLength = subNodesLength; } public Node copy() throws IOException { FileSystemNode copy = new FileSystemNode(getBounds(), id, buffer, subNodeStartByte, subNodesLength); copy.numShapesId = numShapesId; copy.shapesId = new int[numShapesId]; System.arraycopy(shapesId, 0, copy.shapesId, 0, numShapesId); copy.numSubNodes = numSubNodes; return copy; } /** * DOCUMENT ME! * * @return Returns the numSubNodes. */ public int getNumSubNodes() { return this.numSubNodes; } /** * DOCUMENT ME! * * @param numSubNodes * The numSubNodes to set. */ public void setNumSubNodes(int numSubNodes) { this.numSubNodes = numSubNodes; } /** * DOCUMENT ME! * * @return Returns the subNodeStartByte. */ public int getSubNodeStartByte() { return this.subNodeStartByte; } /** * DOCUMENT ME! * * @return Returns the subNodesLength. */ public int getSubNodesLength() { return this.subNodesLength; } /** * @see org.geotools.index.quadtree.Node#getSubNode(int) */ public Node getSubNode(int pos) throws StoreException { if (this.subNodes.size() > pos) { return super.getSubNode(pos); } try { FileSystemNode subNode = null; // Getting prec subNode... int offset = this.subNodeStartByte; if (pos > 0) { subNode = (FileSystemNode) getSubNode(pos - 1); offset = subNode.getSubNodeStartByte() + subNode.getSubNodesLength(); } buffer.goTo(offset); for (int i = 0, ii = subNodes.size(); i < ((pos + 1) - ii); i++) { subNode = readNode(pos, this, buffer); this.addSubNode(subNode); } } catch (IOException e) { throw new StoreException(e); } return super.getSubNode(pos); } /** * DOCUMENT ME! * * @param channel * @param order * DOCUMENT ME! * * * @throws IOException */ public static FileSystemNode readNode(int id, Node parent, FileChannel channel, ByteOrder order) throws IOException { ScrollingBuffer buffer = new ScrollingBuffer(channel, order); return readNode(id, parent, buffer); } static FileSystemNode readNode(int id, Node parent, ScrollingBuffer buf) throws IOException { // offset int offset = buf.getInt(); double x1; double y1; double x2; double y2; // envelope x1 = buf.getDouble(); y1 = buf.getDouble(); x2 = buf.getDouble(); y2 = buf.getDouble(); Envelope env = new Envelope(x1, x2, y1, y2); // shapes in this node int numShapesId = buf.getInt(); int[] ids = new int[numShapesId]; buf.getIntArray(ids); int numSubNodes = buf.getInt(); // let's create the new node FileSystemNode node = new FileSystemNode(env, id, buf, (int) buf.getPosition(), offset); node.setShapesId(ids); node.setNumSubNodes(numSubNodes); return node; } /** * A utility class to access file contents by using a single scrolling * buffer reading file contents with a minimum of 8kb per access */ private static class ScrollingBuffer { FileChannel channel; ByteOrder order; ByteBuffer buffer; /** the initial position of the buffer in the channel */ long bufferStart; public ScrollingBuffer(FileChannel channel, ByteOrder order) throws IOException { this.channel = channel; this.order = order; this.bufferStart = channel.position(); // start with an 8kb buffer this.buffer = ByteBuffer.allocateDirect(8 * 1024); this.buffer.order(order); channel.read(buffer); buffer.flip(); } public int getInt() throws IOException { if (buffer.remaining() < 4) refillBuffer(4); return buffer.getInt(); } public double getDouble() throws IOException { if (buffer.remaining() < 8) refillBuffer(8); return buffer.getDouble(); } public void getIntArray(int[] array) throws IOException { int size = array.length * 4; if (buffer.remaining() < size) refillBuffer(size); // read the array using a view IntBuffer intView = buffer.asIntBuffer(); intView.limit(array.length); intView.get(array); // don't forget to update the original buffer position, since the // view is independent buffer.position(buffer.position() + size); } /** * * @param requiredSize * @throws IOException */ void refillBuffer(int requiredSize) throws IOException { // compute the actual position up to we have read something long currentPosition = bufferStart + buffer.position(); // if the buffer is not big enough enlarge it if (buffer.capacity() < requiredSize) { int size = buffer.capacity(); while (size < requiredSize) size *= 2; buffer = ByteBuffer.allocateDirect(size); buffer.order(order); } readBuffer(currentPosition); } private void readBuffer(long currentPosition) throws IOException { channel.position(currentPosition); buffer.clear(); channel.read(buffer); buffer.flip(); bufferStart = currentPosition; } /** * Jumps the buffer to the specified position in the file * * @param newPosition * @throws IOException */ public void goTo(long newPosition) throws IOException { // if the new position is already in the buffer, just move the // buffer position // otherwise we have to reload it if (newPosition >= bufferStart && newPosition <= bufferStart + buffer.limit()) { buffer.position((int) (newPosition - bufferStart)); } else { readBuffer(newPosition); } } /** * Returns the absolute position of the next byte that will be read * * @return */ public long getPosition() { return bufferStart + buffer.position(); } } }