
org.opentripplanner.routing.impl.StreetVertexIndex Maven / Gradle / Ivy
Show all versions of otp Show documentation
package org.opentripplanner.routing.impl;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.LineString;
import org.opentripplanner.common.geometry.HashGridSpatialIndex;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.graph_builder.linking.DisposableEdgeCollection;
import org.opentripplanner.graph_builder.linking.LinkingDirection;
import org.opentripplanner.graph_builder.linking.VertexLinker;
import org.opentripplanner.model.GenericLocation;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.edgetype.TemporaryFreeEdge;
import org.opentripplanner.routing.edgetype.TemporaryPartialStreetEdge;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.location.TemporaryStreetLocation;
import org.opentripplanner.routing.vertextype.StreetVertex;
import org.opentripplanner.routing.vertextype.TransitStopVertex;
import org.opentripplanner.transit.model.basic.I18NString;
import org.opentripplanner.transit.model.basic.LocalizedString;
import org.opentripplanner.transit.model.basic.NonLocalizedString;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.service.StopModel;
import org.opentripplanner.util.geometry.GeometryUtils;
import org.opentripplanner.util.logging.ProgressTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Indexes all edges and transit vertices of the graph spatially. Has a variety of query methods
* used during network linking and trip planning.
*
* Creates a TemporaryStreetLocation representing a location on a street that's not at an
* intersection, based on input latitude and longitude. Instantiating this class is expensive,
* because it creates a spatial index of all of the intersections in the graph.
*/
public class StreetVertexIndex {
private static final Logger LOG = LoggerFactory.getLogger(StreetVertexIndex.class);
private final StopModel stopModel;
private final VertexLinker vertexLinker;
private final Map transitStopVertices;
/**
* Contains only instances of {@link StreetEdge}
*/
private final HashGridSpatialIndex edgeTree;
private final HashGridSpatialIndex verticesTree;
/**
* Should only be called by the graph.
*/
public StreetVertexIndex(Graph graph, StopModel stopModel) {
this.stopModel = stopModel;
this.edgeTree = new HashGridSpatialIndex<>();
this.verticesTree = new HashGridSpatialIndex<>();
this.vertexLinker = new VertexLinker(graph, stopModel);
this.transitStopVertices = toImmutableMap(graph.getVerticesOfType(TransitStopVertex.class));
postSetup(graph.getVertices());
}
/**
* Creates a TemporaryStreetLocation on the given street (set of PlainStreetEdges). How far along
* is controlled by the location parameter, which represents a distance along the edge between 0
* (the from vertex) and 1 (the to vertex).
*
* @param edges A collection of nearby edges, which represent one street.
* @return the new TemporaryStreetLocation
*/
public static TemporaryStreetLocation createTemporaryStreetLocationForTest(
String label,
I18NString name,
Iterable edges,
Coordinate nearestPoint,
boolean endVertex,
DisposableEdgeCollection tempEdges
) {
boolean wheelchairAccessible = false;
TemporaryStreetLocation location = new TemporaryStreetLocation(
label,
nearestPoint,
name,
endVertex
);
for (StreetEdge street : edges) {
Vertex fromv = street.getFromVertex();
Vertex tov = street.getToVertex();
wheelchairAccessible |= street.isWheelchairAccessible();
/* forward edges and vertices */
Vertex edgeLocation;
if (SphericalDistanceLibrary.distance(nearestPoint, fromv.getCoordinate()) < 1) {
// no need to link to area edges caught on-end
edgeLocation = fromv;
if (endVertex) {
tempEdges.addEdge(new TemporaryFreeEdge(edgeLocation, location));
} else {
tempEdges.addEdge(new TemporaryFreeEdge(location, edgeLocation));
}
} else if (SphericalDistanceLibrary.distance(nearestPoint, tov.getCoordinate()) < 1) {
// no need to link to area edges caught on-end
edgeLocation = tov;
if (endVertex) {
tempEdges.addEdge(new TemporaryFreeEdge(edgeLocation, location));
} else {
tempEdges.addEdge(new TemporaryFreeEdge(location, edgeLocation));
}
} else {
// creates links from street head -> location -> street tail.
createHalfLocationForTest(location, name, nearestPoint, street, endVertex, tempEdges);
}
}
location.setWheelchairAccessible(wheelchairAccessible);
return location;
}
public VertexLinker getVertexLinker() {
return vertexLinker;
}
public TransitStopVertex findTransitStopVertices(FeedScopedId stopId) {
return transitStopVertices.get(stopId);
}
/**
* Returns the vertices intersecting with the specified envelope.
*/
public List getVerticesForEnvelope(Envelope envelope) {
List vertices = verticesTree.query(envelope);
// Here we assume vertices list modifiable
vertices.removeIf(v -> !envelope.contains(new Coordinate(v.getLon(), v.getLat())));
return vertices;
}
/**
* Return the edges whose geometry intersect with the specified envelope. Warning: edges w/o
* geometry will not be indexed.
*/
public Collection getEdgesForEnvelope(Envelope envelope) {
List edges = edgeTree.query(envelope);
for (Iterator ie = edges.iterator(); ie.hasNext();) {
Edge e = ie.next();
if (e.getToVertex() == null || e.getFromVertex() == null) {
ie.remove();
} else {
Envelope eenv = edgeGeometryOrStraightLine(e).getEnvelopeInternal();
if (!envelope.intersects(eenv)) {
ie.remove();
}
}
}
return edges;
}
/**
* Gets a set of vertices corresponding to the location provided. It first tries to match one of
* the stop or station types by id, and if not successful it uses the coordinates if provided.
*
* @param endVertex: whether this is a start vertex (if it's false) or end vertex (if it's true)
*/
public Set getVerticesForLocation(
GenericLocation location,
StreetMode streetMode,
boolean endVertex,
Set tempEdges
) {
// Differentiate between driving and non-driving, as driving is not available from transit stops
TraverseMode nonTransitMode = getTraverseModeForLinker(streetMode, endVertex);
if (nonTransitMode.isDriving()) {
// Fetch coordinate from stop, if not given in request
if (location.stopId != null && location.getCoordinate() == null) {
var coordinate = stopModel.getCoordinateById(location.stopId);
if (coordinate != null) {
location =
new GenericLocation(
location.label,
location.stopId,
coordinate.latitude(),
coordinate.longitude()
);
}
}
} else {
// Check if Stop/StopCollection is found by FeedScopeId
if (location.stopId != null) {
Set transitStopVertices = getStopVerticesById(location.stopId);
if (transitStopVertices != null && !transitStopVertices.isEmpty()) {
return transitStopVertices;
}
}
}
// Check if coordinate is provided and connect it to graph
if (location.getCoordinate() != null) {
return Set.of(createVertexFromLocation(location, streetMode, endVertex, tempEdges));
}
return null;
}
@Override
public String toString() {
return (
getClass().getName() +
" -- edgeTree: " +
edgeTree.toString() +
" -- verticesTree: " +
verticesTree.toString()
);
}
/**
* Finds the appropriate vertex for this location.
*
* @param endVertex: whether this is a start vertex (if it's false) or end vertex (if it's true)
*/
public Vertex getVertexForLocationForTest(
GenericLocation location,
StreetMode streetMode,
boolean endVertex,
Set tempEdges
) {
// Check if coordinate is provided and connect it to graph
Coordinate coordinate = location.getCoordinate();
if (coordinate != null) {
//return getClosestVertex(loc, options, endVertex);
return createVertexFromLocation(location, streetMode, endVertex, tempEdges);
}
return null;
}
/**
* @param id Id of Stop, Station, MultiModalStation or GroupOfStations
* @return The associated TransitStopVertex or all underlying TransitStopVertices
*/
private Set getStopVerticesById(FeedScopedId id) {
return stopModel
.findStopOrChildStops(id)
.stream()
.filter(RegularStop.class::isInstance)
.map(RegularStop.class::cast)
.map(it -> transitStopVertices.get(it.getId()))
.collect(Collectors.toSet());
}
private static void createHalfLocationForTest(
TemporaryStreetLocation base,
I18NString name,
Coordinate nearestPoint,
StreetEdge street,
boolean endVertex,
DisposableEdgeCollection tempEdges
) {
StreetVertex tov = (StreetVertex) street.getToVertex();
StreetVertex fromv = (StreetVertex) street.getFromVertex();
LineString geometry = street.getGeometry();
P2 geometries = getGeometry(street, nearestPoint);
double totalGeomLength = geometry.getLength();
double lengthRatioIn = geometries.first.getLength() / totalGeomLength;
double lengthIn = street.getDistanceMeters() * lengthRatioIn;
double lengthOut = street.getDistanceMeters() * (1 - lengthRatioIn);
if (endVertex) {
TemporaryPartialStreetEdge temporaryPartialStreetEdge = new TemporaryPartialStreetEdge(
street,
fromv,
base,
geometries.first,
name,
lengthIn
);
temporaryPartialStreetEdge.setMotorVehicleNoThruTraffic(street.isMotorVehicleNoThruTraffic());
temporaryPartialStreetEdge.setBicycleNoThruTraffic(street.isBicycleNoThruTraffic());
temporaryPartialStreetEdge.setWalkNoThruTraffic(street.isWalkNoThruTraffic());
temporaryPartialStreetEdge.setStreetClass(street.getStreetClass());
tempEdges.addEdge(temporaryPartialStreetEdge);
} else {
TemporaryPartialStreetEdge temporaryPartialStreetEdge = new TemporaryPartialStreetEdge(
street,
base,
tov,
geometries.second,
name,
lengthOut
);
temporaryPartialStreetEdge.setStreetClass(street.getStreetClass());
temporaryPartialStreetEdge.setMotorVehicleNoThruTraffic(street.isMotorVehicleNoThruTraffic());
temporaryPartialStreetEdge.setBicycleNoThruTraffic(street.isBicycleNoThruTraffic());
temporaryPartialStreetEdge.setWalkNoThruTraffic(street.isWalkNoThruTraffic());
tempEdges.addEdge(temporaryPartialStreetEdge);
}
}
private static P2 getGeometry(StreetEdge e, Coordinate nearestPoint) {
LineString geometry = e.getGeometry();
return GeometryUtils.splitGeometryAtPoint(geometry, nearestPoint);
}
private static LineString edgeGeometryOrStraightLine(Edge e) {
LineString geometry = e.getGeometry();
if (geometry == null) {
Coordinate[] coordinates = new Coordinate[] {
e.getFromVertex().getCoordinate(),
e.getToVertex().getCoordinate(),
};
geometry = GeometryUtils.getGeometryFactory().createLineString(coordinates);
}
return geometry;
}
private Vertex createVertexFromLocation(
GenericLocation location,
StreetMode streetMode,
boolean endVertex,
Set tempEdges
) {
if (endVertex) {
LOG.debug("Finding end vertex for {}", location);
} else {
LOG.debug("Finding start vertex for {}", location);
}
I18NString name;
if (location.label == null || location.label.isEmpty()) {
if (endVertex) {
name = new LocalizedString("destination");
} else {
name = new LocalizedString("origin");
}
} else {
name = new NonLocalizedString(location.label);
}
TemporaryStreetLocation temporaryStreetLocation = new TemporaryStreetLocation(
UUID.randomUUID().toString(),
location.getCoordinate(),
name,
endVertex
);
TraverseMode nonTransitMode = getTraverseModeForLinker(streetMode, endVertex);
tempEdges.add(
vertexLinker.linkVertexForRequest(
temporaryStreetLocation,
new TraverseModeSet(nonTransitMode),
endVertex ? LinkingDirection.OUTGOING : LinkingDirection.INCOMING,
endVertex
? (vertex, streetVertex) ->
List.of(new TemporaryFreeEdge(streetVertex, (TemporaryStreetLocation) vertex))
: (vertex, streetVertex) ->
List.of(new TemporaryFreeEdge((TemporaryStreetLocation) vertex, streetVertex))
)
);
if (
temporaryStreetLocation.getIncoming().isEmpty() &&
temporaryStreetLocation.getOutgoing().isEmpty()
) {
LOG.warn("Couldn't link {}", location);
}
temporaryStreetLocation.setWheelchairAccessible(true);
return temporaryStreetLocation;
}
private TraverseMode getTraverseModeForLinker(StreetMode streetMode, boolean endVertex) {
TraverseMode nonTransitMode = TraverseMode.WALK;
// for park and ride we will start in car mode and walk to the end vertex
boolean parkAndRideDepart = streetMode == StreetMode.CAR_TO_PARK && !endVertex;
boolean onlyCarAvailable = streetMode == StreetMode.CAR;
if (onlyCarAvailable || parkAndRideDepart) {
nonTransitMode = TraverseMode.CAR;
}
return nonTransitMode;
}
private void postSetup(Collection vertices) {
var progress = ProgressTracker.track("Index street vertex", 1000, vertices.size());
LOG.info(progress.startMessage());
for (Vertex gv : vertices) {
/*
* We add all edges with geometry, skipping transit, filtering them out after. We do not
* index transit edges as we do not need them and some GTFS do not have shape data, so
* long straight lines between 2 faraway stations will wreck performance on a hash grid
* spatial index.
*
* If one need to store transit edges in the index, we could improve the hash grid
* rasterizing splitting long segments.
*/
for (Edge e : gv.getOutgoing()) {
LineString geometry = edgeGeometryOrStraightLine(e);
edgeTree.insert(geometry, e);
}
Envelope env = new Envelope(gv.getCoordinate());
verticesTree.insert(env, gv);
//noinspection Convert2MethodRef
progress.step(m -> LOG.info(m));
}
LOG.info(progress.completeMessage());
}
private static Map toImmutableMap(
Collection vertices
) {
var map = new HashMap();
for (TransitStopVertex it : vertices) {
map.put(it.getStop().getId(), it);
}
return Map.copyOf(map);
}
}