package org.opentripplanner.profile;
import com.beust.jcommander.internal.Maps;
import org.opentripplanner.routing.algorithm.AStar;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.spt.DominanceFunction;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.routing.vertextype.TransitStop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
/**
* Keeps travel distances from all transit stops in a particular Graph to their nearby street nodes.
* This allows us to propagate travel times out from transit to streets much faster in one-to-many analyst queries.
* The StopTreeCache has a fixed distance cutoff, so will be unable to provide distance information for vertices beyond
* that cutoff distance.
*/
public class StopTreeCache {
private static final Logger LOG = LoggerFactory.getLogger(StopTreeCache.class);
final int maxWalkMeters;
// Flattened 2D array of (streetVertexIndex, distanceFromStop) for each TransitStop
public final Map<TransitStop, int[]> distancesForStop = Maps.newHashMap();
public StopTreeCache (Graph graph, int maxWalkMeters) {
this.maxWalkMeters = maxWalkMeters;
LOG.info("Caching distances to nearby street intersections from each transit stop...");
graph.index.stopVertexForStop.values().parallelStream().forEach(tstop -> {
RoutingRequest rr = new RoutingRequest(TraverseMode.WALK);
rr.batch = (true);
rr.setRoutingContext(graph, tstop, tstop);
AStar astar = new AStar();
rr.longDistance = true;
rr.setNumItineraries(1);
// since we're storing distances and later using them to optimize
// (in the profile propagation code we optimize on distance / walkSpeed
// not the actual time including turn costs etc.),
// we need to optimize on distance here as well.
rr.maxWalkDistance = maxWalkMeters;
rr.softWalkLimiting = false;
rr.dominanceFunction = new DominanceFunction.LeastWalk();
ShortestPathTree spt = astar.getShortestPathTree(rr, 5); // timeout in seconds
// Copy vertex indices and distances into a flattened 2D array
int[] distances = new int[spt.getVertexCount() * 2];
int i = 0;
for (Vertex vertex : spt.getVertices()) {
State state = spt.getState(vertex);
if (state == null)
continue;
distances[i++] = vertex.getIndex();
distances[i++] = (int) state.getWalkDistance();
}
rr.cleanup();
synchronized (distancesForStop) {
distancesForStop.put(tstop, distances);
}
});
LOG.info("Done caching distances to nearby street intersections from each transit stop.");
}
/**
* Given a travel time to a transit stop, fill in the array with minimum travel times to all nearby street vertices.
* This function is meant to be called repeatedly on multiple transit stops, accumulating minima
* into the same targetArray.
*/
public void propagateStop(TransitStop transitStop, int baseTimeSeconds, double walkSpeed, int[] targetArray) {
// Iterate over street intersections in the vicinity of this particular transit stop.
// Shift the time range at this transit stop, merging it into that for all reachable street intersections.
int[] distances = distancesForStop.get(transitStop);
int v = 0;
while (v < distances.length) {
// Unravel flattened 2D array
int vertexIndex = distances[v++];
int distance = distances[v++];
// distance in meters over walkspeed in meters per second --> seconds
int egressWalkTimeSeconds = (int) (distance / walkSpeed);
int propagated_time = baseTimeSeconds + egressWalkTimeSeconds;
int existing_min = targetArray[vertexIndex];
if (existing_min == 0 || existing_min > propagated_time) {
targetArray[vertexIndex] = propagated_time;
}
}
}
}