use of org.opentripplanner.api.parameter.QualifiedMode in project OpenTripPlanner by opentripplanner.
the class ProfileRouter method route.
/* Maybe don't even try to accumulate stats and weights on the fly, just enumerate options. */
/* TODO Or actually use a priority queue based on the min time. */
public ProfileResponse route() {
// Lazy-initialize stop clusters (threadsafe method)
graph.index.clusterStopsAsNeeded();
// Lazy-initialize profile transfers (before setting timeouts, since this is slow)
if (graph.index.transfersFromStopCluster == null) {
synchronized (graph.index) {
// we don't initialize it again.
if (graph.index.transfersFromStopCluster == null) {
graph.index.initializeProfileTransfers();
}
}
}
LOG.info("access modes: {}", request.accessModes);
LOG.info("egress modes: {}", request.egressModes);
LOG.info("direct modes: {}", request.directModes);
// Establish search timeouts
long searchBeginTime = System.currentTimeMillis();
long abortTime = searchBeginTime + TIMEOUT * 1000;
// TimeWindow could constructed in the caller, which does have access to the graph index.
this.window = new TimeWindow(request.fromTime, request.toTime, graph.index.servicesRunning(request.date));
LOG.info("Finding access/egress paths.");
// Look for stops that are within a given time threshold of the origin and destination
// Find the closest stop on each pattern near the origin and destination
// TODO consider that some stops may be closer by one mode than another
// and that some stops may be accessible by one mode but not another
fromStopPaths = findClosestStops(false);
fromStops = findClosestPatterns(fromStopPaths);
if (!request.analyst) {
toStopPaths = findClosestStops(true);
toStops = findClosestPatterns(toStopPaths);
// Also look for options connecting origin to destination with no transit.
for (QualifiedMode qmode : request.directModes.qModes) {
LOG.info("Finding non-transit path for mode {}", qmode);
findDirectOption(qmode);
}
}
LOG.info("Done finding access/egress paths.");
// printStopsForPatterns("from", fromStops);
// printStopsForPatterns("to", toStops);
/* Enqueue an unfinished PatternRide for each pattern near the origin, grouped by Stop into unfinished Rides. */
// One ride per stop cluster
Map<StopCluster, Ride> initialRides = Maps.newHashMap();
/* This Multimap will contain multiple entries for the same pattern if it can be reached by multiple modes. */
for (TripPattern pattern : fromStops.keySet()) {
if (!request.transitModes.contains(pattern.mode)) {
// FIXME do not even store these patterns when performing access/egress searches
continue;
}
Collection<StopAtDistance> sds = fromStops.get(pattern);
for (StopAtDistance sd : sds) {
/* Fetch or construct a new Ride beginning at this stop cluster. */
Ride ride = initialRides.get(sd.stopCluster);
if (ride == null) {
// null previous ride because this is the first ride
ride = new Ride(sd.stopCluster, null);
// empty stats, times for each access mode will be merged in
ride.accessStats = new Stats();
// higher than any value that will be merged in
ride.accessStats.min = Integer.MAX_VALUE;
// TODO verify correctness and uses
ride.accessDist = (int) sd.state.getWalkDistance();
initialRides.put(sd.stopCluster, ride);
}
/* Record the access time for this mode in the stats. */
ride.accessStats.merge(sd.etime);
/* Loop over stop clusters in case stop cluster appears more than once in the pattern. */
STOP_INDEX: for (int i = 0; i < pattern.getStops().size(); ++i) {
if (sd.stopCluster == graph.index.stopClusterForStop.get(pattern.getStops().get(i))) {
PatternRide newPatternRide = new PatternRide(pattern, i);
// TODO this would be a !contains() call if PatternRides had semantic equality
for (PatternRide existingPatternRide : ride.patternRides) {
if (existingPatternRide.pattern == newPatternRide.pattern && existingPatternRide.fromIndex == newPatternRide.fromIndex) {
continue STOP_INDEX;
}
}
ride.patternRides.add(newPatternRide);
}
}
}
}
for (Ride ride : initialRides.values()) {
// logRide(ride);
queue.insert(ride, 0);
}
/* Explore incomplete rides as long as there are any in the queue. */
while (!queue.empty()) {
/* Get the minimum-time unfinished ride off the queue. */
Ride ride = queue.extract_min();
// TODO should we check whether ride.previous != null (it is an initial ride)?
if (dominated(ride, ride.from))
continue;
// Maybe when ride is complete, then find transfers here, but that makes for more queue operations.
if (ride.to != null)
throw new AssertionError("Ride should be unfinished.");
/* Track finished Rides by their destination StopCluster, so we can add PatternRides to them. */
Map<StopCluster, Ride> rides = Maps.newHashMap();
/* Complete partial PatternRides (with only a pattern and a beginning stop) which were enqueued in this
* partial ride. This is done by scanning through the Pattern, creating rides to all downstream stops. */
PR: for (PatternRide pr : ride.patternRides) {
// LOG.info(" {}", pr);
List<Stop> stops = pr.pattern.getStops();
for (int s = pr.fromIndex + 1; s < stops.size(); ++s) {
StopCluster cluster = graph.index.stopClusterForStop.get(stops.get(s));
/* Originally we only extended rides to destination stops considered useful in the search, i.e.
* those that had transfers leading out of them or were known to be near the destination.
* However, analyst needs to know the times we can reach every stop, and pruning is more effective
* if we know when rides pass through all stops.*/
PatternRide pr2 = pr.extendToIndex(s, window);
// PatternRide may be empty because there are no trips in time window.
if (pr2 == null)
continue PR;
// LOG.info(" {}", pr2);
// Get or create the completed Ride to this destination stop.
Ride ride2 = rides.get(cluster);
if (ride2 == null) {
ride2 = ride.extendTo(cluster);
rides.put(cluster, ride2);
}
// Add the completed PatternRide to the completed Ride.
ride2.patternRides.add(pr2);
}
}
/* Build new downstream Rides by transferring from patterns in current Rides. */
// Create a map of incomplete rides (start but no end point) after transfers, one for each stop.
Map<StopCluster, Ride> xferRides = Maps.newHashMap();
for (Ride r1 : rides.values()) {
r1.calcStats(window, request.walkSpeed);
if (r1.waitStats == null) {
// This is a sign of a questionable algorithm, since we eliminate the ride rather late.
continue;
} else {
r1.recomputeBounds();
}
/* Retain this ride if it is not dominated by some existing ride at the same location. */
if (dominated(r1, r1.to))
continue;
retainedRides.put(r1.to, r1);
/* We have a new, non-dominated, completed ride. Find transfers out of this new ride, respecting the transfer limit. */
int nRides = r1.pathLength;
if (nRides >= MAX_RIDES)
continue;
boolean penultimateRide = (nRides == MAX_RIDES - 1);
// TODO benchmark, this is so not efficient
for (ProfileTransfer tr : graph.index.transfersFromStopCluster.get(r1.to)) {
if (!request.transitModes.contains(tr.tp2.mode))
continue;
if (r1.containsPattern(tr.tp1)) {
// Prune loopy or repetitive paths.
if (r1.pathContainsRoute(tr.tp2.route))
continue;
if (tr.sc1 != tr.sc2 && r1.pathContainsStop(tr.sc2))
continue;
// only transfer to patterns that pass near the destination.
if (!request.analyst && penultimateRide && !toStops.containsKey(tr.tp2))
continue;
// Scan through stops looking for transfer target: stop might appear more than once in a pattern.
TARGET_STOP: for (int i = 0; i < tr.tp2.getStops().size(); ++i) {
StopCluster cluster = graph.index.stopClusterForStop.get(tr.tp2.getStops().get(i));
if (cluster == tr.sc2) {
// Save transfer result in an unfinished ride for later exploration.
Ride r2 = xferRides.get(tr.sc2);
if (r2 == null) {
r2 = new Ride(tr.sc2, r1);
r2.accessDist = tr.distance;
r2.accessStats = new Stats((int) (tr.distance / request.walkSpeed));
r2.recomputeBounds();
xferRides.put(tr.sc2, r2);
}
for (PatternRide pr : r2.patternRides) {
// TODO refactor with equals function and contains().
if (pr.pattern == tr.tp2 && pr.fromIndex == i)
continue TARGET_STOP;
}
r2.patternRides.add(new PatternRide(tr.tp2, i));
}
}
}
}
}
/* Enqueue new incomplete Rides resulting from transfers if they are not dominated at their from-cluster. */
for (Ride r : xferRides.values()) {
if (!dominated(r, r.from)) {
// This ride is unfinished, use the previous ride's travel time lower bound as the p-queue key.
// Note that we are not adding these transfer results to the retained rides, just enqueuing them.
queue.insert(r, r.dlb);
}
}
if (System.currentTimeMillis() > abortTime)
throw new RuntimeException("TIMEOUT");
}
LOG.info("Profile routing request finished in {} sec.", (System.currentTimeMillis() - searchBeginTime) / 1000.0);
if (request.analyst) {
makeSurfaces();
return null;
}
/* In non-analyst (point-to-point) mode, determine which rides are good ways to reach the destination. */
/* A fake stop cluster to allow applying generic domination logic at the final destination. */
final StopCluster DESTINATION = new StopCluster("The Destination", "The Destination");
for (StopCluster cluster : toStopPaths.keySet()) {
// TODO shared logic for making access/egress stats
Stats egressStats = new Stats();
egressStats.min = Integer.MAX_VALUE;
for (StopAtDistance sd : toStopPaths.get(cluster)) {
egressStats.merge(sd.etime);
}
for (Ride ride : retainedRides.get(cluster)) {
// Construct a new unfinished ride, representing "transferring" from the final stop to the destination
Ride rideAtDestination = new Ride(DESTINATION, ride);
rideAtDestination.accessStats = egressStats;
rideAtDestination.recomputeBounds();
if (!dominated(rideAtDestination, DESTINATION)) {
retainedRides.put(DESTINATION, rideAtDestination);
}
}
}
LOG.info("{} nondominated rides reach the destination.", retainedRides.get(DESTINATION).size());
/* Non-analyst: Build the list of Options by following the back-pointers in Rides. */
List<Option> options = Lists.newArrayList();
for (Ride ride : retainedRides.get(DESTINATION)) {
// slice off the final unfinished ride that only contains the egress stats
// TODO actually use this ride in preparing the response
ride = ride.previous;
// All PatternRides in a Ride end at the same stop.
Collection<StopAtDistance> accessPaths = fromStopPaths.get(ride.getAccessStopCluster());
Collection<StopAtDistance> egressPaths = toStopPaths.get(ride.getEgressStopCluster());
Option option = new Option(ride, accessPaths, egressPaths);
if (!option.hasEmptyRides())
options.add(option);
}
/* Include the direct (no-transit) biking, driving, and walking options. */
options.add(new Option(null, directPaths, null));
return new ProfileResponse(options, request.orderBy, request.limit);
}
use of org.opentripplanner.api.parameter.QualifiedMode in project OpenTripPlanner by opentripplanner.
the class ProfileRouter method findClosestStops.
/**
* Perform an on-street search around a point with each of several modes to find nearby stops.
* @return one or more paths to each reachable stop using the various modes.
*/
private Multimap<StopCluster, StopAtDistance> findClosestStops(boolean dest) {
Multimap<StopCluster, StopAtDistance> pathsByStop = ArrayListMultimap.create();
QualifiedModeSet qModes = dest ? request.egressModes : request.accessModes;
for (QualifiedMode qmode : qModes.qModes) {
LOG.info("{} mode {}", dest ? "egress" : "access", qmode);
for (StopAtDistance sd : findClosestStops(qmode, dest)) {
pathsByStop.put(sd.stopCluster, sd);
}
}
return pathsByStop;
}
Aggregations