/* * 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.routing; import com.carrotsearch.hppc.IntArrayList; import com.carrotsearch.hppc.IntIndexedContainer; import com.graphhopper.coll.GHIntArrayList; import com.graphhopper.debatty.java.stringsimilarity.JaroWinkler; import com.graphhopper.routing.util.DataFlagEncoder; import com.graphhopper.routing.util.DefaultEdgeFilter; import com.graphhopper.routing.util.FlagEncoder; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.Graph; import com.graphhopper.storage.NodeAccess; import com.graphhopper.storage.SPTEntry; import com.graphhopper.util.*; import com.graphhopper.util.shapes.GHPoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Stores the nodes for the found path of an algorithm. It additionally needs the edgeIds to make * edge determination faster and less complex as there could be several edges (u,v) especially for * graphs with shortcuts. * <p> * * @author Peter Karich * @author Ottavio Campana * @author jan soe */ public class Path { private final Logger logger = LoggerFactory.getLogger(getClass()); final StopWatch extractSW = new StopWatch("extract"); protected Graph graph; protected double distance; // we go upwards (via SPTEntry.parent) from the goal node to the origin node protected boolean reverseOrder = true; protected long time; /** * Shortest path tree entry */ protected SPTEntry sptEntry; protected int endNode = -1; private List<String> description; protected Weighting weighting; private FlagEncoder encoder; private boolean found; private int fromNode = -1; private GHIntArrayList edgeIds; private double weight; private NodeAccess nodeAccess; public Path(Graph graph, Weighting weighting) { this.weight = Double.MAX_VALUE; this.graph = graph; this.nodeAccess = graph.getNodeAccess(); this.weighting = weighting; this.encoder = weighting.getFlagEncoder(); this.edgeIds = new GHIntArrayList(); } /** * Populates an unextracted path instances from the specified path p. */ Path(Path p) { this(p.graph, p.weighting); weight = p.weight; edgeIds = new GHIntArrayList(p.edgeIds); sptEntry = p.sptEntry; } /** * @return the description of this route alternative to make it meaningful for the user e.g. it * displays one or two main roads of the route. */ public List<String> getDescription() { if (description == null) return Collections.emptyList(); return description; } public Path setDescription(List<String> description) { this.description = description; return this; } public Path setSPTEntry(SPTEntry sptEntry) { this.sptEntry = sptEntry; return this; } protected void addEdge(int edge) { edgeIds.add(edge); } protected Path setEndNode(int end) { endNode = end; return this; } /** * @return the first node of this Path. */ private int getFromNode() { if (fromNode < 0) throw new IllegalStateException("Call extract() before retrieving fromNode"); return fromNode; } /** * We need to remember fromNode explicitly as its not saved in one edgeId of edgeIds. */ protected Path setFromNode(int from) { fromNode = from; return this; } public int getEdgeCount() { return edgeIds.size(); } public boolean isFound() { return found; } public Path setFound(boolean found) { this.found = found; return this; } void reverseOrder() { if (!reverseOrder) throw new IllegalStateException("Switching order multiple times is not supported"); reverseOrder = false; edgeIds.reverse(); } public Path setDistance(double distance) { this.distance = distance; return this; } /** * @return distance in meter */ public double getDistance() { return distance; } /** * @return time in millis */ public long getTime() { return time; } /** * This weight will be updated during the algorithm. The initial value is maximum double. */ public double getWeight() { return weight; } public Path setWeight(double w) { this.weight = w; return this; } /** * Extracts the Path from the shortest-path-tree determined by sptEntry. */ public Path extract() { if (isFound()) throw new IllegalStateException("Extract can only be called once"); extractSW.start(); SPTEntry currEdge = sptEntry; setEndNode(currEdge.adjNode); boolean nextEdgeValid = EdgeIterator.Edge.isValid(currEdge.edge); int nextEdge; while (nextEdgeValid) { // the reverse search needs the next edge nextEdgeValid = EdgeIterator.Edge.isValid(currEdge.parent.edge); nextEdge = nextEdgeValid ? currEdge.parent.edge : EdgeIterator.NO_EDGE; processEdge(currEdge.edge, currEdge.adjNode, nextEdge); currEdge = currEdge.parent; } setFromNode(currEdge.adjNode); reverseOrder(); extractSW.stop(); return setFound(true); } /** * Yields the final edge of the path */ public EdgeIteratorState getFinalEdge() { return graph.getEdgeIteratorState(edgeIds.get(edgeIds.size() - 1), endNode); } /** * @return the time it took to extract the path in nano (!) seconds */ public long getExtractTime() { return extractSW.getNanos(); } public String getDebugInfo() { return extractSW.toString(); } /** * Calculates the distance and time of the specified edgeId. Also it adds the edgeId to the path list. * * @param prevEdgeId here the edge that comes before edgeId is necessary. I.e. for the reverse search we need the * next edge. */ protected void processEdge(int edgeId, int adjNode, int prevEdgeId) { EdgeIteratorState iter = graph.getEdgeIteratorState(edgeId, adjNode); distance += iter.getDistance(); time += weighting.calcMillis(iter, false, prevEdgeId); addEdge(edgeId); } /** * Iterates over all edges in this path sorted from start to end and calls the visitor callback * for every edge. * <p> * * @param visitor callback to handle every edge. The edge is decoupled from the iterator and can * be stored. */ private void forEveryEdge(EdgeVisitor visitor) { int tmpNode = getFromNode(); int len = edgeIds.size(); int prevEdgeId = EdgeIterator.NO_EDGE; for (int i = 0; i < len; i++) { EdgeIteratorState edgeBase = graph.getEdgeIteratorState(edgeIds.get(i), tmpNode); if (edgeBase == null) throw new IllegalStateException("Edge " + edgeIds.get(i) + " was empty when requested with node " + tmpNode + ", array index:" + i + ", edges:" + edgeIds.size()); tmpNode = edgeBase.getBaseNode(); // more efficient swap, currently not implemented for virtual edges: visitor.next(edgeBase.detach(true), i); edgeBase = graph.getEdgeIteratorState(edgeBase.getEdge(), tmpNode); visitor.next(edgeBase, i, prevEdgeId); prevEdgeId = edgeBase.getEdge(); } visitor.finish(); } /** * Returns the list of all edges. */ public List<EdgeIteratorState> calcEdges() { final List<EdgeIteratorState> edges = new ArrayList<EdgeIteratorState>(edgeIds.size()); if (edgeIds.isEmpty()) return edges; forEveryEdge(new EdgeVisitor() { @Override public void next(EdgeIteratorState eb, int index, int prevEdgeId) { edges.add(eb); } @Override public void finish() { } }); return edges; } /** * @return the uncached node indices of the tower nodes in this path. */ public IntIndexedContainer calcNodes() { final IntArrayList nodes = new IntArrayList(edgeIds.size() + 1); if (edgeIds.isEmpty()) { if (isFound()) { nodes.add(endNode); } return nodes; } int tmpNode = getFromNode(); nodes.add(tmpNode); forEveryEdge(new EdgeVisitor() { @Override public void next(EdgeIteratorState eb, int index, int prevEdgeId) { nodes.add(eb.getAdjNode()); } @Override public void finish() { } }); return nodes; } /** * This method calculated a list of points for this path * <p> * * @return this path its geometry */ public PointList calcPoints() { final PointList points = new PointList(edgeIds.size() + 1, nodeAccess.is3D()); if (edgeIds.isEmpty()) { if (isFound()) { points.add(graph.getNodeAccess(), endNode); } return points; } int tmpNode = getFromNode(); points.add(nodeAccess, tmpNode); forEveryEdge(new EdgeVisitor() { @Override public void next(EdgeIteratorState eb, int index, int prevEdgeId) { PointList pl = eb.fetchWayGeometry(2); for (int j = 0; j < pl.getSize(); j++) { points.add(pl, j); } } @Override public void finish() { } }); return points; } /** * @return the list of instructions for this path. */ public InstructionList calcInstructions(final Translation tr) { final InstructionList ways = new InstructionList(edgeIds.size() / 4, tr); if (edgeIds.isEmpty()) { if (isFound()) { ways.add(new FinishInstruction(nodeAccess, endNode)); } return ways; } forEveryEdge(new InstructionsFromEdges(getFromNode(), graph, weighting, encoder, nodeAccess, tr, ways)); return ways; } @Override public String toString() { return "distance:" + getDistance() + ", edges:" + edgeIds.size(); } public String toDetailsString() { String str = ""; for (int i = 0; i < edgeIds.size(); i++) { if (i > 0) str += "->"; str += edgeIds.get(i); } return toString() + ", found:" + isFound() + ", " + str; } /** * The callback used in forEveryEdge. */ public interface EdgeVisitor { void next(EdgeIteratorState edge, int index, int prevEdgeId); void finish(); } }