/*
* 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.template;
import com.graphhopper.GHRequest;
import com.graphhopper.GHResponse;
import com.graphhopper.PathWrapper;
import com.graphhopper.routing.*;
import com.graphhopper.routing.util.*;
import com.graphhopper.storage.index.LocationIndex;
import com.graphhopper.storage.index.QueryResult;
import com.graphhopper.util.EdgeIteratorState;
import com.graphhopper.util.Parameters.Routing;
import com.graphhopper.util.PathMerger;
import com.graphhopper.util.StopWatch;
import com.graphhopper.util.Translation;
import com.graphhopper.util.exceptions.PointNotFoundException;
import com.graphhopper.util.shapes.GHPoint;
import java.util.ArrayList;
import java.util.List;
/**
* Implementation of calculating a route with multiple via points.
*
* @author Peter Karich
*/
public class ViaRoutingTemplate extends AbstractRoutingTemplate implements RoutingTemplate {
protected final GHRequest ghRequest;
protected final GHResponse ghResponse;
protected final PathWrapper altResponse = new PathWrapper();
private final LocationIndex locationIndex;
// result from route
protected List<Path> pathList;
public ViaRoutingTemplate(GHRequest ghRequest, GHResponse ghRsp, LocationIndex locationIndex) {
this.locationIndex = locationIndex;
this.ghRequest = ghRequest;
this.ghResponse = ghRsp;
}
@Override
public List<QueryResult> lookup(List<GHPoint> points, FlagEncoder encoder) {
if (points.size() < 2)
throw new IllegalArgumentException("At least 2 points have to be specified, but was:" + points.size());
EdgeFilter edgeFilter = new DefaultEdgeFilter(encoder);
queryResults = new ArrayList<>(points.size());
for (int placeIndex = 0; placeIndex < points.size(); placeIndex++) {
GHPoint point = points.get(placeIndex);
QueryResult res;
if (ghRequest.hasPointHints()) {
res = locationIndex.findClosest(point.lat, point.lon, new NameSimilarityEdgeFilter(edgeFilter, ghRequest.getPointHints().get(placeIndex)));
if (!res.isValid()) {
res = locationIndex.findClosest(point.lat, point.lon, edgeFilter);
}
} else {
res = locationIndex.findClosest(point.lat, point.lon, edgeFilter);
}
if (!res.isValid())
ghResponse.addError(new PointNotFoundException("Cannot find point " + placeIndex + ": " + point, placeIndex));
queryResults.add(res);
}
return queryResults;
}
@Override
public List<Path> calcPaths(QueryGraph queryGraph, RoutingAlgorithmFactory algoFactory, AlgorithmOptions algoOpts) {
long visitedNodesSum = 0L;
boolean viaTurnPenalty = ghRequest.getHints().getBool(Routing.PASS_THROUGH, false);
int pointCounts = ghRequest.getPoints().size();
pathList = new ArrayList<>(pointCounts - 1);
QueryResult fromQResult = queryResults.get(0);
StopWatch sw;
for (int placeIndex = 1; placeIndex < pointCounts; placeIndex++) {
if (placeIndex == 1) {
// enforce start direction
queryGraph.enforceHeading(fromQResult.getClosestNode(), ghRequest.getFavoredHeading(0), false);
} else if (viaTurnPenalty) {
// enforce straight start after via stop
Path prevRoute = pathList.get(placeIndex - 2);
if (prevRoute.getEdgeCount() > 0) {
EdgeIteratorState incomingVirtualEdge = prevRoute.getFinalEdge();
queryGraph.unfavorVirtualEdgePair(fromQResult.getClosestNode(), incomingVirtualEdge.getEdge());
}
}
QueryResult toQResult = queryResults.get(placeIndex);
// enforce end direction
queryGraph.enforceHeading(toQResult.getClosestNode(), ghRequest.getFavoredHeading(placeIndex), true);
sw = new StopWatch().start();
RoutingAlgorithm algo = algoFactory.createAlgo(queryGraph, algoOpts);
String debug = ", algoInit:" + sw.stop().getSeconds() + "s";
sw = new StopWatch().start();
List<Path> tmpPathList = algo.calcPaths(fromQResult.getClosestNode(), toQResult.getClosestNode());
debug += ", " + algo.getName() + "-routing:" + sw.stop().getSeconds() + "s";
if (tmpPathList.isEmpty())
throw new IllegalStateException("At least one path has to be returned for " + fromQResult + " -> " + toQResult);
int idx = 0;
for (Path path : tmpPathList) {
if (path.getTime() < 0)
throw new RuntimeException("Time was negative " + path.getTime() + " for index " + idx + ". Please report as bug and include:" + ghRequest);
pathList.add(path);
debug += ", " + path.getDebugInfo();
idx++;
}
altResponse.addDebugInfo(debug);
// reset all direction enforcements in queryGraph to avoid influencing next path
queryGraph.clearUnfavoredStatus();
if (algo.getVisitedNodes() >= algoOpts.getMaxVisitedNodes())
throw new IllegalArgumentException("No path found due to maximum nodes exceeded " + algoOpts.getMaxVisitedNodes());
visitedNodesSum += algo.getVisitedNodes();
fromQResult = toQResult;
}
ghResponse.getHints().put("visited_nodes.sum", visitedNodesSum);
ghResponse.getHints().put("visited_nodes.average", (float) visitedNodesSum / (pointCounts - 1));
return pathList;
}
@Override
public boolean isReady(PathMerger pathMerger, Translation tr) {
if (ghRequest.getPoints().size() - 1 != pathList.size())
throw new RuntimeException("There should be exactly one more points than paths. points:" + ghRequest.getPoints().size() + ", paths:" + pathList.size());
altResponse.setWaypoints(getWaypoints());
ghResponse.add(altResponse);
pathMerger.doWork(altResponse, pathList, tr);
return true;
}
@Override
public int getMaxRetries() {
return 1;
}
}