/* 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 (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.core;
import com.google.common.base.Objects;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.gtfs.model.Route;
import org.onebusaway.gtfs.model.Trip;
import org.opentripplanner.api.parameter.QualifiedModeSet;
import org.opentripplanner.common.MavenVersion;
import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.common.model.NamedPlace;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.error.TrivialPathException;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.request.BannedStopSet;
import org.opentripplanner.routing.spt.DominanceFunction;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.util.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
/**
* A trip planning request. Some parameters may not be honored by the trip planner for some or all itineraries.
* For example, maxWalkDistance may be relaxed if the alternative is to not provide a route.
*
* All defaults should be specified here in the RoutingRequest, NOT as annotations on query parameters in web services
* that create RoutingRequests. This establishes a priority chain for default values:
* RoutingRequest field initializers, then JSON router config, then query parameters.
*/
public class RoutingRequest implements Cloneable, Serializable {
private static final long serialVersionUID = MavenVersion.VERSION.getUID();
private static final Logger LOG = LoggerFactory.getLogger(RoutingRequest.class);
/**
* The model that computes turn/traversal costs.
* TODO: move this to the Router or the Graph if it doesn't clutter the code too much
*/
public IntersectionTraversalCostModel traversalCostModel = new SimpleIntersectionTraversalCostModel();
/* FIELDS UNIQUELY IDENTIFYING AN SPT REQUEST */
/** The complete list of incoming query parameters. */
public final HashMap<String, String> parameters = new HashMap<String, String>();
/** The router ID -- internal ID to switch between router implementation (or graphs) */
public String routerId = "";
/** The start location */
public GenericLocation from;
/** The end location */
public GenericLocation to;
/** An ordered list of intermediate locations to be visited. */
public List<GenericLocation> intermediatePlaces;
/**
* The maximum distance (in meters) the user is willing to walk for access/egress legs.
* Defaults to unlimited.
*/
public double maxWalkDistance = Double.MAX_VALUE;
/**
* The maximum distance (in meters) the user is willing to walk for transfer legs.
* Defaults to unlimited. Currently set to be the same value as maxWalkDistance.
*/
public double maxTransferWalkDistance = Double.MAX_VALUE;
/**
* The maximum time (in seconds) of pre-transit travel when using drive-to-transit (park and
* ride or kiss and ride). By default limited to 30 minutes driving, because if it's unlimited on
* large graphs the search becomes very slow.
*/
public int maxPreTransitTime = 30 * 60;
/** The worst possible time (latest for depart-by and earliest for arrive-by) to accept */
public long worstTime = Long.MAX_VALUE;
/** The worst possible weight that we will accept when planning a trip. */
public double maxWeight = Double.MAX_VALUE;
/** The maximum duration of a returned itinerary, in hours. */
public double maxHours = Double.MAX_VALUE;
/** The set of TraverseModes that a user is willing to use. Defaults to WALK | TRANSIT. */
public TraverseModeSet modes = new TraverseModeSet("TRANSIT,WALK"); // defaults in constructor overwrite this
/** The set of characteristics that the user wants to optimize for -- defaults to QUICK, or optimize for transit time. */
public OptimizeType optimize = OptimizeType.QUICK;
// TODO this should be completely removed and done only with individual cost parameters
// Also: apparently OptimizeType only affects BICYCLE mode traversal of street segments.
// If this is the case it should be very well documented and carried over into the Enum name.
/** The epoch date/time that the trip should depart (or arrive, for requests where arriveBy is true) */
public long dateTime = new Date().getTime() / 1000;
/** Whether the trip should depart at dateTime (false, the default), or arrive at dateTime. */
public boolean arriveBy = false;
/** Whether the trip must be wheelchair accessible. */
public boolean wheelchairAccessible = false;
/** The maximum number of itineraries to return. */
public int numItineraries = 3;
/** The maximum slope of streets for wheelchair trips. */
public double maxSlope = 0.0833333333333; // ADA max wheelchair ramp slope is a good default.
/** Whether the planner should return intermediate stops lists for transit legs. */
public boolean showIntermediateStops = false;
/** max walk/bike speed along streets, in meters per second */
public double walkSpeed;
public double bikeSpeed;
public double carSpeed;
public Locale locale = new Locale("en", "US");
/**
* An extra penalty added on transfers (i.e. all boardings except the first one).
* Not to be confused with bikeBoardCost and walkBoardCost, which are the cost of boarding a
* vehicle with and without a bicycle. The boardCosts are used to model the 'usual' perceived
* cost of using a transit vehicle, and the transferPenalty is used when a user requests even
* less transfers. In the latter case, we don't actually optimize for fewest transfers, as this
* can lead to absurd results. Consider a trip in New York from Grand Army
* Plaza (the one in Brooklyn) to Kalustyan's at noon. The true lowest transfers route is to
* wait until midnight, when the 4 train runs local the whole way. The actual fastest route is
* the 2/3 to the 4/5 at Nevins to the 6 at Union Square, which takes half an hour.
* Even someone optimizing for fewest transfers doesn't want to wait until midnight. Maybe they
* would be willing to walk to 7th Ave and take the Q to Union Square, then transfer to the 6.
* If this takes less than optimize_transfer_penalty seconds, then that's what we'll return.
*/
public int transferPenalty = 0;
/** A multiplier for how bad walking is, compared to being in transit for equal lengths of time.
* Defaults to 2. Empirically, values between 10 and 20 seem to correspond well to the concept
* of not wanting to walk too much without asking for totally ridiculous itineraries, but this
* observation should in no way be taken as scientific or definitive. Your mileage may vary.*/
public double walkReluctance = 2.0;
/** Used instead of walk reluctance for stairs */
public double stairsReluctance = 2.0;
/** Multiplicative factor on expected turning time. */
public double turnReluctance = 1.0;
/**
* How long does it take to get an elevator, on average (actually, it probably should be a bit *more* than average, to prevent optimistic trips)?
* Setting it to "seems like forever," while accurate, will probably prevent OTP from working correctly.
*/
// TODO: how long does it /really/ take to get an elevator?
public int elevatorBoardTime = 90;
/** What is the cost of boarding an elevator? */
public int elevatorBoardCost = 90;
/** How long does it take to advance one floor on an elevator? */
public int elevatorHopTime = 20;
/** What is the cost of travelling one floor on an elevator? */
public int elevatorHopCost = 20;
// it is assumed that getting off an elevator is completely free
/** Time to get on and off your own bike */
public int bikeSwitchTime;
/** Cost of getting on and off your own bike */
public int bikeSwitchCost;
/** Time to rent a bike */
public int bikeRentalPickupTime = 60;
/**
* Cost of renting a bike. The cost is a bit more than actual time to model the associated cost and trouble.
*/
public int bikeRentalPickupCost = 120;
/** Time to drop-off a rented bike */
public int bikeRentalDropoffTime = 30;
/** Cost of dropping-off a rented bike */
public int bikeRentalDropoffCost = 30;
/** Time to park a bike */
public int bikeParkTime = 60;
/** Cost of parking a bike. */
public int bikeParkCost = 120;
/**
* Time to park a car in a park and ride, w/o taking into account driving and walking cost
* (time to park, switch off, pick your stuff, lock the car, etc...)
*/
public int carDropoffTime = 120;
/**
* How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. The default value treats wait and on-vehicle
* time as the same.
*
* It may be tempting to set this higher than walkReluctance (as studies often find this kind of preferences among
* riders) but the planner will take this literally and walk down a transit line to avoid waiting at a stop.
* This used to be set less than 1 (0.95) which would make waiting offboard preferable to waiting onboard in an
* interlined trip. That is also undesirable.
*
* If we only tried the shortest possible transfer at each stop to neighboring stop patterns, this problem could disappear.
*/
public double waitReluctance = 1.0;
/** How much less bad is waiting at the beginning of the trip (replaces waitReluctance on the first boarding) */
public double waitAtBeginningFactor = 0.4;
/** This prevents unnecessary transfers by adding a cost for boarding a vehicle. */
public int walkBoardCost = 60 * 10;
/** Separate cost for boarding a vehicle with a bicycle, which is more difficult than on foot. */
public int bikeBoardCost = 60 * 10;
/** Do not use certain named routes */
public RouteMatcher bannedRoutes = RouteMatcher.emptyMatcher();
/** Do not use certain named agencies */
public HashSet<String> bannedAgencies = new HashSet<String>();
/** Do not use certain trips */
public HashMap<AgencyAndId, BannedStopSet> bannedTrips = new HashMap<AgencyAndId, BannedStopSet>();
/** Do not use certain stops. See for more information the bannedStops property in the RoutingResource class. */
public StopMatcher bannedStops = StopMatcher.emptyMatcher();
/** Do not use certain stops. See for more information the bannedStopsHard property in the RoutingResource class. */
public StopMatcher bannedStopsHard = StopMatcher.emptyMatcher();
/** Set of preferred routes by user. */
public RouteMatcher preferredRoutes = RouteMatcher.emptyMatcher();
/** Set of preferred agencies by user. */
public HashSet<String> preferredAgencies = new HashSet<String>();
/**
* Penalty added for using every route that is not preferred if user set any route as preferred. We return number of seconds that we are willing
* to wait for preferred route.
*/
public int otherThanPreferredRoutesPenalty = 300;
/** Set of unpreferred routes for given user. */
public RouteMatcher unpreferredRoutes = RouteMatcher.emptyMatcher();
/** Set of unpreferred agencies for given user. */
public HashSet<String> unpreferredAgencies = new HashSet<String>();
/**
* Penalty added for using every unpreferred route. We return number of seconds that we are willing to wait for preferred route.
*/
public int useUnpreferredRoutesPenalty = 300;
/**
* A global minimum transfer time (in seconds) that specifies the minimum amount of time that must pass between exiting one transit vehicle and
* boarding another. This time is in addition to time it might take to walk between transit stops. This time should also be overridden by specific
* transfer timing information in transfers.txt
*/
// initialize to zero so this does not inadvertently affect tests, and let Planner handle defaults
public int transferSlack = 0;
/** Invariant: boardSlack + alightSlack <= transferSlack. */
public int boardSlack = 0;
public int alightSlack = 0;
public int maxTransfers = 2;
/**
* Extensions to the trip planner will require additional traversal options beyond the default
* set. We provide an extension point for adding arbitrary parameters with an
* extension-specific key.
*/
public Map<Object, Object> extensions = new HashMap<Object, Object>();
/** Penalty for using a non-preferred transfer */
public int nonpreferredTransferPenalty = 180;
/**
* For the bike triangle, how important time is.
* triangleTimeFactor+triangleSlopeFactor+triangleSafetyFactor == 1
*/
public double triangleTimeFactor;
/** For the bike triangle, how important slope is */
public double triangleSlopeFactor;
/** For the bike triangle, how important safety is */
public double triangleSafetyFactor;
/** Options specifically for the case that you are walking a bicycle. */
public RoutingRequest bikeWalkingOptions;
/** This is true when a GraphPath is being traversed in reverse for optimization purposes. */
public boolean reverseOptimizing = false;
/** when true, do not use goal direction or stop at the target, build a full SPT */
public boolean batch = false;
/**
* Whether or not bike rental availability information will be used to plan bike rental trips
*/
public boolean useBikeRentalAvailabilityInformation = false;
/**
* The maximum wait time in seconds the user is willing to delay trip start. Only effective in Analyst.
*/
public long clampInitialWait = -1;
/**
* When true, reverse optimize this search on the fly whenever needed, rather than reverse-optimizing the entire path when it's done.
*/
public boolean reverseOptimizeOnTheFly = false;
/**
* If true, cost turns as they would be in a country where driving occurs on the right; otherwise, cost them as they would be in a country where
* driving occurs on the left.
*/
public boolean driveOnRight = true;
/**
* The deceleration speed of an automobile, in meters per second per second.
*/
// 2.9 m/s/s: 65 mph - 0 mph in 10 seconds
public double carDecelerationSpeed = 2.9;
/**
* The acceleration speed of an automobile, in meters per second per second.
*/
// 2.9 m/s/s: 0 mph to 65 mph in 10 seconds
public double carAccelerationSpeed = 2.9;
/**
* When true, realtime updates are ignored during this search.
*/
public boolean ignoreRealtimeUpdates = false;
/**
* If true, the remaining weight heuristic is disabled. Currently only implemented for the long
* distance path service.
*/
public boolean disableRemainingWeightHeuristic = false;
/**
* The routing context used to actually carry out this search. It is important to build States from TraverseOptions
* rather than RoutingContexts,and just keep a reference to the context in the TraverseOptions, rather than using
* RoutingContexts for everything because in some testing and graph building situations we need to build a bunch of
* initial states with different times and vertices from a single TraverseOptions, without setting all the transit
* context or building temporary vertices (with all the exception-throwing checks that entails).
*
* While they are conceptually separate, TraverseOptions does maintain a reference to its accompanying
* RoutingContext (and vice versa) so that both do not need to be passed/injected separately into tight inner loops
* within routing algorithms. These references should be set to null when the request scope is torn down -- the
* routing context becomes irrelevant at that point, since temporary graph elements have been removed and the graph
* may have been reloaded.
*/
public RoutingContext rctx;
/** A transit stop that this trip must start from */
public AgencyAndId startingTransitStopId;
/** A trip where this trip must start from (depart-onboard routing) */
public AgencyAndId startingTransitTripId;
public boolean walkingBike;
public boolean softWalkLimiting = true;
public boolean softPreTransitLimiting = true;
public double softWalkPenalty = 60.0; // a jump in cost when stepping over the walking limit
public double softWalkOverageRate = 5.0; // a jump in cost for every meter over the walking limit
public double preTransitPenalty = 300.0; // a jump in cost when stepping over the pre-transit time limit
public double preTransitOverageRate = 10.0; // a jump in cost for every second over the pre-transit time limit
/*
Additional flags affecting mode transitions.
This is a temporary solution, as it only covers parking and rental at the beginning of the trip.
*/
public boolean allowBikeRental = false;
public boolean bikeParkAndRide = false;
public boolean parkAndRide = false;
public boolean kissAndRide = false;
/* Whether we are in "long-distance mode". This is currently a server-wide setting, but it could be made per-request. */
// TODO remove
public boolean longDistance = false;
/** Should traffic congestion be considered when driving? */
public boolean useTraffic = true;
/** The function that compares paths converging on the same vertex to decide which ones continue to be explored. */
public DominanceFunction dominanceFunction = new DominanceFunction.Pareto();
/** Accept only paths that use transit (no street-only paths). */
public boolean onlyTransitTrips = false;
/** Option to disable the default filtering of GTFS-RT alerts by time. */
public boolean disableAlertFiltering = false;
/** Whether to apply the ellipsoid->geoid offset to all elevations in the response */
public boolean geoidElevation = false;
/** Saves split edge which can be split on origin/destination search
*
* This is used so that TrivialPathException is thrown if origin and destination search would split the same edge
*/
private StreetEdge splitEdge = null;
/* CONSTRUCTORS */
/** Constructor for options; modes defaults to walk and transit */
public RoutingRequest() {
// http://en.wikipedia.org/wiki/Walking
walkSpeed = 1.33; // 1.33 m/s ~ 3mph, avg. human speed
bikeSpeed = 5; // 5 m/s, ~11 mph, a random bicycling speed
// http://en.wikipedia.org/wiki/Speed_limit
carSpeed = 40; // 40 m/s, 144 km/h, above the maximum (finite) driving speed limit worldwide
setModes(new TraverseModeSet(TraverseMode.WALK, TraverseMode.TRANSIT));
bikeWalkingOptions = this;
// So that they are never null.
from = new GenericLocation();
to = new GenericLocation();
}
public RoutingRequest(TraverseModeSet modes) {
this();
this.setModes(modes);
}
public RoutingRequest(QualifiedModeSet qmodes) {
this();
qmodes.applyToRoutingRequest(this);
}
public RoutingRequest(String qmodes) {
this();
new QualifiedModeSet(qmodes).applyToRoutingRequest(this);
}
public RoutingRequest(TraverseMode mode) {
this();
this.setModes(new TraverseModeSet(mode));
}
public RoutingRequest(TraverseMode mode, OptimizeType optimize) {
this(new TraverseModeSet(mode), optimize);
}
public RoutingRequest(TraverseModeSet modeSet, OptimizeType optimize) {
this();
this.optimize = optimize;
this.setModes(modeSet);
}
/* ACCESSOR/SETTER METHODS */
public boolean transitAllowed() {
return modes.isTransit();
}
public long getSecondsSinceEpoch() {
return dateTime;
}
public void setArriveBy(boolean arriveBy) {
this.arriveBy = arriveBy;
bikeWalkingOptions.arriveBy = arriveBy;
if (worstTime == Long.MAX_VALUE || worstTime == 0)
worstTime = arriveBy ? 0 : Long.MAX_VALUE;
}
public void setMode(TraverseMode mode) {
setModes(new TraverseModeSet(mode));
}
public void setModes(TraverseModeSet modes) {
this.modes = modes;
if (modes.getBicycle()) {
// This alternate routing request is used when we get off a bike to take a shortcut and are
// walking alongside the bike. FIXME why are we only copying certain fields instead of cloning the request?
bikeWalkingOptions = new RoutingRequest();
bikeWalkingOptions.setArriveBy(this.arriveBy);
bikeWalkingOptions.maxWalkDistance = maxWalkDistance;
bikeWalkingOptions.maxPreTransitTime = maxPreTransitTime;
bikeWalkingOptions.walkSpeed = walkSpeed * 0.8; // walking bikes is slow
bikeWalkingOptions.walkReluctance = walkReluctance * 2.7; // and painful
bikeWalkingOptions.optimize = optimize;
bikeWalkingOptions.modes = modes.clone();
bikeWalkingOptions.modes.setBicycle(false);
bikeWalkingOptions.modes.setWalk(true);
bikeWalkingOptions.walkingBike = true;
bikeWalkingOptions.bikeSwitchTime = bikeSwitchTime;
bikeWalkingOptions.bikeSwitchCost = bikeSwitchCost;
bikeWalkingOptions.stairsReluctance = stairsReluctance * 5; // carrying bikes on stairs is awful
} else if (modes.getCar()) {
bikeWalkingOptions = new RoutingRequest();
bikeWalkingOptions.setArriveBy(this.arriveBy);
bikeWalkingOptions.maxWalkDistance = maxWalkDistance;
bikeWalkingOptions.maxPreTransitTime = maxPreTransitTime;
bikeWalkingOptions.modes = modes.clone();
bikeWalkingOptions.modes.setBicycle(false);
bikeWalkingOptions.modes.setWalk(true);
}
}
public void setOptimize(OptimizeType optimize) {
this.optimize = optimize;
bikeWalkingOptions.optimize = optimize;
}
public void setWheelchairAccessible(boolean wheelchairAccessible) {
this.wheelchairAccessible = wheelchairAccessible;
}
/**
* only allow traversal by the specified mode; don't allow walking bikes. This is used during contraction to reduce the number of possible paths.
*/
public void freezeTraverseMode() {
bikeWalkingOptions = clone();
bikeWalkingOptions.bikeWalkingOptions = new RoutingRequest(new TraverseModeSet());
}
/**
* Add an extension parameter with the specified key. Extensions allow you to add arbitrary traversal options.
*/
public void putExtension(Object key, Object value) {
extensions.put(key, value);
}
/** Determine if a particular extension parameter is present for the specified key. */
public boolean containsExtension(Object key) {
return extensions.containsKey(key);
}
/** Get the extension parameter with the specified key. */
@SuppressWarnings("unchecked")
public <T> T getExtension(Object key) {
return (T) extensions.get(key);
}
/** Returns the model that computes the cost of intersection traversal. */
public IntersectionTraversalCostModel getIntersectionTraversalCostModel() {
return traversalCostModel;
}
/** @return the (soft) maximum walk distance */
// If transit is not to be used and this is a point to point search
// or one with soft walk limiting, disable walk limit.
public double getMaxWalkDistance() {
if (modes.isTransit() || (batch && !softWalkLimiting)) {
return maxWalkDistance;
} else {
return Double.MAX_VALUE;
}
}
public void setWalkBoardCost(int walkBoardCost) {
if (walkBoardCost < 0) {
this.walkBoardCost = 0;
}
else {
this.walkBoardCost = walkBoardCost;
}
}
public void setBikeBoardCost(int bikeBoardCost) {
if (bikeBoardCost < 0) {
this.bikeBoardCost = 0;
}
else {
this.bikeBoardCost = bikeBoardCost;
}
}
public void setPreferredAgencies(String s) {
if (s != null && !s.equals(""))
preferredAgencies = new HashSet<String>(Arrays.asList(s.split(",")));
}
public void setPreferredRoutes(String s) {
if (s != null && !s.equals(""))
preferredRoutes = RouteMatcher.parse(s);
else
preferredRoutes = RouteMatcher.emptyMatcher();
}
public void setOtherThanPreferredRoutesPenalty(int penalty) {
if(penalty < 0) penalty = 0;
this.otherThanPreferredRoutesPenalty = penalty;
}
public void setUnpreferredAgencies(String s) {
if (s != null && !s.equals(""))
unpreferredAgencies = new HashSet<String>(Arrays.asList(s.split(",")));
}
public void setUnpreferredRoutes(String s) {
if (s != null && !s.equals(""))
unpreferredRoutes = RouteMatcher.parse(s);
else
unpreferredRoutes = RouteMatcher.emptyMatcher();
}
public void setBannedRoutes(String s) {
if (s != null && !s.equals(""))
bannedRoutes = RouteMatcher.parse(s);
else
bannedRoutes = RouteMatcher.emptyMatcher();
}
public void setBannedStops(String s) {
if (s != null && !s.equals("")) {
bannedStops = StopMatcher.parse(s);
}
else {
bannedStops = StopMatcher.emptyMatcher();
}
}
public void setBannedStopsHard(String s) {
if (s != null && !s.equals("")) {
bannedStopsHard = StopMatcher.parse(s);
}
else {
bannedStopsHard = StopMatcher.emptyMatcher();
}
}
public void setBannedAgencies(String s) {
if (s != null && !s.equals(""))
bannedAgencies = new HashSet<String>(Arrays.asList(s.split(",")));
}
public final static int MIN_SIMILARITY = 1000;
public void setFromString(String from) {
this.from = GenericLocation.fromOldStyleString(from);
}
public void setToString(String to) {
this.to = GenericLocation.fromOldStyleString(to);
}
/**
* Clear the allowed modes.
*/
public void clearModes() {
modes.clear();
}
/**
* Add a TraverseMode to the set of allowed modes.
*/
public void addMode(TraverseMode mode) {
modes.setMode(mode, true);
}
/**
* Add multiple modes to the set of allowed modes.
*/
public void addMode(List<TraverseMode> mList) {
for (TraverseMode m : mList) {
addMode(m);
}
}
public Date getDateTime() {
return new Date(dateTime * 1000);
}
public void setDateTime(Date dateTime) {
this.dateTime = dateTime.getTime() / 1000;
}
public void setDateTime(String date, String time, TimeZone tz) {
Date dateObject = DateUtils.toDate(date, time, tz);
setDateTime(dateObject);
}
public int getNumItineraries() {
if (modes.isTransit()) {
return numItineraries;
} else {
// If transit is not to be used, only search for one itinerary.
return 1;
}
}
public void setNumItineraries(int numItineraries) {
this.numItineraries = numItineraries;
}
public String toString() {
return toString(" ");
}
public String toString(String sep) {
return from + sep + to + sep + getMaxWalkDistance() + sep + getDateTime() + sep
+ arriveBy + sep + optimize + sep + modes.getAsStr() + sep
+ getNumItineraries();
}
public void removeMode(TraverseMode mode) {
modes.setMode(mode, false);
}
/**
* Sets intermediatePlaces by parsing GenericLocations from a list of string.
*/
public void setIntermediatePlacesFromStrings(List<String> intermediates) {
this.intermediatePlaces = new ArrayList<GenericLocation>(intermediates.size());
for (String place : intermediates) {
intermediatePlaces.add(GenericLocation.fromOldStyleString(place));
}
}
/** Clears any intermediate places from this request. */
public void clearIntermediatePlaces() {
if (this.intermediatePlaces != null) {
this.intermediatePlaces.clear();
}
}
/**
* Returns true if there are any intermediate places set.
*/
public boolean hasIntermediatePlaces() {
return this.intermediatePlaces != null && this.intermediatePlaces.size() > 0;
}
/**
* Adds a GenericLocation to the end of the intermediatePlaces list. Will initialize intermediatePlaces if it is null.
*/
public void addIntermediatePlace(GenericLocation location) {
if (this.intermediatePlaces == null) {
this.intermediatePlaces = new ArrayList<GenericLocation>();
}
this.intermediatePlaces.add(location);
}
public void setTriangleSafetyFactor(double triangleSafetyFactor) {
this.triangleSafetyFactor = triangleSafetyFactor;
bikeWalkingOptions.triangleSafetyFactor = triangleSafetyFactor;
}
public void setTriangleSlopeFactor(double triangleSlopeFactor) {
this.triangleSlopeFactor = triangleSlopeFactor;
bikeWalkingOptions.triangleSlopeFactor = triangleSlopeFactor;
}
public void setTriangleTimeFactor(double triangleTimeFactor) {
this.triangleTimeFactor = triangleTimeFactor;
bikeWalkingOptions.triangleTimeFactor = triangleTimeFactor;
}
public NamedPlace getFromPlace() {
return this.from.getNamedPlace();
}
public NamedPlace getToPlace() {
return this.to.getNamedPlace();
}
/* INSTANCE METHODS */
@SuppressWarnings("unchecked")
@Override
public RoutingRequest clone() {
try {
RoutingRequest clone = (RoutingRequest) super.clone();
clone.bannedRoutes = bannedRoutes.clone();
clone.bannedTrips = (HashMap<AgencyAndId, BannedStopSet>) bannedTrips.clone();
clone.bannedStops = bannedStops.clone();
clone.bannedStopsHard = bannedStopsHard.clone();
if (this.bikeWalkingOptions != this)
clone.bikeWalkingOptions = this.bikeWalkingOptions.clone();
else
clone.bikeWalkingOptions = clone;
return clone;
} catch (CloneNotSupportedException e) {
/* this will never happen since our super is the cloneable object */
throw new RuntimeException(e);
}
}
public RoutingRequest reversedClone() {
RoutingRequest ret = this.clone();
ret.setArriveBy(!ret.arriveBy);
ret.reverseOptimizing = !ret.reverseOptimizing; // this is not strictly correct
ret.useBikeRentalAvailabilityInformation = false;
return ret;
}
public void setRoutingContext(Graph graph) {
if (rctx == null) {
// graphService.getGraph(routerId)
this.rctx = new RoutingContext(this, graph);
// check after back reference is established, to allow temp edge cleanup on exceptions
this.rctx.check();
} else {
if (rctx.graph == graph) {
LOG.debug("keeping existing routing context");
return;
} else {
LOG.error("attempted to reset routing context using a different graph");
return;
}
}
}
/** For use in tests. Force RoutingContext to specific vertices rather than making temp edges. */
public void setRoutingContext(Graph graph, Edge fromBackEdge, Vertex from, Vertex to) {
// normally you would want to tear down the routing context...
// but this method is mostly used in tests, and teardown interferes with testHalfEdges
// FIXME here, or in test, and/or in other places like TSP that use this method
// if (rctx != null)
// this.rctx.destroy();
this.rctx = new RoutingContext(this, graph, from, to);
this.rctx.originBackEdge = fromBackEdge;
}
public void setRoutingContext(Graph graph, Vertex from, Vertex to) {
setRoutingContext(graph, null, from, to);
}
/** For use in tests. Force RoutingContext to specific vertices rather than making temp edges. */
public void setRoutingContext(Graph graph, String from, String to) {
this.setRoutingContext(graph, graph.getVertex(from), graph.getVertex(to));
}
/** Used in internals API. Make a RoutingContext with no origin or destination vertices specified. */
public void setDummyRoutingContext(Graph graph) {
this.setRoutingContext(graph, "", "");
}
public RoutingContext getRoutingContext() {
return this.rctx;
}
/**
* Equality does not mean that the fields of the two RoutingRequests are identical, but that they will produce the same SPT. This is particularly
* important when the batch field is set to 'true'. Does not consider the RoutingContext, to allow SPT caching. Intermediate places are also not
* included because the TSP solver will factor a single intermediate places routing request into several routing requests without intermediates
* before searching.
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof RoutingRequest))
return false;
RoutingRequest other = (RoutingRequest) o;
if (this.batch != other.batch)
return false;
boolean endpointsMatch;
if (this.batch) {
if (this.arriveBy) {
endpointsMatch = to.equals(other.to);
} else {
endpointsMatch = from.equals(other.from);
}
} else {
endpointsMatch = ((from == null && other.from == null) || from.equals(other.from))
&& ((to == null && other.to == null) || to.equals(other.to));
}
return endpointsMatch
&& dateTime == other.dateTime
&& arriveBy == other.arriveBy
&& numItineraries == other.numItineraries // should only apply in non-batch?
&& walkSpeed == other.walkSpeed
&& bikeSpeed == other.bikeSpeed
&& carSpeed == other.carSpeed
&& maxWeight == other.maxWeight
&& worstTime == other.worstTime
&& maxTransfers == other.maxTransfers
&& modes.equals(other.modes)
&& wheelchairAccessible == other.wheelchairAccessible
&& optimize.equals(other.optimize)
&& maxWalkDistance == other.maxWalkDistance
&& maxTransferWalkDistance == other.maxTransferWalkDistance
&& maxPreTransitTime == other.maxPreTransitTime
&& transferPenalty == other.transferPenalty
&& maxSlope == other.maxSlope
&& walkReluctance == other.walkReluctance
&& waitReluctance == other.waitReluctance
&& waitAtBeginningFactor == other.waitAtBeginningFactor
&& walkBoardCost == other.walkBoardCost
&& bikeBoardCost == other.bikeBoardCost
&& bannedRoutes.equals(other.bannedRoutes)
&& bannedTrips.equals(other.bannedTrips)
&& preferredRoutes.equals(other.preferredRoutes)
&& unpreferredRoutes.equals(other.unpreferredRoutes)
&& transferSlack == other.transferSlack
&& boardSlack == other.boardSlack
&& alightSlack == other.alightSlack
&& nonpreferredTransferPenalty == other.nonpreferredTransferPenalty
&& otherThanPreferredRoutesPenalty == other.otherThanPreferredRoutesPenalty
&& useUnpreferredRoutesPenalty == other.useUnpreferredRoutesPenalty
&& triangleSafetyFactor == other.triangleSafetyFactor
&& triangleSlopeFactor == other.triangleSlopeFactor
&& triangleTimeFactor == other.triangleTimeFactor
&& stairsReluctance == other.stairsReluctance
&& elevatorBoardTime == other.elevatorBoardTime
&& elevatorBoardCost == other.elevatorBoardCost
&& elevatorHopTime == other.elevatorHopTime
&& elevatorHopCost == other.elevatorHopCost
&& bikeSwitchTime == other.bikeSwitchTime
&& bikeSwitchCost == other.bikeSwitchCost
&& bikeRentalPickupTime == other.bikeRentalPickupTime
&& bikeRentalPickupCost == other.bikeRentalPickupCost
&& bikeRentalDropoffTime == other.bikeRentalDropoffTime
&& bikeRentalDropoffCost == other.bikeRentalDropoffCost
&& useBikeRentalAvailabilityInformation == other.useBikeRentalAvailabilityInformation
&& extensions.equals(other.extensions)
&& clampInitialWait == other.clampInitialWait
&& reverseOptimizeOnTheFly == other.reverseOptimizeOnTheFly
&& ignoreRealtimeUpdates == other.ignoreRealtimeUpdates
&& disableRemainingWeightHeuristic == other.disableRemainingWeightHeuristic
&& Objects.equal(startingTransitTripId, other.startingTransitTripId)
&& useTraffic == other.useTraffic
&& disableAlertFiltering == other.disableAlertFiltering
&& geoidElevation == other.geoidElevation;
}
/**
* Equality and hashCode should not consider the routing context, to allow SPT caching.
* When adding fields to the hash code, pick a random large prime number that's not yet in use.
*/
@Override
public int hashCode() {
int hashCode = new Double(walkSpeed).hashCode() + new Double(bikeSpeed).hashCode()
+ new Double(carSpeed).hashCode() + new Double(maxWeight).hashCode()
+ (int) (worstTime & 0xffffffff) + modes.hashCode()
+ (arriveBy ? 8966786 : 0) + (wheelchairAccessible ? 731980 : 0)
+ optimize.hashCode() + new Double(maxWalkDistance).hashCode()
+ new Double(maxTransferWalkDistance).hashCode()
+ new Double(transferPenalty).hashCode() + new Double(maxSlope).hashCode()
+ new Double(walkReluctance).hashCode() + new Double(waitReluctance).hashCode()
+ new Double(waitAtBeginningFactor).hashCode() * 15485863
+ walkBoardCost + bikeBoardCost + bannedRoutes.hashCode()
+ bannedTrips.hashCode() * 1373 + transferSlack * 20996011
+ (int) nonpreferredTransferPenalty + (int) transferPenalty * 163013803
+ new Double(triangleSafetyFactor).hashCode() * 195233277
+ new Double(triangleSlopeFactor).hashCode() * 136372361
+ new Double(triangleTimeFactor).hashCode() * 790052899
+ new Double(stairsReluctance).hashCode() * 315595321
+ maxPreTransitTime * 63061489
+ new Long(clampInitialWait).hashCode() * 209477
+ new Boolean(reverseOptimizeOnTheFly).hashCode() * 95112799
+ new Boolean(ignoreRealtimeUpdates).hashCode() * 154329
+ new Boolean(disableRemainingWeightHeuristic).hashCode() * 193939
+ new Boolean(useTraffic).hashCode() * 10169;
if (batch) {
hashCode *= -1;
// batch mode, only one of two endpoints matters
if (arriveBy) {
hashCode += to.hashCode() * 1327144003;
} else {
hashCode += from.hashCode() * 524287;
}
hashCode += numItineraries; // why is this only present here?
} else {
// non-batch, both endpoints matter
hashCode += from.hashCode() * 524287;
hashCode += to.hashCode() * 1327144003;
}
return hashCode;
}
/** Tear down any routing context (remove temporary edges from edge lists) */
public void cleanup() {
if (this.rctx == null)
LOG.warn("routing context was not set, cannot destroy it.");
else {
rctx.destroy();
LOG.debug("routing context destroyed");
}
}
/**
* @param mode
* @return The road speed for a specific traverse mode.
*/
public double getSpeed(TraverseMode mode) {
switch (mode) {
case WALK:
return walkSpeed;
case BICYCLE:
return bikeSpeed;
case CAR:
return carSpeed;
default:
break;
}
throw new IllegalArgumentException("getSpeed(): Invalid mode " + mode);
}
/** @return The highest speed for all possible road-modes. */
public double getStreetSpeedUpperBound() {
// Assume carSpeed > bikeSpeed > walkSpeed
if (modes.getCar())
return carSpeed;
if (modes.getBicycle())
return bikeSpeed;
return walkSpeed;
}
/**
* @param mode
* @return The board cost for a specific traverse mode.
*/
public int getBoardCost(TraverseMode mode) {
if (mode == TraverseMode.BICYCLE)
return bikeBoardCost;
// I assume you can't bring your car in the bus
return walkBoardCost;
}
/** @return The lower boarding cost for all possible road-modes. */
public int getBoardCostLowerBound() {
// Assume walkBoardCost < bikeBoardCost
if (modes.getWalk())
return walkBoardCost;
return bikeBoardCost;
}
/**
* @return The time it actually takes to board a vehicle. Could be significant eg. on airplanes and ferries
*/
public int getBoardTime(TraverseMode transitMode) {
Integer i = this.rctx.graph.boardTimes.get(transitMode);
return i == null ? 0 : i;
}
/**
* @return The time it actually takes to alight a vehicle. Could be significant eg. on airplanes and ferries
*/
public int getAlightTime(TraverseMode transitMode) {
Integer i = this.rctx.graph.alightTimes.get(transitMode);
return i == null ? 0 : i;
}
private String getRouteOrAgencyStr(HashSet<String> strings) {
StringBuilder builder = new StringBuilder();
for (String agency : strings) {
builder.append(agency);
builder.append(",");
}
if (builder.length() > 0) {
// trim trailing comma
builder.setLength(builder.length() - 1);
}
return builder.toString();
}
public void setMaxWalkDistance(double maxWalkDistance) {
if (maxWalkDistance > 0) {
this.maxWalkDistance = maxWalkDistance;
bikeWalkingOptions.maxWalkDistance = maxWalkDistance;
}
}
public void setMaxPreTransitTime(int maxPreTransitTime) {
if (maxPreTransitTime > 0) {
this.maxPreTransitTime = maxPreTransitTime;
bikeWalkingOptions.maxPreTransitTime = maxPreTransitTime;
}
}
public void setWalkReluctance(double walkReluctance) {
if (walkReluctance > 0) {
this.walkReluctance = walkReluctance;
// Do not set bikeWalkingOptions.walkReluctance here, because that needs a higher value.
}
}
public void setWaitReluctance(double waitReluctance) {
if (waitReluctance > 0) {
this.waitReluctance = waitReluctance;
}
}
public void setWaitAtBeginningFactor(double waitAtBeginningFactor) {
if (waitAtBeginningFactor > 0) {
this.waitAtBeginningFactor = waitAtBeginningFactor;
}
}
public void banTrip(AgencyAndId trip) {
bannedTrips.put(trip, BannedStopSet.ALL);
}
/**
* tripIsBanned is a misnomer: this checks whether the agency or route are banned.
* banning of individual trips is actually performed inside the trip search,
* in TripTimes.tripAcceptable.
*/
public boolean tripIsBanned(Trip trip) {
/* check if agency is banned for this plan */
if (bannedAgencies != null) {
if (bannedAgencies.contains(trip.getRoute().getAgency().getId())) {
return true;
}
}
/* check if route banned for this plan */
if (bannedRoutes != null) {
Route route = trip.getRoute();
if (bannedRoutes.matches(route)) {
return true;
}
}
return false;
}
/** Check if route is preferred according to this request. */
public long preferencesPenaltyForRoute(Route route) {
long preferences_penalty = 0;
String agencyID = route.getAgency().getId();
if ((preferredRoutes != null && !preferredRoutes.equals(RouteMatcher.emptyMatcher())) ||
(preferredAgencies != null && !preferredAgencies.isEmpty())) {
boolean isPreferedRoute = preferredRoutes != null && preferredRoutes.matches(route);
boolean isPreferedAgency = preferredAgencies != null && preferredAgencies.contains(agencyID);
if (!isPreferedRoute && !isPreferedAgency) {
preferences_penalty += otherThanPreferredRoutesPenalty;
}
else {
preferences_penalty = 0;
}
}
boolean isUnpreferedRoute = unpreferredRoutes != null && unpreferredRoutes.matches(route);
boolean isUnpreferedAgency = unpreferredAgencies != null && unpreferredAgencies.contains(agencyID);
if (isUnpreferedRoute || isUnpreferedAgency) {
preferences_penalty += useUnpreferredRoutesPenalty;
}
return preferences_penalty;
}
/**
* Get the maximum expected speed over all transit modes.
* TODO derive actual speeds from GTFS feeds. On the other hand, that's what the bidirectional heuristic does on the fly.
*/
public double getTransitSpeedUpperBound() {
if (modes.contains(TraverseMode.RAIL)) {
return 84; // 300kph typical peak speed of a TGV
}
if (modes.contains(TraverseMode.CAR)) {
return 40; // 130kph max speed of a car on a highway
}
// Considering that buses can travel on highways, return the same max speed for all other transit.
return 40; // TODO find accurate max speeds
}
/**
* Sets the bicycle triangle routing parameters -- the relative importance of safety, flatness, and speed.
* These three fields of the RoutingRequest should have values between 0 and 1, and should add up to 1.
* This setter function accepts any three numbers and will normalize them to add up to 1.
*/
public void setTriangleNormalized (double safe, double slope, double time) {
double total = safe + slope + time;
safe /= total;
slope /= total;
time /= total;
this.triangleSafetyFactor = safe;
this.triangleSlopeFactor = slope;
this.triangleTimeFactor = time;
}
/** Create a new ShortestPathTree instance using the DominanceFunction specified in this RoutingRequest. */
public ShortestPathTree getNewShortestPathTree() {
return this.dominanceFunction.getNewShortestPathTree(this);
}
/**
* Does nothing if different edge is split in origin/destination search
*
* But throws TrivialPathException if same edge is split in origin/destination search.
*
* used in {@link org.opentripplanner.graph_builder.linking.SimpleStreetSplitter} in {@link org.opentripplanner.graph_builder.linking.SimpleStreetSplitter#link(Vertex, StreetEdge, double, RoutingRequest)}
* @param edge
*/
public void canSplitEdge(StreetEdge edge) {
if (splitEdge == null) {
splitEdge = edge;
} else {
if (splitEdge.equals(edge)) {
throw new TrivialPathException();
}
}
}
}