com.graphhopper.routing.ViaRouting Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of graphhopper-core Show documentation
Show all versions of graphhopper-core Show documentation
GraphHopper is a fast and memory efficient Java road routing engine
working seamlessly with OpenStreetMap data.
/*
* 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.graphhopper.routing.ev.EncodedValueLookup;
import com.graphhopper.routing.ev.EnumEncodedValue;
import com.graphhopper.routing.ev.RoadClass;
import com.graphhopper.routing.ev.RoadEnvironment;
import com.graphhopper.routing.querygraph.QueryGraph;
import com.graphhopper.routing.querygraph.VirtualEdgeIteratorState;
import com.graphhopper.routing.util.*;
import com.graphhopper.storage.index.LocationIndex;
import com.graphhopper.storage.index.Snap;
import com.graphhopper.util.EdgeIterator;
import com.graphhopper.util.EdgeIteratorState;
import com.graphhopper.util.Helper;
import com.graphhopper.util.shapes.GHPoint;
import java.util.ArrayList;
import java.util.List;
import static com.graphhopper.util.EdgeIterator.ANY_EDGE;
import static com.graphhopper.util.EdgeIterator.NO_EDGE;
import static com.graphhopper.util.Parameters.Curbsides.CURBSIDE_ANY;
import static com.graphhopper.util.Parameters.Routing.CURBSIDE;
/**
* The methods here can be used to calculate routes with or without via points and implement possible restrictions
* like snap preventions, headings and curbsides.
*
* @author Peter Karich
* @author easbar
*/
public class ViaRouting {
/**
* @throws MultiplePointsNotFoundException in case one or more points could not be resolved
*/
public static List lookup(EncodedValueLookup lookup, List points, EdgeFilter snapFilter,
LocationIndex locationIndex, List snapPreventions, List pointHints,
DirectedEdgeFilter directedSnapFilter, List headings) {
if (points.size() < 2)
throw new IllegalArgumentException("At least 2 points have to be specified, but was:" + points.size());
final EnumEncodedValue roadClassEnc = lookup.getEnumEncodedValue(RoadClass.KEY, RoadClass.class);
final EnumEncodedValue roadEnvEnc = lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class);
EdgeFilter strictEdgeFilter = snapPreventions.isEmpty()
? snapFilter
: new SnapPreventionEdgeFilter(snapFilter, roadClassEnc, roadEnvEnc, snapPreventions);
List snaps = new ArrayList<>(points.size());
IntArrayList pointsNotFound = new IntArrayList();
for (int placeIndex = 0; placeIndex < points.size(); placeIndex++) {
GHPoint point = points.get(placeIndex);
Snap snap = null;
if (placeIndex < headings.size() && !Double.isNaN(headings.get(placeIndex))) {
if (!pointHints.isEmpty() && !Helper.isEmpty(pointHints.get(placeIndex)))
throw new IllegalArgumentException("Cannot specify heading and point_hint at the same time. " +
"Make sure you specify either an empty point_hint (String) or a NaN heading (double) for point " + placeIndex);
snap = locationIndex.findClosest(point.lat, point.lon, new HeadingEdgeFilter(directedSnapFilter, headings.get(placeIndex), point));
} else if (!pointHints.isEmpty()) {
snap = locationIndex.findClosest(point.lat, point.lon, new NameSimilarityEdgeFilter(strictEdgeFilter,
pointHints.get(placeIndex), point, 170));
} else if (!snapPreventions.isEmpty()) {
snap = locationIndex.findClosest(point.lat, point.lon, strictEdgeFilter);
}
if (snap == null || !snap.isValid())
snap = locationIndex.findClosest(point.lat, point.lon, snapFilter);
if (!snap.isValid())
pointsNotFound.add(placeIndex);
snaps.add(snap);
}
if (!pointsNotFound.isEmpty())
throw new MultiplePointsNotFoundException(pointsNotFound);
return snaps;
}
public static Result calcPaths(List points, QueryGraph queryGraph, List snaps, DirectedEdgeFilter directedEdgeFilter, PathCalculator pathCalculator, List curbsides, boolean forceCurbsides, List headings, boolean passThrough) {
if (!curbsides.isEmpty() && curbsides.size() != points.size())
throw new IllegalArgumentException("If you pass " + CURBSIDE + ", you need to pass exactly one curbside for every point, empty curbsides will be ignored");
if (!curbsides.isEmpty() && !headings.isEmpty())
throw new IllegalArgumentException("You cannot use curbsides and headings or pass_through at the same time");
final int legs = snaps.size() - 1;
Result result = new Result(legs);
for (int leg = 0; leg < legs; ++leg) {
Snap fromSnap = snaps.get(leg);
Snap toSnap = snaps.get(leg + 1);
// enforce headings
// at via-nodes and the target node the heading parameter is interpreted as the direction we want
// to enforce for arriving (not starting) at this node. the starting direction is not enforced at
// all for these points (unless using pass through). see this forum discussion:
// https://discuss.graphhopper.com/t/meaning-of-heading-parameter-for-via-routing/5643/6
double fromHeading = (leg == 0 && !headings.isEmpty()) ? headings.get(0) : Double.NaN;
double toHeading = (snaps.size() == headings.size() && !Double.isNaN(headings.get(leg + 1))) ? headings.get(leg + 1) : Double.NaN;
// enforce pass-through
int incomingEdge = NO_EDGE;
if (leg != 0) {
// enforce straight start after via stop
Path prevRoute = result.paths.get(leg - 1);
if (prevRoute.getEdgeCount() > 0)
incomingEdge = prevRoute.getFinalEdge().getEdge();
}
// enforce curbsides
final String fromCurbside = curbsides.isEmpty() ? CURBSIDE_ANY : curbsides.get(leg);
final String toCurbside = curbsides.isEmpty() ? CURBSIDE_ANY : curbsides.get(leg + 1);
EdgeRestrictions edgeRestrictions = buildEdgeRestrictions(queryGraph, fromSnap, toSnap,
fromHeading, toHeading, incomingEdge, passThrough,
fromCurbside, toCurbside, directedEdgeFilter);
edgeRestrictions.setSourceOutEdge(ignoreThrowOrAcceptImpossibleCurbsides(curbsides, edgeRestrictions.getSourceOutEdge(), leg, forceCurbsides));
edgeRestrictions.setTargetInEdge(ignoreThrowOrAcceptImpossibleCurbsides(curbsides, edgeRestrictions.getTargetInEdge(), leg + 1, forceCurbsides));
// calculate paths
List paths = pathCalculator.calcPaths(fromSnap.getClosestNode(), toSnap.getClosestNode(), edgeRestrictions);
result.debug += pathCalculator.getDebugString();
// for alternative routing we get multiple paths and add all of them (which is ok, because we do not allow
// via-points for alternatives at the moment). otherwise we would have to return a list> and find
// a good method to decide how to combine the different legs
for (int i = 0; i < paths.size(); i++) {
Path path = paths.get(i);
if (path.getTime() < 0)
throw new RuntimeException("Time was negative " + path.getTime() + " for index " + i);
result.paths.add(path);
result.debug += ", " + path.getDebugInfo();
}
result.visitedNodes += pathCalculator.getVisitedNodes();
result.debug += ", visited nodes sum: " + result.visitedNodes;
}
return result;
}
public static class Result {
public List paths;
public long visitedNodes;
public String debug = "";
Result(int legs) {
paths = new ArrayList<>(legs);
}
}
/**
* Determines restrictions for the start/target edges to account for the heading, pass_through and curbside parameters
* for a single via-route leg.
*
* @param fromHeading the heading at the start node of this leg, or NaN if no restriction should be applied
* @param toHeading the heading at the target node (the vehicle's heading when arriving at the target), or NaN if
* no restriction should be applied
* @param incomingEdge the last edge of the previous leg (or {@link EdgeIterator#NO_EDGE} if not available
*/
private static EdgeRestrictions buildEdgeRestrictions(
QueryGraph queryGraph, Snap fromSnap, Snap toSnap,
double fromHeading, double toHeading, int incomingEdge, boolean passThrough,
String fromCurbside, String toCurbside, DirectedEdgeFilter edgeFilter) {
EdgeRestrictions edgeRestrictions = new EdgeRestrictions();
// curbsides
if (!fromCurbside.equals(CURBSIDE_ANY) || !toCurbside.equals(CURBSIDE_ANY)) {
DirectedEdgeFilter directedEdgeFilter = (edge, reverse) -> {
// todo: maybe find a cleaner way to obtain the original edge given a VirtualEdgeIterator (not VirtualEdgeIteratorState)
if (queryGraph.isVirtualEdge(edge.getEdge())) {
EdgeIteratorState virtualEdge = queryGraph.getEdgeIteratorStateForKey(edge.getEdgeKey());
EdgeIteratorState origEdge = queryGraph.getEdgeIteratorStateForKey(((VirtualEdgeIteratorState) virtualEdge).getOriginalEdgeKey());
return edgeFilter.accept(origEdge, reverse);
} else
return edgeFilter.accept(edge, reverse);
};
DirectionResolver directionResolver = new DirectionResolver(queryGraph, directedEdgeFilter);
DirectionResolverResult fromDirection = directionResolver.resolveDirections(fromSnap.getClosestNode(), fromSnap.getQueryPoint());
DirectionResolverResult toDirection = directionResolver.resolveDirections(toSnap.getClosestNode(), toSnap.getQueryPoint());
int sourceOutEdge = DirectionResolverResult.getOutEdge(fromDirection, fromCurbside);
int targetInEdge = DirectionResolverResult.getInEdge(toDirection, toCurbside);
if (fromSnap.getClosestNode() == toSnap.getClosestNode()) {
// special case where we go from one point back to itself. for example going from a point A
// with curbside right to the same point with curbside right is interpreted as 'being there
// already' -> empty path. Similarly if the curbside for the start/target is not even specified
// there is no need to drive a loop. However, going from point A/right to point A/left (or the
// other way around) means we need to drive some kind of loop to get back to the same location
// (arriving on the other side of the road).
if (Helper.isEmpty(fromCurbside) || Helper.isEmpty(toCurbside) ||
fromCurbside.equals(CURBSIDE_ANY) || toCurbside.equals(CURBSIDE_ANY) ||
fromCurbside.equals(toCurbside)) {
// we just disable start/target edge constraints to get an empty path
sourceOutEdge = ANY_EDGE;
targetInEdge = ANY_EDGE;
}
}
edgeRestrictions.setSourceOutEdge(sourceOutEdge);
edgeRestrictions.setTargetInEdge(targetInEdge);
}
// heading
if (!Double.isNaN(fromHeading) || !Double.isNaN(toHeading)) {
// todo: for heading/pass_through with edge-based routing (especially CH) we have to find the edge closest
// to the heading and use it as sourceOutEdge/targetInEdge here. the heading penalty will not be applied
// this way (unless we implement this), but this is more or less ok as we can use finite u-turn costs
// instead. maybe the hardest part is dealing with headings that cannot be fulfilled, like in one-way
// streets. see also #1765
HeadingResolver headingResolver = new HeadingResolver(queryGraph);
if (!Double.isNaN(fromHeading))
edgeRestrictions.getUnfavoredEdges().addAll(headingResolver.getEdgesWithDifferentHeading(fromSnap.getClosestNode(), fromHeading));
if (!Double.isNaN(toHeading)) {
toHeading += 180;
if (toHeading > 360)
toHeading -= 360;
edgeRestrictions.getUnfavoredEdges().addAll(headingResolver.getEdgesWithDifferentHeading(toSnap.getClosestNode(), toHeading));
}
}
// pass through
if (incomingEdge != NO_EDGE && passThrough)
edgeRestrictions.getUnfavoredEdges().add(incomingEdge);
return edgeRestrictions;
}
private static int ignoreThrowOrAcceptImpossibleCurbsides(List curbsides, int edge, int placeIndex, boolean forceCurbsides) {
if (edge != NO_EDGE) {
return edge;
}
if (forceCurbsides) {
return throwImpossibleCurbsideConstraint(curbsides, placeIndex);
} else {
return ANY_EDGE;
}
}
private static int throwImpossibleCurbsideConstraint(List curbsides, int placeIndex) {
throw new IllegalArgumentException("Impossible curbside constraint: 'curbside=" + curbsides.get(placeIndex) + "' at point " + placeIndex);
}
}