/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (props, at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.opentripplanner.routing.algorithm.strategies; import com.google.common.collect.Iterables; import org.opentripplanner.common.geometry.SphericalDistanceLibrary; import org.opentripplanner.routing.algorithm.GenericDijkstra; import org.opentripplanner.routing.algorithm.TraverseVisitor; import org.opentripplanner.routing.core.RoutingRequest; import org.opentripplanner.routing.core.State; import org.opentripplanner.routing.edgetype.FreeEdge; import org.opentripplanner.routing.edgetype.StreetTransitLink; import org.opentripplanner.routing.graph.Edge; import org.opentripplanner.routing.graph.Vertex; import org.opentripplanner.routing.spt.ShortestPathTree; import org.opentripplanner.routing.vertextype.TransitStationStop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A Euclidean remaining weight strategy that takes into account transit boarding costs where applicable. * */ public class EuclideanRemainingWeightHeuristic implements RemainingWeightHeuristic { private static final long serialVersionUID = -5172878150967231550L; private static Logger LOG = LoggerFactory.getLogger(EuclideanRemainingWeightHeuristic.class); private double lat; private double lon; private boolean transit; private double walkReluctance; private double maxStreetSpeed; private double maxTransitSpeed; private double requiredWalkDistance; @Override public void initialize(RoutingRequest options, long abortTime) { RoutingRequest req = options; Vertex target = req.rctx.target; this.transit = req.modes.isTransit(); maxStreetSpeed = req.getStreetSpeedUpperBound(); maxTransitSpeed = req.getTransitSpeedUpperBound(); if (target.getDegreeIn() == 1) { Edge edge = Iterables.getOnlyElement(target.getIncoming()); if (edge instanceof FreeEdge) { target = edge.getFromVertex(); } } lat = target.getLat(); lon = target.getLon(); requiredWalkDistance = determineRequiredWalkDistance(req); walkReluctance = req.walkReluctance; } /** * On a non-transit trip, the remaining weight is simply distance / street speed. * On a transit trip, there are two cases: * (1) we're not on a transit vehicle. In this case, there are two possible ways to compute * the remaining distance, and we take whichever is smaller: * (a) walking distance / walking speed * (b) boarding cost + transit distance / transit speed (this is complicated a bit when * we know that there is some walking portion of the trip). * (2) we are on a transit vehicle, in which case the remaining weight is simply transit * distance / transit speed (no need for boarding cost), again considering any mandatory * walking. */ @Override public double estimateRemainingWeight (State s) { Vertex sv = s.getVertex(); double euclideanDistance = SphericalDistanceLibrary.fastDistance(sv.getLat(), sv.getLon(), lat, lon); if (transit) { if (euclideanDistance < requiredWalkDistance) { return walkReluctance * euclideanDistance / maxStreetSpeed; } /* Due to the above conditional, the following value is known to be positive. */ double transitWeight = (euclideanDistance - requiredWalkDistance) / maxTransitSpeed; double streetWeight = walkReluctance * (requiredWalkDistance / maxStreetSpeed); return transitWeight + streetWeight; } else { // all travel is on-street, no transit involved return walkReluctance * euclideanDistance / maxStreetSpeed; } } /** * Figure out the minimum amount of walking to reach the destination from transit. * This is done by doing a Dijkstra search for the first reachable transit stop. */ private double determineRequiredWalkDistance(final RoutingRequest req) { if (!transit) return 0; // required walk distance will be unused. RoutingRequest options = req.clone(); options.setArriveBy(!req.arriveBy); options.setRoutingContext(req.rctx.graph, req.rctx.fromVertex, req.rctx.toVertex); GenericDijkstra gd = new GenericDijkstra(options); State s = new State(options); gd.setHeuristic(new TrivialRemainingWeightHeuristic()); final ClosestStopTraverseVisitor visitor = new ClosestStopTraverseVisitor(); gd.traverseVisitor = visitor; gd.searchTerminationStrategy = new SearchTerminationStrategy() { @Override public boolean shouldSearchTerminate(Vertex origin, Vertex target, State current, ShortestPathTree spt, RoutingRequest traverseOptions) { return visitor.distanceToClosestStop != Double.POSITIVE_INFINITY; } }; gd.getShortestPathTree(s); return visitor.distanceToClosestStop; } private class ClosestStopTraverseVisitor implements TraverseVisitor { private double distanceToClosestStop = Double.POSITIVE_INFINITY; @Override public void visitEdge(Edge edge, State state) { } @Override public void visitEnqueue(State state) { } @Override public void visitVertex(State state) { Edge backEdge = state.getBackEdge(); if (backEdge instanceof StreetTransitLink) { Vertex backVertex = state.getBackState().getVertex(); distanceToClosestStop = SphericalDistanceLibrary.fastDistance( backVertex.getLat(), backVertex.getLon(), lat, lon); LOG.debug("Found closest stop to search target: {} at {}m", state.getVertex(), (int) distanceToClosestStop); } else if (state.getVertex() instanceof TransitStationStop && backEdge == null) { LOG.debug("Search target is a transit stop, no walking is required at end of trip"); distanceToClosestStop = 0; } } } @Override public void reset() {} @Override public void doSomeWork() {} }