/* * 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.util.EdgeIterator; /** * Holds turn cost tables for each node. The additional field of a node will be used to point * towards the first entry within a node cost table to identify turn restrictions, or later, turn * getCosts. * <p> * * @author Karl Hübner * @author Peter Karich */ public class TurnCostExtension implements GraphExtension { /* pointer for no cost entry */ private static final int NO_TURN_ENTRY = -1; private static final long EMPTY_FLAGS = 0L; /* * items in turn cost tables: edge from, edge to, getCosts, pointer to next * cost entry of same node */ private final int TC_FROM, TC_TO, TC_FLAGS, TC_NEXT; private DataAccess turnCosts; private int turnCostsEntryIndex = -4; private int turnCostsEntryBytes; private int turnCostsCount; private NodeAccess nodeAccess; public TurnCostExtension() { TC_FROM = nextTurnCostEntryIndex(); TC_TO = nextTurnCostEntryIndex(); TC_FLAGS = nextTurnCostEntryIndex(); TC_NEXT = nextTurnCostEntryIndex(); turnCostsEntryBytes = turnCostsEntryIndex + 4; turnCostsCount = 0; } @Override public void init(Graph graph, Directory dir) { if (turnCostsCount > 0) throw new AssertionError("The turn cost storage must be initialized only once."); this.nodeAccess = graph.getNodeAccess(); this.turnCosts = dir.find("turn_costs"); } private int nextTurnCostEntryIndex() { turnCostsEntryIndex += 4; return turnCostsEntryIndex; } @Override public void setSegmentSize(int bytes) { turnCosts.setSegmentSize(bytes); } @Override public TurnCostExtension create(long initBytes) { turnCosts.create(initBytes); return this; } @Override public void flush() { turnCosts.setHeader(0, turnCostsEntryBytes); turnCosts.setHeader(1 * 4, turnCostsCount); turnCosts.flush(); } @Override public void close() { turnCosts.close(); } @Override public long getCapacity() { return turnCosts.getCapacity(); } @Override public boolean loadExisting() { if (!turnCosts.loadExisting()) return false; turnCostsEntryBytes = turnCosts.getHeader(0); turnCostsCount = turnCosts.getHeader(4); return true; } /** * This method adds a new entry which is a turn restriction or cost information via the * turnFlags. */ public void addTurnInfo(int fromEdge, int viaNode, int toEdge, long turnFlags) { // no need to store turn information if (turnFlags == EMPTY_FLAGS) return; // append int newEntryIndex = turnCostsCount; turnCostsCount++; ensureTurnCostIndex(newEntryIndex); // determine if we already have an cost entry for this node int previousEntryIndex = nodeAccess.getAdditionalNodeField(viaNode); if (previousEntryIndex == NO_TURN_ENTRY) { // set cost-pointer to this new cost entry nodeAccess.setAdditionalNodeField(viaNode, newEntryIndex); } else { int i = 0; int tmp = previousEntryIndex; while ((tmp = turnCosts.getInt((long) tmp * turnCostsEntryBytes + TC_NEXT)) != NO_TURN_ENTRY) { previousEntryIndex = tmp; // search for the last added cost entry if (i++ > 1000) { throw new IllegalStateException("Something unexpected happened. A node probably will not have 1000+ relations."); } } // set next-pointer to this new cost entry turnCosts.setInt((long) previousEntryIndex * turnCostsEntryBytes + TC_NEXT, newEntryIndex); } // add entry long costsBase = (long) newEntryIndex * turnCostsEntryBytes; turnCosts.setInt(costsBase + TC_FROM, fromEdge); turnCosts.setInt(costsBase + TC_TO, toEdge); turnCosts.setInt(costsBase + TC_FLAGS, (int) turnFlags); // next-pointer is NO_TURN_ENTRY turnCosts.setInt(costsBase + TC_NEXT, NO_TURN_ENTRY); } /** * @return turn flags of the specified node and edge properties. */ public long getTurnCostFlags(int edgeFrom, int nodeVia, int edgeTo) { if (edgeFrom == EdgeIterator.NO_EDGE || edgeTo == EdgeIterator.NO_EDGE) throw new IllegalArgumentException("from and to edge cannot be NO_EDGE"); if (nodeVia < 0) throw new IllegalArgumentException("via node cannot be negative"); return nextCostFlags(edgeFrom, nodeVia, edgeTo); } private long nextCostFlags(int edgeFrom, int nodeVia, int edgeTo) { int turnCostIndex = nodeAccess.getAdditionalNodeField(nodeVia); int i = 0; for (; i < 1000; i++) { if (turnCostIndex == NO_TURN_ENTRY) break; long turnCostPtr = (long) turnCostIndex * turnCostsEntryBytes; if (edgeFrom == turnCosts.getInt(turnCostPtr + TC_FROM)) { if (edgeTo == turnCosts.getInt(turnCostPtr + TC_TO)) return turnCosts.getInt(turnCostPtr + TC_FLAGS); } int nextTurnCostIndex = turnCosts.getInt(turnCostPtr + TC_NEXT); if (nextTurnCostIndex == turnCostIndex) throw new IllegalStateException("something went wrong: next entry would be the same"); turnCostIndex = nextTurnCostIndex; } // so many turn restrictions on one node? here is something wrong if (i >= 1000) throw new IllegalStateException("something went wrong: there seems to be no end of the turn cost-list!?"); return EMPTY_FLAGS; } private void ensureTurnCostIndex(int nodeIndex) { turnCosts.ensureCapacity(((long) nodeIndex + 4) * turnCostsEntryBytes); } @Override public boolean isRequireNodeField() { //we require the additional field in the graph to point to the first entry in the node table return true; } @Override public boolean isRequireEdgeField() { return false; } @Override public int getDefaultNodeFieldValue() { return NO_TURN_ENTRY; } @Override public int getDefaultEdgeFieldValue() { throw new UnsupportedOperationException("Not supported by this storage"); } @Override public GraphExtension copyTo(GraphExtension clonedStorage) { if (!(clonedStorage instanceof TurnCostExtension)) { throw new IllegalStateException("the extended storage to clone must be the same"); } TurnCostExtension clonedTC = (TurnCostExtension) clonedStorage; turnCosts.copyTo(clonedTC.turnCosts); clonedTC.turnCostsCount = turnCostsCount; return clonedStorage; } @Override public boolean isClosed() { return turnCosts.isClosed(); } @Override public String toString() { return "turn_cost"; } }