/* 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 org.opentripplanner.common.geometry.DistanceLibrary; import org.opentripplanner.common.geometry.SphericalDistanceLibrary; import org.opentripplanner.routing.core.OptimizeType; import org.opentripplanner.routing.core.State; import org.opentripplanner.routing.core.TraverseMode; import org.opentripplanner.routing.core.RoutingRequest; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.Vertex; import org.opentripplanner.routing.vertextype.IntersectionVertex; /** * A Euclidean remaining weight strategy that takes into account transit boarding costs where applicable. * */ public class DefaultRemainingWeightHeuristic implements RemainingWeightHeuristic { private static final long serialVersionUID = -5172878150967231550L; private RoutingRequest options; private boolean useTransit = false; private double maxSpeed; private DistanceLibrary distanceLibrary = SphericalDistanceLibrary.getInstance(); private TransitLocalStreetService localStreetService; private double targetX; private double targetY; @Override public void initialize(State s, Vertex target) { this.options = s.getOptions(); this.useTransit = options.getModes().isTransit(); this.maxSpeed = getMaxSpeed(options); Graph graph = options.rctx.graph; localStreetService = graph.getService(TransitLocalStreetService.class); targetX = target.getX(); targetY = target.getY(); } /** * On a non-transit trip, the remaining weight is simply distance / 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 computeForwardWeight(State s, Vertex target) { Vertex sv = s.getVertex(); double euclideanDistance = distanceLibrary.fastDistance(sv.getY(), sv.getX(), targetY, targetX); if (useTransit) { double streetSpeed = options.getStreetSpeedUpperBound(); if (s.isAlightedLocal() || euclideanDistance < target.getDistanceToNearestTransitStop()) { // Search allows using transit, passenger is alighted local or within mandatory // walking distance of the target. We will not reach the target via transit. if (euclideanDistance + s.getWalkDistance() > options.getMaxWalkDistance()) { return -1; // impossible to reach destination } return options.walkReluctance * euclideanDistance / streetSpeed; } // Search allows using transit, passenger is not alighted local and is not within // mandatory walking distance of the target: It is possible we will reach the // destination using transit. Find lower bound on cost of this hypothetical trip. int boardCost; if (s.isOnboard()) { // onboard: we might not need any more boardings (remember this is a lower bound). boardCost = 0; } else { // offboard: we know that using transit to reach the destination would require at // least one boarding. boardCost = options.getBoardCostLowerBound(); if (s.isEverBoarded()) { // the boarding would be a transfer, because we've boarded before. boardCost += options.transferPenalty; if (localStreetService != null) { if (options.getMaxWalkDistance() - s.getWalkDistance() < euclideanDistance && sv instanceof IntersectionVertex && !localStreetService.transferrable(sv)) { return Double.POSITIVE_INFINITY; } } } } // Find how much mandatory walking is needed to use transit from here. // If the passenger is onboard, the second term is zero. double mandatoryWalkDistance = target.getDistanceToNearestTransitStop() + sv.getDistanceToNearestTransitStop(); double transitCost = (euclideanDistance - mandatoryWalkDistance) / maxSpeed + boardCost; double transitStreetCost = mandatoryWalkDistance * options.walkReluctance / streetSpeed; // Compare transit use with the cost of just walking all the way to the destination, // and return the lower of the two. return Math.min(transitCost + transitStreetCost, options.walkReluctance * euclideanDistance / streetSpeed); } else { // search disallows using transit: all travel is on-street return options.walkReluctance * euclideanDistance / maxSpeed; } } /** * computeForwardWeight and computeReverseWeight were identical (except that * computeReverseWeight did not have the localStreetService clause). They have been merged. */ @Override public double computeReverseWeight(State s, Vertex target) { return computeForwardWeight(s, target); } /** * Get the maximum expected speed over all modes. This should probably be moved to * RoutingRequest. */ public static double getMaxSpeed(RoutingRequest options) { if (options.getModes().contains(TraverseMode.TRANSIT)) { // assume that the max average transit speed over a hop is 10 m/s, which is roughly // true in Portland and NYC, but *not* true on highways return 10; } else { if (options.optimize == OptimizeType.QUICK) { return options.getStreetSpeedUpperBound(); } else { // assume that the best route is no more than 10 times better than // the as-the-crow-flies flat base route. return options.getStreetSpeedUpperBound() * 10; } } } @Override public void reset() {} @Override public void abort() {} }