
org.opentripplanner.graph_builder.module.OsmBoardingLocationsModule Maven / Gradle / Ivy
Show all versions of otp Show documentation
package org.opentripplanner.graph_builder.module;
import java.util.List;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.graph_builder.linking.LinkingDirection;
import org.opentripplanner.graph_builder.linking.VertexLinker;
import org.opentripplanner.graph_builder.model.GraphBuilderModule;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.edgetype.AreaEdge;
import org.opentripplanner.routing.edgetype.BoardingLocationToStopLink;
import org.opentripplanner.routing.edgetype.NamedArea;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.edgetype.StreetTransitStopLink;
import org.opentripplanner.routing.edgetype.StreetTraversalPermission;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.impl.StreetVertexIndex;
import org.opentripplanner.routing.vertextype.OsmBoardingLocationVertex;
import org.opentripplanner.routing.vertextype.StreetVertex;
import org.opentripplanner.routing.vertextype.TransitStopVertex;
import org.opentripplanner.transit.model.basic.LocalizedString;
import org.opentripplanner.transit.service.TransitModel;
import org.opentripplanner.util.geometry.GeometryUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This module takes advantage of the fact that in some cities, an authoritative linking location
* for GTFS stops is provided by tags in the OSM data.
*
* When OSM data is being loaded, certain entities that represent transit stops are made into
* {@link OsmBoardingLocationVertex} instances. In some cities, these nodes have a ref=* tag which
* gives the corresponding GTFS stop ID for the stop but the exact tag name is configurable. See
* the OSM wiki page.
*
* This module will attempt to link all transit stops and platforms to such nodes or way centroids
* in the OSM data, based on the stop ID or stop code and ref tag. It is run before the main transit
* stop linker, and if no linkage was created here, the main linker should create one based on
* distance or other heuristics.
*/
public class OsmBoardingLocationsModule implements GraphBuilderModule {
private static final Logger LOG = LoggerFactory.getLogger(OsmBoardingLocationsModule.class);
private final double searchRadiusDegrees = SphericalDistanceLibrary.metersToDegrees(250);
private final Graph graph;
private final TransitModel transitModel;
private VertexLinker linker;
@Inject
public OsmBoardingLocationsModule(Graph graph, TransitModel transitModel) {
this.graph = graph;
this.transitModel = transitModel;
}
@Override
public void buildGraph() {
LOG.info("Improving boarding locations by checking OSM entities...");
StreetVertexIndex streetIndex = graph.getStreetIndexSafe(transitModel.getStopModel());
this.linker = streetIndex.getVertexLinker();
int successes = 0;
for (TransitStopVertex ts : graph.getVerticesOfType(TransitStopVertex.class)) {
// if the street is already linked there is no need to linked it again,
// could happened if using the prune isolated island
boolean alreadyLinked = false;
for (Edge e : ts.getOutgoing()) {
if (e instanceof StreetTransitStopLink) {
alreadyLinked = true;
break;
}
}
if (alreadyLinked) continue;
// only connect transit stops that are not part of a pathway network
if (!ts.hasPathways()) {
if (!connectVertexToStop(ts, streetIndex, graph)) {
LOG.debug("Could not connect {} at {}", ts.getStop().getCode(), ts.getCoordinate());
} else {
successes++;
}
}
}
LOG.info("Found {} OSM references which match a stop's id or code", successes);
}
@Override
public void checkInputs() {
//no inputs
}
private boolean connectVertexToStop(TransitStopVertex ts, StreetVertexIndex index, Graph graph) {
var stopCode = ts.getStop().getCode();
var stopId = ts.getStop().getId().getId();
Envelope envelope = new Envelope(ts.getCoordinate());
double xscale = Math.cos(ts.getCoordinate().y * Math.PI / 180);
envelope.expandBy(searchRadiusDegrees / xscale, searchRadiusDegrees);
// if the boarding location is an OSM node it's generated in the OSM processing step but we need
// link it here
var nearbyBoardingLocations = index
.getVerticesForEnvelope(envelope)
.stream()
.filter(OsmBoardingLocationVertex.class::isInstance)
.map(OsmBoardingLocationVertex.class::cast)
.collect(Collectors.toSet());
for (var boardingLocation : nearbyBoardingLocations) {
if (
(stopCode != null && boardingLocation.references.contains(stopCode)) ||
boardingLocation.references.contains(stopId)
) {
if (!boardingLocation.isConnectedToStreetNetwork()) {
linker.linkVertexPermanently(
boardingLocation,
new TraverseModeSet(TraverseMode.WALK),
LinkingDirection.BOTH_WAYS,
(osmBoardingLocationVertex, splitVertex) -> {
// the OSM boarding location vertex is not connected to the street network, so we
// need to link it first
return List.of(
linkBoardingLocationToStreetNetwork(boardingLocation, splitVertex),
linkBoardingLocationToStreetNetwork(splitVertex, boardingLocation)
);
}
);
}
linkBoardingLocationToStop(ts, stopCode, boardingLocation);
return true;
}
}
// if the boarding location is an OSM way (an area) then we are generating the vertex here and
// use the AreaEdgeList to link it to the correct vertices of the platform edge
var nearbyEdgeLists = index
.getEdgesForEnvelope(envelope)
.stream()
.filter(AreaEdge.class::isInstance)
.map(AreaEdge.class::cast)
.map(AreaEdge::getArea)
.collect(Collectors.toSet());
// Iterate over all nearby areas representing transit stops in OSM, linking to them if they have a stop code or id
// in their ref= tag that matches the GTFS stop code of this StopVertex.
for (var edgeList : nearbyEdgeLists) {
if (
(stopCode != null && edgeList.references.contains(stopCode)) ||
edgeList.references.contains(stopId)
) {
var name = edgeList
.getAreas()
.stream()
.findFirst()
.map(NamedArea::getName)
.orElse(new LocalizedString("name.platform"));
var label = "platform-centroid/%s".formatted(ts.getStop().getId().toString());
var centroid = edgeList.getGeometry().getCentroid();
var boardingLocation = new OsmBoardingLocationVertex(
graph,
label,
centroid.getX(),
centroid.getY(),
name,
edgeList.references
);
edgeList.addVertex(boardingLocation);
linkBoardingLocationToStop(ts, stopCode, boardingLocation);
return true;
}
}
return false;
}
private StreetEdge linkBoardingLocationToStreetNetwork(StreetVertex from, StreetVertex to) {
var line = GeometryUtils.makeLineString(List.of(from.getCoordinate(), to.getCoordinate()));
return new StreetEdge(
from,
to,
line,
new LocalizedString("name.platform"),
SphericalDistanceLibrary.length(line),
StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE,
false
);
}
private void linkBoardingLocationToStop(
TransitStopVertex ts,
String stopCode,
OsmBoardingLocationVertex boardingLocation
) {
new BoardingLocationToStopLink(ts, boardingLocation);
new BoardingLocationToStopLink(boardingLocation, ts);
LOG.debug(
"Connected {} ({}) to {} at {}",
ts,
stopCode,
boardingLocation.getLabel(),
boardingLocation.getCoordinate()
);
}
}