Package org.opentripplanner.profile

Source Code of org.opentripplanner.profile.ProfileRouter$ExtremaPropagationTraverseVisitor

package org.opentripplanner.profile;

import com.google.common.collect.*;
import org.onebusaway.gtfs.model.Stop;
import org.opentripplanner.analyst.TimeSurface;
import org.opentripplanner.api.resource.SimpleIsochrone;
import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.common.pqueue.BinHeap;
import org.opentripplanner.routing.algorithm.GenericAStar;
import org.opentripplanner.routing.algorithm.TraverseVisitor;
import org.opentripplanner.routing.core.RoutingContext;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.edgetype.TripPattern;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.routing.vertextype.TransitStop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.Map.Entry;

public class ProfileRouter {

    private static final Logger LOG = LoggerFactory.getLogger(ProfileRouter.class);

    /* Search configuration constants */
    public static final int SLACK = 60; // in seconds, time required to catch a transit vehicle
    private static final int TIMEOUT = 10; // in seconds, maximum computation time
    public static final int MAX_DURATION = 90 * 60; // in seconds, the longest we want to travel
    private static final int MAX_RIDES = 3; // maximum number of boardings in a trip
    private static final List<TraverseMode> ACCESS_MODES =
            Lists.newArrayList(TraverseMode.WALK, TraverseMode.BICYCLE, TraverseMode.CAR);
    private static final List<TraverseMode> EGRESS_MODES =
            Lists.newArrayList(TraverseMode.WALK);

    public final Graph graph;
    public final ProfileRequest request;

    public ProfileRouter(Graph graph, ProfileRequest request) {
        this.graph = graph;
        this.request = request;
    }

    /* Search state */
    Multimap<StopCluster, StopAtDistance> fromStopPaths, toStopPaths; // ways to reach each origin or dest stop cluster
    List<RoutingContext> routingContexts = Lists.newArrayList();

    /* Analyst: time bounds for each vertex */
    public int[] mins, maxs;
    public TimeSurface minSurface, maxSurface;

    // while finding direct paths:
    // if dist < M meters OR we don't yet have N stations: record station
    Collection<StopAtDistance> directPaths = Lists.newArrayList(); // ways to reach the destination without transit
    Multimap<StopCluster, Ride> retainedRides = ArrayListMultimap.create(); // the rides arriving at each stop that are worth continuing to explore.
    BinHeap<Ride> queue = new BinHeap<Ride>(); // rides to be explored, prioritized by minumum travel time
    // TODO rename fromStopsByPattern
    Map<TripPattern, StopAtDistance> fromStops, toStops;
    TimeWindow window; // filters trips used by time of day and service schedule

    /** @return true if the given stop cluster has at least one transfer coming from the given pattern. */
    private boolean hasTransfers(StopCluster stopCluster, TripPattern pattern) {
        for (ProfileTransfer tr : graph.index.transfersFromStopCluster.get(stopCluster)) {
            if (tr.tp1 == pattern) return true;
        }
        return false;
    }

    /* 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 profile transfers (before setting timeouts, since this is slow)
        if (graph.index.transfersFromStopCluster == null) {
            graph.index.initializeProfileTransfers();
        }
        // Analyst
        if (request.analyst) {
            mins = new int[Vertex.getMaxIndex()];
            maxs = new int[Vertex.getMaxIndex()];
            Arrays.fill(mins, TimeSurface.UNREACHABLE);
            Arrays.fill(maxs, TimeSurface.UNREACHABLE);
        }
        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 (TraverseMode mode : ACCESS_MODES) {
                LOG.info("Finding non-transit path for mode {}", mode);
                if (request.directModes.contains(mode)) findStreetOption(mode); // TODO rename function
            }
        }
        LOG.info("Done finding access/egress paths.");
        LOG.info("From patterns/stops: {}", fromStops);
        LOG.info("To patterns/stops: {}", toStops);

        /* Enqueue an unfinished PatternRide for each pattern near the origin, grouped by Stop into unfinished Rides. */
        Map<StopCluster, Ride> initialRides = Maps.newHashMap(); // One ride per stop cluster
        for (Entry<TripPattern, StopAtDistance> entry : fromStops.entrySet()) {
            TripPattern pattern = entry.getKey();
            StopAtDistance sd = entry.getValue();
            if ( ! request.transitModes.contains(pattern.mode)) {
                continue; // FIXME why are we even storing these patterns?
            }
            /* Loop over stop clusters in case stop cluster appears more than once in the same pattern. */
            for (int i = 0; i < pattern.getStops().size(); ++i) {
                // FIXME using String identity equality for stop clusters on purpose
                if (sd.stop == graph.index.stopClusterForStop.get(pattern.getStops().get(i))) {
                    Ride ride = initialRides.get(sd.stop);
                    if (ride == null) {
                        ride = new Ride(sd.stop, null); // null previous ride because this is the first ride
                        ride.accessTime = sd.etime;
                        ride.accessDist = 0; // FIXME
                        initialRides.put(sd.stop, ride);
                    }
                    ride.patternRides.add(new PatternRide(pattern, i));
                }
            }
        }
        for (Ride ride : initialRides.values()) {
            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();
            //LOG.info("dequeued ride {}", ride);
            //ride.dump();
            // 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 stop, 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 that
             * are near the destination or have relevant transfers. */
            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. */
            Map<StopCluster, Ride> xferRides = Maps.newHashMap(); // A map of incomplete rides after transfers, one for each stop.
            for (Ride r1 : rides.values()) {
                r1.calcStats(window, request.walkSpeed);
                if (r1.waitStats == null) {
                    // This is a sign of a questionable algorithm: we're only seeing if it was possible
                    // to board these trips after the fact, and removing the ride late.
                    // It might make more sense to keep bags of arrival times per ride+stop.
                    continue;
                }
                if ( ! addIfNondominated(r1)) continue; // abandon this ride if it is dominated by some existing ride at the same location
                // We have a new, nondominated, completed ride. Record its lower and upper bounds at the arrival stop.
                if (request.analyst) {
                    for (Stop s : r1.to.children) {
                        // TODO This could be done at the end now that we are retaining all rides.
                        TransitStop tstop = graph.index.stopVertexForStop.get(s);
                        int tsidx = tstop.getIndex();
                        int lb = r1.durationLowerBound();
                        int ub = r1.durationUpperBound();
                        if (mins[tsidx] == TimeSurface.UNREACHABLE || mins[tsidx] > lb)
                            mins[tsidx] = lb;
                        if (maxs[tsidx] == TimeSurface.UNREACHABLE || maxs[tsidx] > ub) // Yes, we want the _minimum_ upper bound.
                            maxs[tsidx] = ub;
                    }
                }
                /* Find transfers out of this new ride. */
                // Do not transfer too many times. Check after calculating stats since stats are needed in any case.
                int nRides = r1.pathLength();
                if (nRides >= MAX_RIDES) continue;
                boolean penultimateRide = (nRides == MAX_RIDES - 1);
                // Invariant: r1.to should be the same as r1's key in rides
                // 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;
                        // Optimization: on the last ride of point-to-point searches,
                        // 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.accessTime = (int) (tr.distance / request.walkSpeed);
                                    xferRides.put(tr.sc2, r2);
                                }
                                for (PatternRide pr : r2.patternRides) {
                                    // Multiple patterns can have transfers to the same target pattern,
                                    // but Rides should not have duplicate 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 with non-excessive durations. */
            for (Ride r : xferRides.values()) {
                // ride is unfinished, use previous ride's time as key
                if (addIfNondominated(r)) queue.insert(r, r.previous.durationLowerBound());
            }
            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;
        }
        /* Non-analyst: Determine which rides are good ways to reach the destination. */
        // FIXME determine why there are multiple copies of the same ride then maybe use a list
        Set<Ride> targetRides = Sets.newHashSet();
        for (StopCluster cluster : toStopPaths.keySet()) {
            for (Ride ride : retainedRides.get(cluster)) {
                PATTERN: for (PatternRide pr : ride.patternRides) {
                    StopAtDistance clusterForPattern = toStops.get(pr.pattern);
                    if (clusterForPattern != null && clusterForPattern.stop == cluster) {
                        targetRides.add(ride);
                        break PATTERN;
                    }
                }
            }
        }
        LOG.info("{} nondominated rides stop near the destination.", targetRides.size());
        /* Non-analyst: Build the list of Options by following the back-pointers in Rides. */
        List<Option> options = Lists.newArrayList();
        for (Ride ride : targetRides) {
            /* We alight from all patterns in a ride at the same stop. */
            int dist = toStops.get(ride.patternRides.get(0).pattern).etime; // TODO time vs dist
            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));
/*
        for (Stop stop : graph.index.stopVertexForStop.keySet()) {
            TransitStop tstop = graph.index.stopVertexForStop.get(stop);
            int min = mins[tstop.getIndex()];
            int max = maxs[tstop.getIndex()];
            if (min == Integer.MAX_VALUE)
                LOG.info("{} unreachable", tstop.getName());
            else
                LOG.info("{} min {} max {}", tstop.getName(), min, max);
        }
*/
        return new ProfileResponse(options, request.orderBy, request.limit);
    }

    /** Check whether a new ride has too long a duration relative to existing rides at the same location or global time limit. */
    public boolean addIfNondominated(Ride newRide) {
        StopCluster cluster = newRide.to;
        if (cluster == null) { // if ride is unfinished, calculate time to its from-cluster based on previous ride
            cluster = newRide.from;
            newRide = newRide.previous;
        }
        if (newRide.durationLowerBound() > MAX_DURATION) return false;
        // Check whether any existing rides at the same location (stop cluster) dominate the new one.
        for (Ride oldRide : retainedRides.get(cluster)) {
            if (oldRide.to == null) oldRide = oldRide.previous; // rides may be unfinished
            // New rides must be strictly better (min and max) than any existing one with less transfers.
            // This avoids alternatives formed by simply inserting extra unnecessary rides.
            if (oldRide.pathLength() < newRide.pathLength() &&
                oldRide.durationLowerBound() < newRide.durationLowerBound() &&
                oldRide.durationUpperBound() < newRide.durationUpperBound()) {
                return false;
            }
            // State is not strictly dominated. Perhaps it has the same number of transfers.
            // In this case we want to keep it as long as it's sometimes better than all the others (time ranges overlap).
            if (newRide.durationLowerBound() > oldRide.durationUpperBound() + request.suboptimalMinutes) {
                return false;
            }
        }
        retainedRides.put(cluster, newRide);
        return true; // No existing ride is strictly better than the new ride.
    }

    /**
     * @param stopClusters a multimap from stop clusters to one or more StopAtDistance objects at the corresponding cluster.
     * @return for each TripPattern that passes through any of the supplied stop clusters, the stop cluster that is
     * closest to the origin or destination point according to the distances in the StopAtDistance objects.
     *
     * In short, take a bunch of stop clusters near the origin or destination and return the quickest way to reach each
     * pattern that passes through them.
     * We want stop cluster references rather than indexes within the patterns because when a stop cluster appears more
     * than once in a pattern, we want to consider boarding or alighting from that pattern at every index where the
     * cluster occurs.
     */
    public Map<TripPattern, StopAtDistance> findClosestPatterns(Multimap<StopCluster, StopAtDistance> stopClusters) {
        SimpleIsochrone.MinMap<TripPattern, StopAtDistance> closest = new SimpleIsochrone.MinMap<TripPattern, StopAtDistance>();
        // Iterate over all StopAtDistance for all Stops. The fastest mode will win at each stop.
        for (StopAtDistance stopDist : stopClusters.values()) {
            for (Stop stop : stopDist.stop.children) {
                for (TripPattern pattern : graph.index.patternsForStop.get(stop)) {
                    closest.putMin(pattern, stopDist);
                    //LOG.info("trip pattern {}", pattern);
                }
            }
        }
        // Truncate long lists to include a mix of nearby bus and train patterns
        if (closest.size() > 500) {
            LOG.warn("Truncating long list of patterns.");
            Multimap<StopAtDistance, TripPattern> busPatterns = TreeMultimap.create(Ordering.natural(), Ordering.arbitrary());
            Multimap<StopAtDistance, TripPattern> otherPatterns = TreeMultimap.create(Ordering.natural(), Ordering.arbitrary());
            Multimap<StopAtDistance, TripPattern> patterns;
            for (TripPattern pattern : closest.keySet()) {
                patterns = (pattern.mode == TraverseMode.BUS) ? busPatterns : otherPatterns;
                patterns.put(closest.get(pattern), pattern);
            }
            closest.clear();
            Iterator<StopAtDistance> iterBus = busPatterns.keySet().iterator();
            Iterator<StopAtDistance> iterOther = otherPatterns.keySet().iterator();
            // Alternately add one of each kind of pattern until we reach the max
            while (closest.size() < 50) {
                StopAtDistance sd;
                if (iterBus.hasNext()) {
                    sd = iterBus.next();
                    for (TripPattern tp : busPatterns.get(sd)) closest.put(tp, sd);
                }
                if (iterOther.hasNext()) {
                    sd = iterOther.next();
                    for (TripPattern tp: otherPatterns.get(sd)) closest.put(tp, sd);
                }
            }
        }
        return closest;
    }

    /**
     * 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();
        for (TraverseMode mode: (dest ? EGRESS_MODES : ACCESS_MODES)) {
            if ((dest ? request.egressModes : request.accessModes).contains(mode)) {
                LOG.info("{} mode {}", dest ? "egress" : "access", mode);
                for (StopAtDistance sd : findClosestStops(mode, dest)) {
                    pathsByStop.put(sd.stop, sd);
                }
            }
        }
        return pathsByStop;
    }

    /**
     * Perform an on-street search around a point with a specific mode to find nearby stops.
     * @param dest : whether to search at the destination instead of the origin.
     */
    private Collection<StopAtDistance> findClosestStops(final TraverseMode mode, boolean dest) {
        // Make a normal OTP routing request so we can traverse edges and use GenericAStar
        RoutingRequest rr = new RoutingRequest(mode);
        if (mode == TraverseMode.CAR) {
            //rr.kissAndRide = true; // allow car->walk transition. we are assuming that someone will drop you off.
            rr.parkAndRide = true; // allow car->walk transition only at tagged park and ride facilities.
            rr.modes.setWalk(true); // need to walk after dropping the car off
        }
        rr.from = (new GenericLocation(request.from.lat, request.from.lon));
        // FIXME requires destination to be set, not necesary for analyst
        rr.to = new GenericLocation(request.to.lat, request.to.lon);
        rr.setArriveBy(dest);
        rr.setRoutingContext(graph);
        // Set batch after context, so both origin and dest vertices will be found.
        rr.batch = (true);
        rr.walkSpeed = request.walkSpeed;
        // RR dateTime defaults to currentTime.
        // If elapsed time is not capped, searches are very slow.
        int minAccessTime = 0;
        int maxAccessTime = request.maxWalkTime;
        if (mode == TraverseMode.BICYCLE) {
            rr.bikeSpeed = request.bikeSpeed;
            minAccessTime = request.minBikeTime;
            maxAccessTime = request.maxBikeTime;
        } else if (mode == TraverseMode.CAR) {
            rr.carSpeed = request.carSpeed;
            minAccessTime = request.minCarTime;
            maxAccessTime = request.maxCarTime;
        } else {
            LOG.warn("No modes matched when setting min/max travel times.");
        }
        long worstElapsedTimeSeconds = maxAccessTime * 60; // convert from minutes to seconds
        if (dest) worstElapsedTimeSeconds *= -1;
        rr.worstTime = (rr.dateTime + worstElapsedTimeSeconds);
        // Note that the (forward) search is intentionally unlimited so it will reach the destination
        // on-street, even though only transit boarding locations closer than req.streetDist will be used.
        GenericAStar astar = new GenericAStar();
        rr.setNumItineraries(1);
        StopFinderTraverseVisitor visitor = new StopFinderTraverseVisitor(mode, minAccessTime * 60);
        astar.setTraverseVisitor(visitor);
        astar.getShortestPathTree(rr, 5); // timeout in seconds
        // Save the routing context for later cleanup. We need its temporary edges to render street segments at the end.
        routingContexts.add(rr.rctx);
        return visitor.stopClustersFound.values();
    }

    static class StopFinderTraverseVisitor implements TraverseVisitor {
        TraverseMode mode;
        int minTravelTimeSeconds = 0;
        Map<StopCluster, StopAtDistance> stopClustersFound = Maps.newHashMap();
        public StopFinderTraverseVisitor(TraverseMode mode, int minTravelTimeSeconds) {
            this.mode = mode;
            this.minTravelTimeSeconds = minTravelTimeSeconds;
        }
        @Override public void visitEdge(Edge edge, State state) { }
        @Override public void visitEnqueue(State state) { }
        // Accumulate stops into ret as the search runs.
        @Override public void visitVertex(State state) {
            Vertex vertex = state.getVertex();
            if (vertex instanceof TransitStop) {
                StopAtDistance sd = new StopAtDistance(state);
                sd.mode = mode; // Override final mode from State, since driving to transit ends in walking.
                if (sd.mode == TraverseMode.CAR && sd.etime < minTravelTimeSeconds) return;
                if (stopClustersFound.containsKey(sd.stop)) return; // record only the closest stop in each cluster
                LOG.debug("found stop cluster: {}", sd);
                stopClustersFound.put(sd.stop, sd);
            }
        }
    }

    /** Look for an option connecting origin to destination without using transit. */
    private void findStreetOption(TraverseMode mode) {
        // Make a normal OTP routing request so we can traverse edges and use GenericAStar
        RoutingRequest rr = new RoutingRequest(mode);
        rr.from = (new GenericLocation(request.from.lat, request.from.lon));
        rr.to = new GenericLocation(request.to.lat, request.to.lon);
        rr.setArriveBy(false);
        rr.setRoutingContext(graph);
        // This is not a batch search, it is a point-to-point search with goal direction.
        // Impose a max time to protect against very slow searches.
        int worstElapsedTime = request.streetTime * 60;
        rr.worstTime = (rr.dateTime + worstElapsedTime);
        rr.walkSpeed = request.walkSpeed;
        rr.bikeSpeed = request.bikeSpeed;
        GenericAStar astar = new GenericAStar();
        rr.setNumItineraries(1);
        ShortestPathTree spt = astar.getShortestPathTree(rr, System.currentTimeMillis() + 5000);
        State state = spt.getState(rr.rctx.target);
        if (state != null) {
            LOG.info("Found non-transit option for mode {}", mode);
            directPaths.add(new StopAtDistance(state));
        }
        routingContexts.add(rr.rctx); // save context for later cleanup so temp edges remain available
    }

    // Major change: This needs to include all stops, not just those where transfers occur or those near the destination.
    /** Make two time surfaces, one for the minimum and one for the maximum. */
    public P2<TimeSurface> makeSurfaces() {
        LOG.info("Propagating profile router result to street vertices.");
        // Make a normal OTP routing request so we can traverse edges and use GenericAStar
        RoutingRequest rr = new RoutingRequest(TraverseMode.WALK);
        rr.setMode(TraverseMode.WALK);
        rr.walkSpeed = request.walkSpeed;
        // If max trip duration is not limited, searches are of course much slower.
        int worstElapsedTime = request.maxWalkTime * 60; // convert from minutes to seconds, assume walking at egress
        rr.worstTime = (rr.dateTime + worstElapsedTime);
        rr.batch = (true);
        GenericAStar astar = new GenericAStar();
        rr.setNumItineraries(1);
        for (TransitStop tstop : graph.index.stopVertexForStop.values()) {
            int index = tstop.getIndex();
            // Generate a tree outward from all stops that have been touched in the basic profile search
            if (mins[index] == TimeSurface.UNREACHABLE || maxs[index] == TimeSurface.UNREACHABLE) continue;
            rr.setRoutingContext(graph, tstop, null); // Set origin vertex directly instead of generating link edges
            astar.setTraverseVisitor(new ExtremaPropagationTraverseVisitor(mins[index], maxs[index]));
            ShortestPathTree spt = astar.getShortestPathTree(rr, 5);
            rr.rctx.destroy();
        }
        minSurface = new TimeSurface(this, false);
        maxSurface = new TimeSurface(this, true);
        LOG.info("done making timesurfaces.");
        return new P2<TimeSurface>(minSurface, maxSurface);
    }

    /** Given a minimum and maximum at a starting vertex, build an on-street SPT and propagate those values outward. */
    class ExtremaPropagationTraverseVisitor implements TraverseVisitor {
        final int min0;
        final int max0;
        ExtremaPropagationTraverseVisitor(int min0, int max0) {
            this.min0 = min0;
            this.max0 = max0;
        }
        @Override public void visitEdge(Edge edge, State state) { }
        @Override public void visitEnqueue(State state) { }
        @Override public void visitVertex(State state) {
            int min = min0 + (int) state.getElapsedTimeSeconds();
            int max = max0 + (int) state.getElapsedTimeSeconds();
            Vertex vertex = state.getVertex();
            int index = vertex.getIndex();
            if (index >= mins.length) return; // New temp vertices may have been created since the array was dimensioned.
            if (mins[index] == TimeSurface.UNREACHABLE || mins[index] > min)
                mins[index] = min;
            if (maxs[index] == TimeSurface.UNREACHABLE || maxs[index] > max) // Yes we want the minimum upper bound (minimum maximum)
                maxs[index] = max;
        }
    }

    /** Destroy all routing contexts created during this search. */
    public int cleanup() {
        int n = 0;
        for (RoutingContext rctx : routingContexts) {
            rctx.destroy();
            n += 1;
        }
        routingContexts.clear();
        LOG.info("destroyed {} routing contexts.", n);
        return n;
    }

}
TOP

Related Classes of org.opentripplanner.profile.ProfileRouter$ExtremaPropagationTraverseVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.