package org.opentripplanner.graph_builder.module;
import com.beust.jcommander.internal.Lists;
import com.beust.jcommander.internal.Sets;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import org.opentripplanner.api.resource.CoordinateArrayListSequence;
import org.opentripplanner.api.resource.SimpleIsochrone;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.geometry.PackedCoordinateSequence;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.routing.algorithm.EarliestArrivalSearch;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.edgetype.StreetEdge;
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.impl.StreetVertexIndexServiceImpl;
import org.opentripplanner.routing.services.StreetVertexIndexService;
import org.opentripplanner.routing.spt.GraphPath;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.routing.vertextype.TransitStop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* These library functions are used by the streetless and streetful stop linkers, and in profile transfer generation.
* Ideally they could also be used in long distance mode and profile routing for the street segments.
* For each stop, it finds the closest stops on all other patterns. This reduces the number of transfer edges
* significantly compared to simple radius-constrained all-to-all stop linkage.
*/
public class NearbyStopFinder {
private static Logger LOG = LoggerFactory.getLogger(NearbyStopFinder.class);
private static GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory();
public final boolean useStreets;
private Graph graph;
private double radiusMeters;
/* Fields used when finding stops via the street network. */
private EarliestArrivalSearch earliestArrivalSearch;
/* Fields used when finding stops without a street network. */
private StreetVertexIndexService streetIndex;
/**
* Construct a NearbyStopFinder for the given graph and search radius, choosing whether to search via the street
* network or straight line distance based on the presence of OSM street data in the graph.
*/
public NearbyStopFinder(Graph graph, double radiusMeters) {
this (graph, radiusMeters, graph.hasStreets);
}
/**
* Construct a NearbyStopFinder for the given graph and search radius.
* @param useStreets if true, search via the street network instead of using straight-line distance.
*/
public NearbyStopFinder(Graph graph, double radiusMeters, boolean useStreets) {
this.graph = graph;
this.useStreets = useStreets;
this.radiusMeters = radiusMeters;
if (useStreets) {
earliestArrivalSearch = new EarliestArrivalSearch();
// We need to accommodate straight line distance (in meters) but when streets are present we use an
// earliest arrival search, which optimizes on time. Ideally we'd specify in meters,
// but we don't have much of a choice here. Use the default walking speed to convert.
earliestArrivalSearch.maxDuration = (int) (radiusMeters / new RoutingRequest().walkSpeed);
} else {
// FIXME use the vertex index already in the graph if it exists.
streetIndex = new StreetVertexIndexServiceImpl(graph);
}
}
/**
* Find all unique nearby stops that are the closest stop on some trip pattern.
* Note that the result will include the origin vertex if it is an instance of TransitStop.
* This is intentional: we don't want to return the next stop down the line for trip patterns that pass through the
* origin vertex.
*/
public Set<StopAtDistance> findNearbyStopsConsideringPatterns (Vertex vertex) {
/* Track the closest stop on each pattern passing nearby. */
SimpleIsochrone.MinMap<TripPattern, StopAtDistance> closestStopForPattern =
new SimpleIsochrone.MinMap<TripPattern, StopAtDistance>();
/* Iterate over nearby stops via the street network or using straight-line distance, depending on the graph. */
for (NearbyStopFinder.StopAtDistance stopAtDistance : findNearbyStops(vertex)) {
/* Filter out destination stops that are already reachable via pathways or transfers. */
// FIXME why is the above comment relevant here? how does the next line achieve this?
TransitStop ts1 = stopAtDistance.tstop;
if (!ts1.isStreetLinkable()) continue;
/* Consider this destination stop as a candidate for every trip pattern passing through it. */
for (TripPattern pattern : graph.index.patternsForStop.get(ts1.getStop())) {
closestStopForPattern.putMin(pattern, stopAtDistance);
}
}
/* Make a transfer from the origin stop to each destination stop that was the closest stop on any pattern. */
Set<StopAtDistance> uniqueStops = Sets.newHashSet();
uniqueStops.addAll(closestStopForPattern.values());
return uniqueStops;
}
/**
* Return all stops within a certain radius of the given vertex, using network distance along streets.
* Use the correct method depending on whether the graph has street data or not.
* If the origin vertex is a TransitStop, the result will include it; this characteristic is essential for
* associating the correct stop with each trip pattern in the vicinity.
*/
public List<StopAtDistance> findNearbyStops (Vertex vertex) {
return useStreets ? findNearbyStopsViaStreets(vertex) : findNearbyStopsEuclidean(vertex);
}
/**
* Return all stops within a certain radius of the given vertex, using network distance along streets.
* If the origin vertex is a TransitStop, the result will include it.
*/
public List<StopAtDistance> findNearbyStopsViaStreets (Vertex originVertex) {
RoutingRequest routingRequest = new RoutingRequest(TraverseMode.WALK);
routingRequest.clampInitialWait = (0L);
routingRequest.setRoutingContext(graph, originVertex, null);
ShortestPathTree spt = earliestArrivalSearch.getShortestPathTree(routingRequest);
List<StopAtDistance> stopsFound = Lists.newArrayList();
if (spt != null) {
// TODO use GenericAStar and a traverseVisitor? Add an earliestArrival switch to genericAStar?
for (State state : spt.getAllStates()) {
Vertex targetVertex = state.getVertex();
if (targetVertex == originVertex) continue;
if (targetVertex instanceof TransitStop) {
stopsFound.add(stopAtDistanceForState(state));
}
}
}
/* Add the origin vertex if needed. The SPT does not include the initial state. FIXME shouldn't it? */
if (originVertex instanceof TransitStop) {
stopsFound.add(new StopAtDistance((TransitStop)originVertex, 0));
}
routingRequest.cleanup();
return stopsFound;
}
/**
* Return all stops within a certain radius of the given vertex, using straight-line distance independent of streets.
* If the origin vertex is a TransitStop, the result will include it.
*/
public List<StopAtDistance> findNearbyStopsEuclidean (Vertex originVertex) {
List<StopAtDistance> stopsFound = Lists.newArrayList();
Coordinate c0 = originVertex.getCoordinate();
for (TransitStop ts1 : streetIndex.getNearbyTransitStops(c0, radiusMeters)) {
double distance = SphericalDistanceLibrary.distance(c0, ts1.getCoordinate());
if (distance < radiusMeters) {
Coordinate coordinates[] = new Coordinate[] {c0, ts1.getCoordinate()};
StopAtDistance sd = new StopAtDistance(ts1, distance);
sd.geom = geometryFactory.createLineString(coordinates);
stopsFound.add(sd);
}
}
return stopsFound;
}
/**
* Represents a stop that is comparable to other stops on the basis of its distance from some point.
*/
public static class StopAtDistance implements Comparable<StopAtDistance> {
public TransitStop tstop;
public double dist;
public LineString geom;
public List<Edge> edges;
public StopAtDistance(TransitStop tstop, double dist) {
this.tstop = tstop;
this.dist = dist;
}
@Override
public int compareTo(StopAtDistance that) {
return (int) (this.dist) - (int) (that.dist);
}
public String toString() {
return String.format("stop %s at %.1f meters", tstop, dist);
}
}
/**
* Given a State at a TransitStop, bundle the TransitStop together with information about how far away it is
* and the geometry of the path leading up to the given State.
*
* TODO this should probably be merged with similar classes in Profile routing.
*/
public static StopAtDistance stopAtDistanceForState (State state) {
double distance = 0.0;
GraphPath graphPath = new GraphPath(state, false);
CoordinateArrayListSequence coordinates = new CoordinateArrayListSequence();
List<Edge> edges = new ArrayList<>();
for (Edge edge : graphPath.edges) {
if (edge instanceof StreetEdge) {
LineString geometry = edge.getGeometry();
if (geometry != null) {
if (coordinates.size() == 0) {
coordinates.extend(geometry.getCoordinates());
} else {
coordinates.extend(geometry.getCoordinates(), 1);
}
}
distance += edge.getDistance();
}
edges.add(edge);
}
if (coordinates.size() < 2) { // Otherwise the walk step generator breaks.
ArrayList<Coordinate> coordinateList = new ArrayList<Coordinate>(2);
coordinateList.add(graphPath.states.get(1).getVertex().getCoordinate());
State lastState = graphPath.states.getLast().getBackState();
coordinateList.add(lastState.getVertex().getCoordinate());
coordinates = new CoordinateArrayListSequence(coordinateList);
}
StopAtDistance sd = new StopAtDistance((TransitStop) state.getVertex(), distance);
sd.geom = geometryFactory.createLineString(new PackedCoordinateSequence.Double(coordinates.toCoordinateArray()));
sd.edges = edges;
return sd;
}
}