/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2004-2008, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2010, Geomatys
*
* 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.index.quadtree.fs;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.util.logging.Level;
import org.geotoolkit.index.quadtree.AbstractNode;
import org.geotoolkit.index.quadtree.IndexStore;
import org.geotoolkit.index.quadtree.QuadTree;
import org.geotoolkit.index.quadtree.StoreException;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.WRITE;
import static org.geotoolkit.index.quadtree.fs.IndexHeader.*;
/**
* DOCUMENT ME!
*
* @author Tommaso Nolli
* @author Johann Sorel (Geomatys)
* @module
*/
public class FileSystemIndexStore implements IndexStore {
private final Path file;
private byte byteOrder;
/**
* Constructor. The byte order defaults to NEW_MSB_ORDER
*
* @param file
*/
@Deprecated
public FileSystemIndexStore(final File file) {
this(file.toPath(), NEW_MSB_ORDER);
}
public FileSystemIndexStore(final Path path) {
this(path, NEW_MSB_ORDER);
}
/**
* Constructor
*
* @param file
* @param byteOrder
*/
@Deprecated
public FileSystemIndexStore(final File file, final byte byteOrder) {
this(file.toPath(), byteOrder);
}
/**
* Constructor
*
* @param file
* @param byteOrder
*/
public FileSystemIndexStore(final Path file, final byte byteOrder) {
this.file = file;
this.byteOrder = byteOrder;
}
/**
* {@inheritDoc }
*/
@Override
public void store(final QuadTree tree) throws StoreException {
// For efficiency, trim the tree
tree.trim();
try (FileChannel channel = FileChannel.open(file, CREATE, WRITE)){
final ByteBuffer buf = ByteBuffer.allocate(8);
if (this.byteOrder > NATIVE_ORDER) {
QuadTree.LOGGER.finest("Writing file header");
final IndexHeader header = new IndexHeader(byteOrder);
header.writeTo(buf);
buf.flip();
channel.write(buf);
}
final 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, tree.getRoot(), channel, order);
} catch (IOException e) {
throw new StoreException(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(final QuadTree tree, final AbstractNode node, final FileChannel channel, final ByteOrder order)
throws IOException, StoreException {
final int offset = this.getSubNodeOffset(node);
final int numShapeIds = node.getNumShapeIds();
final int numSubNodes = node.getNumSubNodes();
final ByteBuffer buf = ByteBuffer.allocate((4 * 8) + (3 * 4) + (numShapeIds * 4));
buf.order(order);
buf.putInt(offset);
final double[] env = node.getEnvelope();
buf.putDouble(env[0]);
buf.putDouble(env[1]);
buf.putDouble(env[2]);
buf.putDouble(env[3]);
buf.putInt(numShapeIds);
for (int i=0; i<numShapeIds; i++) {
buf.putInt(node.getShapeId(i));
}
buf.putInt(numSubNodes);
buf.flip();
channel.write(buf);
for (int i=0; i<numSubNodes; i++) {
this.writeNode(tree, node.getSubNode(i), channel, order);
}
}
/**
* Calculates the offset
*
* @param node
*
*
* @throws StoreException
* DOCUMENT ME!
*/
private int getSubNodeOffset(final AbstractNode node) throws StoreException {
int offset = 0;
for (int i=0,n=node.getNumSubNodes(); i<n; i++) {
final AbstractNode 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 quadtree 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 IndexStore#load(org.geotoolkit.data.shapefile.shp.IndexFile)
*/
@Override
public QuadTree load() throws StoreException {
QuadTree tree = null;
try {
if (QuadTree.LOGGER.isLoggable(Level.FINEST)) {
QuadTree.LOGGER.log(Level.FINEST, "Opening QuadTree {0}", this.file);
}
tree = FileSystemQuadTree.load(file);
QuadTree.LOGGER.finest("QuadTree opened");
} catch (IOException e) {
throw new StoreException(e);
}
return tree;
}
static FileSystemNode readNode(final FileChannel channel, final ByteOrder order) throws IOException {
return readNode(new ScrollingBuffer(channel, order));
}
static FileSystemNode readNode(final ScrollingBuffer buf)
throws IOException {
// offset(4) + envelope(32) + nbIds(4)
buf.refillBuffer(40);
// offset
final int offset = buf.original.getInt();
// envelope
final double x1 = buf.original.getDouble();
final double y1 = buf.original.getDouble();
final double x2 = buf.original.getDouble();
final double y2 = buf.original.getDouble();
// shapes in this node
final int numShapesId = buf.original.getInt();
final int[] ids = new int[numShapesId];
buf.getIntArray(ids);
final int numSubNodes = buf.getInt();
// let's create the new node
final FileSystemNode node = new FileSystemNode(
x1,y1,x2,y2, buf,(int)buf.getPosition(),offset);
node.setShapesId(ids);
node.setNumSubNodes(numSubNodes);
return node;
}
}