/* * Licensed to GraphHopper GmbH under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. * * GraphHopper GmbH licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.graphhopper.storage; import com.graphhopper.coll.GHBitSet; import com.graphhopper.coll.GHBitSetImpl; import com.graphhopper.coll.SparseIntIntArray; import com.graphhopper.routing.util.AllEdgesIterator; import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.FlagEncoder; import com.graphhopper.search.NameIndex; import com.graphhopper.util.*; import com.graphhopper.util.shapes.BBox; import static com.graphhopper.util.Helper.nf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The base graph handles nodes and edges file format. It can be used with different Directory * implementations like RAMDirectory for fast access or via MMapDirectory for virtual-memory and not * thread safe usage. * <p> * Note: A RAM DataAccess Object is thread-safe in itself but if used in this Graph implementation * it is not write thread safe. * <p> * Life cycle: (1) object creation, (2) configuration via setters & getters, (3) create or * loadExisting, (4) usage, (5) flush, (6) close */ class BaseGraph implements Graph { final DataAccess edges; final DataAccess nodes; final BBox bounds; final NodeAccess nodeAccess; final GraphExtension extStorage; final NameIndex nameIndex; final BitUtil bitUtil; final EncodingManager encodingManager; final EdgeAccess edgeAccess; // length | nodeA | nextNode | ... | nodeB // as we use integer index in 'egdes' area => 'geometry' area is limited to 4GB (we use pos&neg values!) private final DataAccess wayGeometry; private final Directory dir; private final InternalGraphEventListener listener; /** * interval [0,n) */ protected int edgeCount; // node memory layout: protected int N_EDGE_REF, N_LAT, N_LON, N_ELE, N_ADDITIONAL; // edge memory layout not found in EdgeAccess: int E_GEO, E_NAME, E_ADDITIONAL; /** * Specifies how many entries (integers) are used per edge. */ int edgeEntryBytes; /** * Specifies how many entries (integers) are used per node */ int nodeEntryBytes; private boolean initialized = false; /** * interval [0,n) */ private int nodeCount; // remove markers are not yet persistent! private GHBitSet removedNodes; private int edgeEntryIndex, nodeEntryIndex; private long maxGeoRef; private boolean frozen = false; public BaseGraph(Directory dir, final EncodingManager encodingManager, boolean withElevation, InternalGraphEventListener listener, GraphExtension extendedStorage) { this.dir = dir; this.encodingManager = encodingManager; this.bitUtil = BitUtil.get(dir.getByteOrder()); this.wayGeometry = dir.find("geometry"); this.nameIndex = new NameIndex(dir); this.nodes = dir.find("nodes"); this.edges = dir.find("edges"); this.listener = listener; this.edgeAccess = new EdgeAccess(edges, bitUtil) { @Override final EdgeIterable createSingleEdge(EdgeFilter filter) { return new EdgeIterable(BaseGraph.this, this, filter); } @Override final int getEdgeRef(int nodeId) { return nodes.getInt((long) nodeId * nodeEntryBytes + N_EDGE_REF); } @Override final void setEdgeRef(int nodeId, int edgeId) { nodes.setInt((long) nodeId * nodeEntryBytes + N_EDGE_REF, edgeId); } @Override final int getEntryBytes() { return edgeEntryBytes; } @Override final long toPointer(int edgeId) { assert isInBounds(edgeId) : "edgeId " + edgeId + " not in bounds [0," + edgeCount + ")"; return (long) edgeId * edgeEntryBytes; } @Override final boolean isInBounds(int edgeId) { return edgeId < edgeCount && edgeId >= 0; } @Override final long reverseFlags(long edgePointer, long flags) { return encodingManager.reverseFlags(flags); } @Override public String toString() { return "base edge access"; } }; this.bounds = BBox.createInverse(withElevation); this.nodeAccess = new GHNodeAccess(this, withElevation); this.extStorage = extendedStorage; this.extStorage.init(this, dir); } private static boolean isTestingEnabled() { boolean enableIfAssert = false; assert (enableIfAssert = true) : true; return enableIfAssert; } @Override public Graph getBaseGraph() { return this; } void checkInit() { if (initialized) throw new IllegalStateException("You cannot configure this GraphStorage " + "after calling create or loadExisting. Calling one of the methods twice is also not allowed."); } protected int loadNodesHeader() { nodeEntryBytes = nodes.getHeader(1 * 4); nodeCount = nodes.getHeader(2 * 4); bounds.minLon = Helper.intToDegree(nodes.getHeader(3 * 4)); bounds.maxLon = Helper.intToDegree(nodes.getHeader(4 * 4)); bounds.minLat = Helper.intToDegree(nodes.getHeader(5 * 4)); bounds.maxLat = Helper.intToDegree(nodes.getHeader(6 * 4)); if (bounds.hasElevation()) { bounds.minEle = Helper.intToEle(nodes.getHeader(7 * 4)); bounds.maxEle = Helper.intToEle(nodes.getHeader(8 * 4)); } frozen = nodes.getHeader(9 * 4) == 1; return 10; } protected int setNodesHeader() { nodes.setHeader(1 * 4, nodeEntryBytes); nodes.setHeader(2 * 4, nodeCount); nodes.setHeader(3 * 4, Helper.degreeToInt(bounds.minLon)); nodes.setHeader(4 * 4, Helper.degreeToInt(bounds.maxLon)); nodes.setHeader(5 * 4, Helper.degreeToInt(bounds.minLat)); nodes.setHeader(6 * 4, Helper.degreeToInt(bounds.maxLat)); if (bounds.hasElevation()) { nodes.setHeader(7 * 4, Helper.eleToInt(bounds.minEle)); nodes.setHeader(8 * 4, Helper.eleToInt(bounds.maxEle)); } nodes.setHeader(9 * 4, isFrozen() ? 1 : 0); return 10; } protected int loadEdgesHeader() { edgeEntryBytes = edges.getHeader(0 * 4); edgeCount = edges.getHeader(1 * 4); return 5; } protected int setEdgesHeader() { edges.setHeader(0, edgeEntryBytes); edges.setHeader(1 * 4, edgeCount); edges.setHeader(2 * 4, encodingManager.hashCode()); edges.setHeader(3 * 4, extStorage.hashCode()); return 5; } protected int loadWayGeometryHeader() { maxGeoRef = bitUtil.combineIntsToLong(wayGeometry.getHeader(0), wayGeometry.getHeader(4)); return 1; } protected int setWayGeometryHeader() { wayGeometry.setHeader(0, bitUtil.getIntLow(maxGeoRef)); wayGeometry.setHeader(4, bitUtil.getIntHigh(maxGeoRef)); return 1; } void initStorage() { edgeEntryIndex = 0; nodeEntryIndex = 0; boolean flagsSizeIsLong = encodingManager.getBytesForFlags() == 8; edgeAccess.init(nextEdgeEntryIndex(4), nextEdgeEntryIndex(4), nextEdgeEntryIndex(4), nextEdgeEntryIndex(4), nextEdgeEntryIndex(4), nextEdgeEntryIndex(encodingManager.getBytesForFlags()), flagsSizeIsLong); E_GEO = nextEdgeEntryIndex(4); E_NAME = nextEdgeEntryIndex(4); if (extStorage.isRequireEdgeField()) E_ADDITIONAL = nextEdgeEntryIndex(4); else E_ADDITIONAL = -1; N_EDGE_REF = nextNodeEntryIndex(4); N_LAT = nextNodeEntryIndex(4); N_LON = nextNodeEntryIndex(4); if (nodeAccess.is3D()) N_ELE = nextNodeEntryIndex(4); else N_ELE = -1; if (extStorage.isRequireNodeField()) N_ADDITIONAL = nextNodeEntryIndex(4); else N_ADDITIONAL = -1; initNodeAndEdgeEntrySize(); listener.initStorage(); initialized = true; } /** * Initializes the node area with the empty edge value and default additional value. */ void initNodeRefs(long oldCapacity, long newCapacity) { for (long pointer = oldCapacity + N_EDGE_REF; pointer < newCapacity; pointer += nodeEntryBytes) { nodes.setInt(pointer, EdgeIterator.NO_EDGE); } if (extStorage.isRequireNodeField()) { for (long pointer = oldCapacity + N_ADDITIONAL; pointer < newCapacity; pointer += nodeEntryBytes) { nodes.setInt(pointer, extStorage.getDefaultNodeFieldValue()); } } } protected final int nextEdgeEntryIndex(int sizeInBytes) { int tmp = edgeEntryIndex; edgeEntryIndex += sizeInBytes; return tmp; } protected final int nextNodeEntryIndex(int sizeInBytes) { int tmp = nodeEntryIndex; nodeEntryIndex += sizeInBytes; return tmp; } protected final void initNodeAndEdgeEntrySize() { nodeEntryBytes = nodeEntryIndex; edgeEntryBytes = edgeEntryIndex; } /** * Check if byte capacity of DataAcess nodes object is sufficient to include node index, else * extend byte capacity */ final void ensureNodeIndex(int nodeIndex) { if (!initialized) throw new AssertionError("The graph has not yet been initialized."); if (nodeIndex < nodeCount) return; long oldNodes = nodeCount; nodeCount = nodeIndex + 1; boolean capacityIncreased = nodes.ensureCapacity((long) nodeCount * nodeEntryBytes); if (capacityIncreased) { long newBytesCapacity = nodes.getCapacity(); initNodeRefs(oldNodes * nodeEntryBytes, newBytesCapacity); } } @Override public int getNodes() { return nodeCount; } @Override public NodeAccess getNodeAccess() { return nodeAccess; } @Override public BBox getBounds() { return bounds; } @Override public EdgeIteratorState edge(int a, int b, double distance, boolean bothDirection) { return edge(a, b).setDistance(distance).setFlags(encodingManager.flagsDefault(true, bothDirection)); } void setSegmentSize(int bytes) { checkInit(); nodes.setSegmentSize(bytes); edges.setSegmentSize(bytes); wayGeometry.setSegmentSize(bytes); nameIndex.setSegmentSize(bytes); extStorage.setSegmentSize(bytes); } synchronized void freeze() { if (isFrozen()) throw new IllegalStateException("base graph already frozen"); frozen = true; listener.freeze(); } synchronized boolean isFrozen() { return frozen; } public void checkFreeze() { if (isFrozen()) throw new IllegalStateException("Cannot add edge or node after baseGraph.freeze was called"); } void create(long initSize) { nodes.create(initSize); edges.create(initSize); initSize = Math.min(initSize, 2000); wayGeometry.create(initSize); nameIndex.create(initSize); extStorage.create(initSize); initStorage(); // 0 stands for no separate geoRef maxGeoRef = 4; initNodeRefs(0, nodes.getCapacity()); } String toDetailsString() { return "edges:" + nf(edgeCount) + "(" + edges.getCapacity() / Helper.MB + "MB), " + "nodes:" + nf(getNodes()) + "(" + nodes.getCapacity() / Helper.MB + "MB), " + "name:(" + nameIndex.getCapacity() / Helper.MB + "MB), " + "geo:" + nf(maxGeoRef) + "(" + wayGeometry.getCapacity() / Helper.MB + "MB), " + "bounds:" + bounds; } void flush() { setNodesHeader(); setEdgesHeader(); setWayGeometryHeader(); wayGeometry.flush(); nameIndex.flush(); edges.flush(); nodes.flush(); extStorage.flush(); } void close() { wayGeometry.close(); nameIndex.close(); edges.close(); nodes.close(); extStorage.close(); } long getCapacity() { return edges.getCapacity() + nodes.getCapacity() + nameIndex.getCapacity() + wayGeometry.getCapacity() + extStorage.getCapacity(); } long getMaxGeoRef() { return maxGeoRef; } void loadExisting(String dim) { if (!nodes.loadExisting()) throw new IllegalStateException("Cannot load nodes. corrupt file or directory? " + dir); if (!dim.equalsIgnoreCase("" + nodeAccess.getDimension())) throw new IllegalStateException("Configured dimension (" + nodeAccess.getDimension() + ") is not equal " + "to dimension of loaded graph (" + dim + ")"); if (!edges.loadExisting()) throw new IllegalStateException("Cannot load edges. corrupt file or directory? " + dir); if (!wayGeometry.loadExisting()) throw new IllegalStateException("Cannot load geometry. corrupt file or directory? " + dir); if (!nameIndex.loadExisting()) throw new IllegalStateException("Cannot load name index. corrupt file or directory? " + dir); if (!extStorage.loadExisting()) throw new IllegalStateException("Cannot load extended storage. corrupt file or directory? " + dir); // first define header indices of this storage initStorage(); // now load some properties from stored data loadNodesHeader(); loadEdgesHeader(); loadWayGeometryHeader(); } /** * @return to */ EdgeIteratorState copyProperties(CommonEdgeIterator from, EdgeIteratorState to) { to.setDistance(from.getDistance()). setName(from.getName()). setFlags(from.getDirectFlags()). setWayGeometry(from.fetchWayGeometry(0)); if (E_ADDITIONAL >= 0) to.setAdditionalField(from.getAdditionalField()); return to; } /** * Create edge between nodes a and b * <p> * * @return EdgeIteratorState of newly created edge */ @Override public EdgeIteratorState edge(int nodeA, int nodeB) { if (isFrozen()) throw new IllegalStateException("Cannot create edge if graph is already frozen"); ensureNodeIndex(Math.max(nodeA, nodeB)); int edgeId = edgeAccess.internalEdgeAdd(nextEdgeId(), nodeA, nodeB); EdgeIterable iter = new EdgeIterable(this, edgeAccess, EdgeFilter.ALL_EDGES); boolean ret = iter.init(edgeId, nodeB); assert ret; if (extStorage.isRequireEdgeField()) iter.setAdditionalField(extStorage.getDefaultEdgeFieldValue()); return iter; } // for test only void setEdgeCount(int cnt) { edgeCount = cnt; } /** * Determine next free edgeId and ensure byte capacity to store edge * <p> * * @return next free edgeId */ protected int nextEdgeId() { int nextEdge = edgeCount; edgeCount++; if (edgeCount < 0) throw new IllegalStateException("too many edges. new edge id would be negative. " + toString()); edges.ensureCapacity(((long) edgeCount + 1) * edgeEntryBytes); return nextEdge; } @Override public EdgeIteratorState getEdgeIteratorState(int edgeId, int adjNode) { if (!edgeAccess.isInBounds(edgeId)) throw new IllegalStateException("edgeId " + edgeId + " out of bounds"); checkAdjNodeBounds(adjNode); return edgeAccess.getEdgeProps(edgeId, adjNode); } final void checkAdjNodeBounds(int adjNode) { if (adjNode < 0 && adjNode != Integer.MIN_VALUE || adjNode >= nodeCount) throw new IllegalStateException("adjNode " + adjNode + " out of bounds [0," + nf(nodeCount) + ")"); } @Override public EdgeExplorer createEdgeExplorer(EdgeFilter filter) { return new EdgeIterable(this, edgeAccess, filter); } @Override public EdgeExplorer createEdgeExplorer() { return createEdgeExplorer(EdgeFilter.ALL_EDGES); } @Override public AllEdgesIterator getAllEdges() { return new AllEdgeIterator(this, edgeAccess); } @Override public Graph copyTo(Graph g) { initialized = true; if (g.getClass().equals(getClass())) { _copyTo((BaseGraph) g); return g; } else { return GHUtility.copyTo(this, g); } } void _copyTo(BaseGraph clonedG) { if (clonedG.edgeEntryBytes != edgeEntryBytes) throw new IllegalStateException("edgeEntryBytes cannot be different for cloned graph. " + "Cloned: " + clonedG.edgeEntryBytes + " vs " + edgeEntryBytes); if (clonedG.nodeEntryBytes != nodeEntryBytes) throw new IllegalStateException("nodeEntryBytes cannot be different for cloned graph. " + "Cloned: " + clonedG.nodeEntryBytes + " vs " + nodeEntryBytes); if (clonedG.nodeAccess.getDimension() != nodeAccess.getDimension()) throw new IllegalStateException("dimension cannot be different for cloned graph. " + "Cloned: " + clonedG.nodeAccess.getDimension() + " vs " + nodeAccess.getDimension()); // nodes setNodesHeader(); nodes.copyTo(clonedG.nodes); clonedG.loadNodesHeader(); // edges setEdgesHeader(); edges.copyTo(clonedG.edges); clonedG.loadEdgesHeader(); // name nameIndex.copyTo(clonedG.nameIndex); // geometry setWayGeometryHeader(); wayGeometry.copyTo(clonedG.wayGeometry); clonedG.loadWayGeometryHeader(); // extStorage extStorage.copyTo(clonedG.extStorage); if (removedNodes == null) clonedG.removedNodes = null; else clonedG.removedNodes = removedNodes.copyTo(new GHBitSetImpl()); } protected void trimToSize() { long nodeCap = (long) nodeCount * nodeEntryBytes; nodes.trimTo(nodeCap); // long edgeCap = (long) (edgeCount + 1) * edgeEntrySize; // edges.trimTo(edgeCap * 4); } /** * This methods disconnects all edges from removed nodes. It does no edge compaction. Then it * moves the last nodes into the deleted nodes, where it needs to update the node ids in every * edge. */ void inPlaceNodeRemove(int removeNodeCount) { // Prepare edge-update of nodes which are connected to deleted nodes int toMoveNodes = getNodes(); int itemsToMove = 0; // sorted map when we access it via keyAt and valueAt - see below! final SparseIntIntArray oldToNewMap = new SparseIntIntArray(removeNodeCount); GHBitSet toRemoveSet = new GHBitSetImpl(removeNodeCount); removedNodes.copyTo(toRemoveSet); Logger logger = LoggerFactory.getLogger(getClass()); if (removeNodeCount > getNodes() / 2.0) logger.warn("More than a half of the network should be removed!? " + "Nodes:" + getNodes() + ", remove:" + removeNodeCount); EdgeExplorer delExplorer = createEdgeExplorer(); // create map of old node ids pointing to new ids for (int removeNode = removedNodes.next(0); removeNode >= 0; removeNode = removedNodes.next(removeNode + 1)) { EdgeIterator delEdgesIter = delExplorer.setBaseNode(removeNode); while (delEdgesIter.next()) { toRemoveSet.add(delEdgesIter.getAdjNode()); } toMoveNodes--; for (; toMoveNodes >= 0; toMoveNodes--) { if (!removedNodes.contains(toMoveNodes)) break; } if (toMoveNodes >= removeNode) oldToNewMap.put(toMoveNodes, removeNode); itemsToMove++; } EdgeIterable adjNodesToDelIter = (EdgeIterable) createEdgeExplorer(); // now similar process to disconnectEdges but only for specific nodes // all deleted nodes could be connected to existing. remove the connections for (int removeNode = toRemoveSet.next(0); removeNode >= 0; removeNode = toRemoveSet.next(removeNode + 1)) { // remove all edges connected to the deleted nodes adjNodesToDelIter.setBaseNode(removeNode); long prev = EdgeIterator.NO_EDGE; while (adjNodesToDelIter.next()) { int nodeId = adjNodesToDelIter.getAdjNode(); // already invalidated if (nodeId != EdgeAccess.NO_NODE && removedNodes.contains(nodeId)) { int edgeToRemove = adjNodesToDelIter.getEdge(); long edgeToRemovePointer = edgeAccess.toPointer(edgeToRemove); edgeAccess.internalEdgeDisconnect(edgeToRemove, prev, removeNode, nodeId); edgeAccess.invalidateEdge(edgeToRemovePointer); } else { prev = adjNodesToDelIter.edgePointer; } } } GHBitSet toMoveSet = new GHBitSetImpl(removeNodeCount * 3); EdgeExplorer movedEdgeExplorer = createEdgeExplorer(); // marks connected nodes to rewrite the edges for (int i = 0; i < itemsToMove; i++) { int oldI = oldToNewMap.keyAt(i); EdgeIterator movedEdgeIter = movedEdgeExplorer.setBaseNode(oldI); while (movedEdgeIter.next()) { int nodeId = movedEdgeIter.getAdjNode(); if (nodeId == EdgeAccess.NO_NODE) continue; if (removedNodes.contains(nodeId)) throw new IllegalStateException("shouldn't happen the edge to the node " + nodeId + " should be already deleted. " + oldI); toMoveSet.add(nodeId); } } // move nodes into deleted nodes for (int i = 0; i < itemsToMove; i++) { int oldI = oldToNewMap.keyAt(i); int newI = oldToNewMap.valueAt(i); long newOffset = (long) newI * nodeEntryBytes; long oldOffset = (long) oldI * nodeEntryBytes; for (long j = 0; j < nodeEntryBytes; j += 4) { nodes.setInt(newOffset + j, nodes.getInt(oldOffset + j)); } } // *rewrites* all edges connected to moved nodes // go through all edges and pick the necessary <- this is easier to implement than // a more efficient (?) breadth-first search EdgeIterator iter = getAllEdges(); while (iter.next()) { int nodeA = iter.getBaseNode(); int nodeB = iter.getAdjNode(); if (!toMoveSet.contains(nodeA) && !toMoveSet.contains(nodeB)) continue; // now overwrite exiting edge with new node ids // also flags and links could have changed due to different node order int updatedA = oldToNewMap.get(nodeA); if (updatedA < 0) updatedA = nodeA; int updatedB = oldToNewMap.get(nodeB); if (updatedB < 0) updatedB = nodeB; int edgeId = iter.getEdge(); long edgePointer = edgeAccess.toPointer(edgeId); int linkA = edgeAccess.getEdgeRef(nodeA, nodeB, edgePointer); int linkB = edgeAccess.getEdgeRef(nodeB, nodeA, edgePointer); long flags = edgeAccess.getFlags_(edgePointer, false); edgeAccess.writeEdge(edgeId, updatedA, updatedB, linkA, linkB); edgeAccess.setFlags_(edgePointer, updatedA > updatedB, flags); if (updatedA < updatedB != nodeA < nodeB) setWayGeometry_(fetchWayGeometry_(edgePointer, true, 0, -1, -1), edgePointer, false); } if (removeNodeCount >= nodeCount) throw new IllegalStateException("graph is empty after in-place removal but was " + removeNodeCount); // we do not remove the invalid edges => edgeCount stays the same! nodeCount -= removeNodeCount; // health check if (isTestingEnabled()) { EdgeExplorer explorer = createEdgeExplorer(); iter = getAllEdges(); while (iter.next()) { int base = iter.getBaseNode(); int adj = iter.getAdjNode(); String str = iter.getEdge() + ", r.contains(" + base + "):" + removedNodes.contains(base) + ", r.contains(" + adj + "):" + removedNodes.contains(adj) + ", tr.contains(" + base + "):" + toRemoveSet.contains(base) + ", tr.contains(" + adj + "):" + toRemoveSet.contains(adj) + ", base:" + base + ", adj:" + adj + ", nodeCount:" + nodeCount; if (adj >= nodeCount) throw new RuntimeException("Adj.node problem with edge " + str); if (base >= nodeCount) throw new RuntimeException("Base node problem with edge " + str); try { explorer.setBaseNode(adj).toString(); } catch (Exception ex) { org.slf4j.LoggerFactory.getLogger(getClass()).error("adj:" + adj); } try { explorer.setBaseNode(base).toString(); } catch (Exception ex) { org.slf4j.LoggerFactory.getLogger(getClass()).error("base:" + base); } } // access last node -> no error explorer.setBaseNode(nodeCount - 1).toString(); } removedNodes = null; } @Override public GraphExtension getExtension() { return extStorage; } public void setAdditionalEdgeField(long edgePointer, int value) { if (extStorage.isRequireEdgeField() && E_ADDITIONAL >= 0) edges.setInt(edgePointer + E_ADDITIONAL, value); else throw new AssertionError("This graph does not support an additional edge field."); } private void setWayGeometry_(PointList pillarNodes, long edgePointer, boolean reverse) { if (pillarNodes != null && !pillarNodes.isEmpty()) { if (pillarNodes.getDimension() != nodeAccess.getDimension()) throw new IllegalArgumentException("Cannot use pointlist which is " + pillarNodes.getDimension() + "D for graph which is " + nodeAccess.getDimension() + "D"); long existingGeoRef = Helper.toUnsignedLong(edges.getInt(edgePointer + E_GEO)); int len = pillarNodes.getSize(); int dim = nodeAccess.getDimension(); if (existingGeoRef > 0) { final int count = wayGeometry.getInt(existingGeoRef * 4L); if (len <= count) { setWayGeometryAtGeoRef(pillarNodes, edgePointer, reverse, existingGeoRef); return; } } long nextGeoRef = nextGeoRef(len * dim); setWayGeometryAtGeoRef(pillarNodes, edgePointer, reverse, nextGeoRef); } else { edges.setInt(edgePointer + E_GEO, 0); } } private void setWayGeometryAtGeoRef(PointList pillarNodes, long edgePointer, boolean reverse, long geoRef) { int len = pillarNodes.getSize(); int dim = nodeAccess.getDimension(); long geoRefPosition = (long) geoRef * 4; int totalLen = len * dim * 4 + 4; ensureGeometry(geoRefPosition, totalLen); byte[] wayGeometryBytes = createWayGeometryBytes(pillarNodes, reverse); wayGeometry.setBytes(geoRefPosition, wayGeometryBytes, wayGeometryBytes.length); edges.setInt(edgePointer + E_GEO, Helper.toSignedInt(geoRef)); } private byte[] createWayGeometryBytes(PointList pillarNodes, boolean reverse) { int len = pillarNodes.getSize(); int dim = nodeAccess.getDimension(); int totalLen = len * dim * 4 + 4; byte[] bytes = new byte[totalLen]; bitUtil.fromInt(bytes, len, 0); if (reverse) pillarNodes.reverse(); int tmpOffset = 4; boolean is3D = nodeAccess.is3D(); for (int i = 0; i < len; i++) { double lat = pillarNodes.getLatitude(i); bitUtil.fromInt(bytes, Helper.degreeToInt(lat), tmpOffset); tmpOffset += 4; bitUtil.fromInt(bytes, Helper.degreeToInt(pillarNodes.getLongitude(i)), tmpOffset); tmpOffset += 4; if (is3D) { bitUtil.fromInt(bytes, Helper.eleToInt(pillarNodes.getElevation(i)), tmpOffset); tmpOffset += 4; } } return bytes; } private PointList fetchWayGeometry_(long edgePointer, boolean reverse, int mode, int baseNode, int adjNode) { long geoRef = Helper.toUnsignedLong(edges.getInt(edgePointer + E_GEO)); int count = 0; byte[] bytes = null; if (geoRef > 0) { geoRef *= 4L; count = wayGeometry.getInt(geoRef); geoRef += 4L; bytes = new byte[count * nodeAccess.getDimension() * 4]; wayGeometry.getBytes(geoRef, bytes, bytes.length); } else if (mode == 0) return PointList.EMPTY; PointList pillarNodes = new PointList(count + mode, nodeAccess.is3D()); if (reverse) { if ((mode & 2) != 0) pillarNodes.add(nodeAccess, adjNode); } else if ((mode & 1) != 0) pillarNodes.add(nodeAccess, baseNode); int index = 0; for (int i = 0; i < count; i++) { double lat = Helper.intToDegree(bitUtil.toInt(bytes, index)); index += 4; double lon = Helper.intToDegree(bitUtil.toInt(bytes, index)); index += 4; if (nodeAccess.is3D()) { pillarNodes.add(lat, lon, Helper.intToEle(bitUtil.toInt(bytes, index))); index += 4; } else { pillarNodes.add(lat, lon); } } if (reverse) { if ((mode & 1) != 0) pillarNodes.add(nodeAccess, baseNode); pillarNodes.reverse(); } else if ((mode & 2) != 0) pillarNodes.add(nodeAccess, adjNode); return pillarNodes; } private void setName(long edgePointer, String name) { int nameIndexRef = (int) nameIndex.put(name); if (nameIndexRef < 0) throw new IllegalStateException("Too many names are stored, currently limited to int pointer"); edges.setInt(edgePointer + E_NAME, nameIndexRef); } GHBitSet getRemovedNodes() { if (removedNodes == null) removedNodes = new GHBitSetImpl(getNodes()); return removedNodes; } private void ensureGeometry(long bytePos, int byteLength) { wayGeometry.ensureCapacity(bytePos + byteLength); } private long nextGeoRef(int arrayLength) { long tmp = maxGeoRef; maxGeoRef += arrayLength + 1L; if (maxGeoRef >= 0xFFFFffffL) throw new IllegalStateException("Geometry too large, does not fit in 32 bits " + maxGeoRef); return tmp; } protected static class EdgeIterable extends CommonEdgeIterator implements EdgeExplorer, EdgeIterator { final EdgeFilter filter; int nextEdgeId; public EdgeIterable(BaseGraph baseGraph, EdgeAccess edgeAccess, EdgeFilter filter) { super(-1, edgeAccess, baseGraph); if (filter == null) throw new IllegalArgumentException("Instead null filter use EdgeFilter.ALL_EDGES"); this.filter = filter; } final void setEdgeId(int edgeId) { this.nextEdgeId = this.edgeId = edgeId; } final boolean init(int tmpEdgeId, int expectedAdjNode) { setEdgeId(tmpEdgeId); if (tmpEdgeId != EdgeIterator.NO_EDGE) { selectEdgeAccess(); this.edgePointer = edgeAccess.toPointer(tmpEdgeId); } // expect only edgePointer is properly initialized via setEdgeId baseNode = edgeAccess.edges.getInt(edgePointer + edgeAccess.E_NODEA); if (baseNode == EdgeAccess.NO_NODE) throw new IllegalStateException("content of edgeId " + edgeId + " is marked as invalid - ie. the edge is already removed!"); adjNode = edgeAccess.edges.getInt(edgePointer + edgeAccess.E_NODEB); // a next() call should return false nextEdgeId = EdgeIterator.NO_EDGE; if (expectedAdjNode == adjNode || expectedAdjNode == Integer.MIN_VALUE) { reverse = false; return true; } else if (expectedAdjNode == baseNode) { reverse = true; baseNode = adjNode; adjNode = expectedAdjNode; return true; } return false; } final void _setBaseNode(int baseNode) { this.baseNode = baseNode; } @Override public EdgeIterator setBaseNode(int baseNode) { // always use base graph edge access setEdgeId(baseGraph.edgeAccess.getEdgeRef(baseNode)); _setBaseNode(baseNode); return this; } protected void selectEdgeAccess() { } @Override public final boolean next() { while (true) { if (nextEdgeId == EdgeIterator.NO_EDGE) return false; selectEdgeAccess(); edgePointer = edgeAccess.toPointer(nextEdgeId); edgeId = nextEdgeId; adjNode = edgeAccess.getOtherNode(baseNode, edgePointer); reverse = baseNode > adjNode; freshFlags = false; // position to next edge nextEdgeId = edgeAccess.getEdgeRef(baseNode, adjNode, edgePointer); assert nextEdgeId != edgeId : ("endless loop detected for base node: " + baseNode + ", adj node: " + adjNode + ", edge pointer: " + edgePointer + ", edge: " + edgeId); if (filter.accept(this)) return true; } } @Override public EdgeIteratorState detach(boolean reverseArg) { if (edgeId == nextEdgeId || edgeId == EdgeIterator.NO_EDGE) throw new IllegalStateException("call next before detaching or setEdgeId (edgeId:" + edgeId + " vs. next " + nextEdgeId + ")"); EdgeIterable iter = edgeAccess.createSingleEdge(filter); boolean ret; if (reverseArg) { ret = iter.init(edgeId, baseNode); // for #162 iter.reverse = !reverse; } else ret = iter.init(edgeId, adjNode); assert ret; return iter; } } /** * Include all edges of this storage in the iterator. */ protected static class AllEdgeIterator extends CommonEdgeIterator implements AllEdgesIterator { public AllEdgeIterator(BaseGraph baseGraph) { this(baseGraph, baseGraph.edgeAccess); } private AllEdgeIterator(BaseGraph baseGraph, EdgeAccess edgeAccess) { super(-1, edgeAccess, baseGraph); } @Override public int getMaxId() { return baseGraph.edgeCount; } @Override public boolean next() { while (true) { edgeId++; edgePointer = (long) edgeId * edgeAccess.getEntryBytes(); if (!checkRange()) return false; baseNode = edgeAccess.edges.getInt(edgePointer + edgeAccess.E_NODEA); // some edges are deleted and have a negative node if (baseNode == EdgeAccess.NO_NODE) continue; freshFlags = false; adjNode = edgeAccess.edges.getInt(edgePointer + edgeAccess.E_NODEB); // this is always false because of 'getBaseNode() <= getAdjNode()' reverse = false; return true; } } protected boolean checkRange() { return edgeId < baseGraph.edgeCount; } @Override public final EdgeIteratorState detach(boolean reverseArg) { if (edgePointer < 0) throw new IllegalStateException("call next before detaching"); AllEdgeIterator iter = new AllEdgeIterator(baseGraph, edgeAccess); iter.edgeId = edgeId; iter.edgePointer = edgePointer; if (reverseArg) { iter.reverse = !this.reverse; iter.baseNode = adjNode; iter.adjNode = baseNode; } else { iter.reverse = this.reverse; iter.baseNode = baseNode; iter.adjNode = adjNode; } return iter; } } /** * Common private super class for AllEdgesIteratorImpl and EdgeIterable */ static abstract class CommonEdgeIterator implements EdgeIteratorState { final BaseGraph baseGraph; protected long edgePointer; protected int baseNode; protected int adjNode; protected EdgeAccess edgeAccess; // we need reverse if detach is called boolean reverse = false; boolean freshFlags; int edgeId = -1; private long cachedFlags; public CommonEdgeIterator(long edgePointer, EdgeAccess edgeAccess, BaseGraph baseGraph) { this.edgePointer = edgePointer; this.edgeAccess = edgeAccess; this.baseGraph = baseGraph; } @Override public final int getBaseNode() { return baseNode; } @Override public final int getAdjNode() { return adjNode; } @Override public final double getDistance() { return edgeAccess.getDist(edgePointer); } @Override public final EdgeIteratorState setDistance(double dist) { edgeAccess.setDist(edgePointer, dist); return this; } final long getDirectFlags() { if (!freshFlags) { cachedFlags = edgeAccess.getFlags_(edgePointer, reverse); freshFlags = true; } return cachedFlags; } @Override public long getFlags() { return getDirectFlags(); } @Override public final EdgeIteratorState setFlags(long fl) { edgeAccess.setFlags_(edgePointer, reverse, fl); cachedFlags = fl; freshFlags = true; return this; } @Override public final int getAdditionalField() { return baseGraph.edges.getInt(edgePointer + baseGraph.E_ADDITIONAL); } @Override public final EdgeIteratorState setAdditionalField(int value) { baseGraph.setAdditionalEdgeField(edgePointer, value); return this; } @Override public final EdgeIteratorState copyPropertiesTo(EdgeIteratorState edge) { return baseGraph.copyProperties(this, edge); } /** * Reports whether the edge is available in forward direction for the specified encoder. */ @Override public boolean isForward(FlagEncoder encoder) { return encoder.isForward(getDirectFlags()); } /** * Reports whether the edge is available in backward direction for the specified encoder. */ @Override public boolean isBackward(FlagEncoder encoder) { return encoder.isBackward(getDirectFlags()); } @Override public EdgeIteratorState setWayGeometry(PointList pillarNodes) { baseGraph.setWayGeometry_(pillarNodes, edgePointer, reverse); return this; } @Override public PointList fetchWayGeometry(int mode) { return baseGraph.fetchWayGeometry_(edgePointer, reverse, mode, getBaseNode(), getAdjNode()); } @Override public int getEdge() { return edgeId; } @Override public String getName() { int nameIndexRef = baseGraph.edges.getInt(edgePointer + baseGraph.E_NAME); return baseGraph.nameIndex.get(nameIndexRef); } @Override public EdgeIteratorState setName(String name) { baseGraph.setName(edgePointer, name); return this; } @Override public final boolean getBool(int key, boolean _default) { // for non-existent keys return default return _default; } @Override public final String toString() { return getEdge() + " " + getBaseNode() + "-" + getAdjNode(); } } }