// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.pgsimple.common; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import org.openstreetmap.osmosis.core.OsmosisRuntimeException; import org.openstreetmap.osmosis.core.store.BufferedRandomAccessFileInputStream; import org.openstreetmap.osmosis.core.store.StorageStage; import org.openstreetmap.osmosis.core.util.FixedPrecisionCoordinateConvertor; /** * A file-based node location store implementation. * * @author Brett Henderson */ public class PersistentNodeLocationStore implements NodeLocationStore { private static final Logger LOG = Logger.getLogger(PersistentNodeLocationStore.class.getName()); private static final int ZERO_BUFFER_SIZE = 1024 * 1024; private static final int NODE_DATA_SIZE = 9; private File nodeStorageFile; private StorageStage stage; private long lastNodeId; private FileOutputStream fileOutStream; private DataOutputStream dataOutStream; private BufferedRandomAccessFileInputStream fileInStream; private DataInputStream dataInStream; private long currentFileOffset; private byte[] zeroBuffer; private NodeLocation invalidNodeLocation; /** * Creates a new instance. */ public PersistentNodeLocationStore() { stage = StorageStage.NotStarted; lastNodeId = Long.MIN_VALUE; zeroBuffer = new byte[ZERO_BUFFER_SIZE]; Arrays.fill(zeroBuffer, (byte) 0); invalidNodeLocation = new NodeLocation(); } private void initializeAddStage() { // We can't add if we've passed the add stage. if (stage.compareTo(StorageStage.Add) > 0) { throw new OsmosisRuntimeException("Cannot add to storage in stage " + stage + "."); } // If we're not up to the add stage, initialise for adding. if (stage.compareTo(StorageStage.Add) < 0) { try { nodeStorageFile = File.createTempFile("nodelatlon", null); fileOutStream = new FileOutputStream(nodeStorageFile); dataOutStream = new DataOutputStream(new BufferedOutputStream(fileOutStream, 65536)); currentFileOffset = 0; stage = StorageStage.Add; } catch (IOException e) { throw new OsmosisRuntimeException( "Unable to create object stream writing to temporary file " + nodeStorageFile + ".", e); } } } private void initializeReadingStage() { // If we're already in the reading stage, do nothing. if (stage.compareTo(StorageStage.Reading) == 0) { return; } // If we've been released, we can't iterate. if (stage.compareTo(StorageStage.Released) >= 0) { throw new OsmosisRuntimeException("Cannot read from node storage in stage " + stage + "."); } // If no data was written, writing should be initialized before reading. if (stage.compareTo(StorageStage.NotStarted) <= 0) { initializeAddStage(); } // If we're in the add stage, close the output streams. if (stage.compareTo(StorageStage.Add) == 0) { try { dataOutStream.close(); fileOutStream.close(); } catch (IOException e) { throw new OsmosisRuntimeException("Unable to close output stream.", e); } finally { dataOutStream = null; fileOutStream = null; } try { fileInStream = new BufferedRandomAccessFileInputStream(nodeStorageFile); dataInStream = new DataInputStream(fileInStream); } catch (IOException e) { throw new OsmosisRuntimeException("Unable to open the node data file " + nodeStorageFile + ".", e); } stage = StorageStage.Reading; } } /** * {@inheritDoc} */ @Override public void addLocation(long nodeId, NodeLocation nodeLocation) { long requiredFileOffset; initializeAddStage(); // We can only add nodes in sorted order. if (nodeId <= lastNodeId) { throw new OsmosisRuntimeException( "The node id of " + nodeId + " must be greater than the previous id of " + lastNodeId + "." ); } lastNodeId = nodeId; try { // Write zeros to the file where no node data is available. requiredFileOffset = nodeId * NODE_DATA_SIZE; if (requiredFileOffset > currentFileOffset) { while (currentFileOffset < requiredFileOffset) { long offsetDifference; offsetDifference = requiredFileOffset - currentFileOffset; if (offsetDifference > ZERO_BUFFER_SIZE) { offsetDifference = ZERO_BUFFER_SIZE; } dataOutStream.write(zeroBuffer, 0, (int) offsetDifference); currentFileOffset += offsetDifference; } } // Write the node data. Prefix with a non-zero byte to identify that // data is available for this node. dataOutStream.writeByte(1); dataOutStream.writeInt(FixedPrecisionCoordinateConvertor.convertToFixed(nodeLocation.getLongitude())); dataOutStream.writeInt(FixedPrecisionCoordinateConvertor.convertToFixed(nodeLocation.getLatitude())); currentFileOffset += NODE_DATA_SIZE; } catch (IOException e) { throw new OsmosisRuntimeException( "Unable to write node location data to node storage file " + nodeStorageFile + ".", e ); } } /** * {@inheritDoc} */ @Override public NodeLocation getNodeLocation(long nodeId) { NodeLocation nodeLocation; long offset; initializeReadingStage(); offset = nodeId * NODE_DATA_SIZE; nodeLocation = invalidNodeLocation; if (offset < currentFileOffset) { try { byte validFlag; fileInStream.seek(offset); validFlag = dataInStream.readByte(); if (validFlag != 0) { nodeLocation = new NodeLocation( FixedPrecisionCoordinateConvertor.convertToDouble(dataInStream.readInt()), FixedPrecisionCoordinateConvertor.convertToDouble(dataInStream.readInt()) ); } } catch (IOException e) { throw new OsmosisRuntimeException("Unable to read node information from the node storage file.", e); } } return nodeLocation; } /** * {@inheritDoc} */ @Override public void close() { if (fileOutStream != null) { try { fileOutStream.close(); } catch (Exception e) { // We cannot throw an exception within a release method. LOG.log(Level.WARNING, "Unable to close file output stream.", e); } fileOutStream = null; } if (fileInStream != null) { try { fileInStream.close(); } catch (Exception e) { // We cannot throw an exception within a release method. LOG.log(Level.WARNING, "Unable to close file input stream.", e); } fileInStream = null; } if (nodeStorageFile != null) { if (!nodeStorageFile.delete()) { // We cannot throw an exception within a release method. LOG.warning("Unable to delete file " + nodeStorageFile); } nodeStorageFile = null; } } }