Search in sources :

Example 1 with QualifiedMode

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);
}
Also used : TripPattern(org.opentripplanner.routing.edgetype.TripPattern) List(java.util.List) QualifiedMode(org.opentripplanner.api.parameter.QualifiedMode)

Example 2 with QualifiedMode

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;
}
Also used : QualifiedModeSet(org.opentripplanner.api.parameter.QualifiedModeSet) QualifiedMode(org.opentripplanner.api.parameter.QualifiedMode)

Aggregations

QualifiedMode (org.opentripplanner.api.parameter.QualifiedMode)2 List (java.util.List)1 QualifiedModeSet (org.opentripplanner.api.parameter.QualifiedModeSet)1 TripPattern (org.opentripplanner.routing.edgetype.TripPattern)1