/*
* 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.DefaultEdgeFilter;
import com.graphhopper.routing.util.EdgeFilter;
import com.graphhopper.routing.util.FlagEncoder;
import com.graphhopper.routing.util.tour.MultiPointTour;
import com.graphhopper.routing.util.tour.TourStrategy;
import com.graphhopper.routing.weighting.AvoidEdgesWeighting;
import com.graphhopper.storage.index.LocationIndex;
import com.graphhopper.storage.index.QueryResult;
import com.graphhopper.util.Helper;
import com.graphhopper.util.Parameters;
import com.graphhopper.util.Parameters.Algorithms;
import com.graphhopper.util.Parameters.Algorithms.RoundTrip;
import com.graphhopper.util.PathMerger;
import com.graphhopper.util.Translation;
import com.graphhopper.util.exceptions.PointNotFoundException;
import com.graphhopper.util.shapes.GHPoint;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
/**
* Implementation of calculating a route with one or more round trip (route with identical start and
* end).
*
* @author Peter Karich
*/
public class RoundTripRoutingTemplate extends AbstractRoutingTemplate implements RoutingTemplate {
private final int maxRetries;
private final GHRequest ghRequest;
private final GHResponse ghResponse;
private final LocationIndex locationIndex;
private PathWrapper altResponse;
// result from route
private List<Path> pathList;
public RoundTripRoutingTemplate(GHRequest request, GHResponse ghRsp, LocationIndex locationIndex, int maxRetries) {
this.ghRequest = request;
this.ghResponse = ghRsp;
this.locationIndex = locationIndex;
this.maxRetries = maxRetries;
}
@Override
public List<QueryResult> lookup(List<GHPoint> points, FlagEncoder encoder) {
if (points.isEmpty())
throw new IllegalStateException("For round trip calculation one point is required");
final double distanceInMeter = ghRequest.getHints().getDouble(RoundTrip.DISTANCE, 10000);
final long seed = ghRequest.getHints().getLong(RoundTrip.SEED, 0L);
final double initialHeading = ghRequest.getHints().getDouble(RoundTrip.HEADING, Double.NaN);
final int roundTripPointCount = Math.min(20, ghRequest.getHints().getInt(Algorithms.ROUND_TRIP + ".points", 2 + (int) (distanceInMeter / 50000)));
final GHPoint start = ghRequest.getPoints().get(0);
TourStrategy strategy = new MultiPointTour(new Random(seed), distanceInMeter, roundTripPointCount, initialHeading);
queryResults = new ArrayList<>(2 + strategy.getNumberOfGeneratedPoints());
EdgeFilter edgeFilter = new DefaultEdgeFilter(encoder);
QueryResult startQR = locationIndex.findClosest(start.lat, start.lon, edgeFilter);
if (!startQR.isValid())
throw new PointNotFoundException("Cannot find point 0: " + start, 0);
queryResults.add(startQR);
GHPoint last = points.get(0);
for (int i = 0; i < strategy.getNumberOfGeneratedPoints(); i++) {
double heading = strategy.getHeadingForIteration(i);
QueryResult result = generateValidPoint(last, strategy.getDistanceForIteration(i), heading, edgeFilter);
if (result == null) {
ghResponse.addError(new IllegalStateException("Could not find a valid point after " + maxRetries + " tries, for the point:" + last));
return Collections.emptyList();
}
last = result.getSnappedPoint();
queryResults.add(result);
}
queryResults.add(startQR);
return queryResults;
}
void setQueryResults(List<QueryResult> queryResults) {
this.queryResults = queryResults;
}
@Override
public List<Path> calcPaths(QueryGraph queryGraph, RoutingAlgorithmFactory algoFactory, AlgorithmOptions algoOpts) {
pathList = new ArrayList<>(queryResults.size() - 1);
AvoidEdgesWeighting avoidPathWeighting = new AvoidEdgesWeighting(algoOpts.getWeighting());
avoidPathWeighting.setEdgePenaltyFactor(5);
algoOpts = AlgorithmOptions.start(algoOpts).
algorithm(Parameters.Algorithms.ASTAR_BI).
weighting(avoidPathWeighting).build();
algoOpts.getHints().put(Algorithms.ASTAR_BI + ".epsilon", 2);
long visitedNodesSum = 0L;
QueryResult start = queryResults.get(0);
for (int qrIndex = 1; qrIndex < queryResults.size(); qrIndex++) {
RoutingAlgorithm algo = algoFactory.createAlgo(queryGraph, algoOpts);
// instead getClosestNode (which might be a virtual one and introducing unnecessary tails of the route)
// use next tower node -> getBaseNode or getAdjNode
// Later: remove potential route tail
QueryResult startQR = queryResults.get(qrIndex - 1);
int startNode = (startQR == start) ? startQR.getClosestNode() : startQR.getClosestEdge().getBaseNode();
QueryResult endQR = queryResults.get(qrIndex);
int endNode = (endQR == start) ? endQR.getClosestNode() : endQR.getClosestEdge().getBaseNode();
Path path = algo.calcPath(startNode, endNode);
visitedNodesSum += algo.getVisitedNodes();
pathList.add(path);
// it is important to avoid previously visited nodes for future paths
avoidPathWeighting.addEdges(path.calcEdges());
}
ghResponse.getHints().put("visited_nodes.sum", visitedNodesSum);
ghResponse.getHints().put("visited_nodes.average", (float) visitedNodesSum / (queryResults.size() - 1));
return pathList;
}
public void setPaths(List<Path> pathList) {
this.pathList = pathList;
}
@Override
public boolean isReady(PathMerger pathMerger, Translation tr) {
altResponse = new PathWrapper();
altResponse.setWaypoints(getWaypoints());
ghResponse.add(altResponse);
pathMerger.doWork(altResponse, pathList, tr);
// with potentially retrying, including generating new route points, for now disabled
return true;
}
private QueryResult generateValidPoint(GHPoint from, double distanceInMeters, double heading,
EdgeFilter edgeFilter) {
int tryCount = 0;
while (true) {
GHPoint generatedPoint = Helper.DIST_EARTH.projectCoordinate(from.getLat(), from.getLon(), distanceInMeters, heading);
QueryResult qr = locationIndex.findClosest(generatedPoint.getLat(), generatedPoint.getLon(), edgeFilter);
if (qr.isValid())
return qr;
tryCount++;
distanceInMeters *= 0.95;
if (tryCount >= maxRetries)
return null;
}
}
@Override
public int getMaxRetries() {
// with potentially retrying, including generating new route points, for now disabled
return 1;
}
}