Skip to content

Commit

Permalink
Merge pull request #6247 from Jnction/boarding-locations
Browse files Browse the repository at this point in the history
Process boarding location for OSM ways (linear platforms)
  • Loading branch information
leonardehrenfried authored Jan 15, 2025
2 parents ce6fc7a + 35f8b5d commit e61f455
Show file tree
Hide file tree
Showing 32 changed files with 823 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary;
import org.opentripplanner.graph_builder.model.GraphBuilderModule;
import org.opentripplanner.graph_builder.module.configure.DaggerGraphBuilderFactory;
import org.opentripplanner.graph_builder.module.configure.GraphBuilderFactory;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.VehicleParkingRepository;
import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository;
import org.opentripplanner.standalone.config.BuildConfig;
Expand Down Expand Up @@ -62,6 +64,7 @@ public static GraphBuilder create(
BuildConfig config,
GraphBuilderDataSources dataSources,
Graph graph,
OsmInfoGraphBuildRepository osmInfoGraphBuildRepository,
TimetableRepository timetableRepository,
WorldEnvelopeRepository worldEnvelopeRepository,
VehicleParkingRepository vehicleParkingService,
Expand All @@ -78,10 +81,11 @@ public static GraphBuilder create(

timetableRepository.initTimeZone(config.transitModelTimeZone);

var builder = DaggerGraphBuilderFactory
.builder()
GraphBuilderFactory.Builder builder = DaggerGraphBuilderFactory.builder();
builder
.config(config)
.graph(graph)
.osmInfoGraphBuildRepository(osmInfoGraphBuildRepository)
.timetableRepository(timetableRepository)
.worldEnvelopeRepository(worldEnvelopeRepository)
.vehicleParkingRepository(vehicleParkingService)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
package org.opentripplanner.graph_builder.module;

import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Point;
import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.framework.geometry.SphericalDistanceLibrary;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.i18n.LocalizedString;
import org.opentripplanner.graph_builder.model.GraphBuilderModule;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.index.StreetIndex;
import org.opentripplanner.routing.linking.LinkingDirection;
import org.opentripplanner.routing.linking.VertexLinker;
import org.opentripplanner.service.osminfo.OsmInfoGraphBuildService;
import org.opentripplanner.service.osminfo.model.Platform;
import org.opentripplanner.street.model.StreetTraversalPermission;
import org.opentripplanner.street.model.edge.AreaEdge;
import org.opentripplanner.street.model.edge.BoardingLocationToStopLink;
Expand All @@ -24,9 +33,12 @@
import org.opentripplanner.street.model.vertex.OsmBoardingLocationVertex;
import org.opentripplanner.street.model.vertex.StreetVertex;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.model.vertex.VertexFactory;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.TraverseModeSet;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StationElement;
import org.opentripplanner.transit.service.TimetableRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -55,14 +67,20 @@ public class OsmBoardingLocationsModule implements GraphBuilderModule {

private final Graph graph;

private final OsmInfoGraphBuildService osmInfoGraphBuildService;
private final TimetableRepository timetableRepository;
private final VertexFactory vertexFactory;

private VertexLinker linker;

@Inject
public OsmBoardingLocationsModule(Graph graph, TimetableRepository timetableRepository) {
public OsmBoardingLocationsModule(
Graph graph,
OsmInfoGraphBuildService osmInfoGraphBuildService,
TimetableRepository timetableRepository
) {
this.graph = graph;
this.osmInfoGraphBuildService = osmInfoGraphBuildService;
this.timetableRepository = timetableRepository;
this.vertexFactory = new VertexFactory(graph);
}
Expand Down Expand Up @@ -99,54 +117,32 @@ public void buildGraph() {
}

private boolean connectVertexToStop(TransitStopVertex ts, StreetIndex index) {
var stopCode = ts.getStop().getCode();
var stopId = ts.getStop().getId().getId();
if (connectVertexToNode(ts, index)) return true;

if (connectVertexToWay(ts, index)) return true;

return connectVertexToArea(ts, index);
}

private Envelope getEnvelope(TransitStopVertex ts) {
Envelope envelope = new Envelope(ts.getCoordinate());

double xscale = Math.cos(ts.getCoordinate().y * Math.PI / 180);
envelope.expandBy(searchRadiusDegrees / xscale, searchRadiusDegrees);
return envelope;
}

// 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) -> {
if (osmBoardingLocationVertex == splitVertex) {
return List.of();
}
// 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)
/**
* Connect a transit stop vertex into a boarding location area in the index.
* <p>
* A centroid vertex is generated in the area and connected to the vertices on the platform edge.
*
* @return if the vertex has been connected
*/
private boolean connectVertexToArea(TransitStopVertex ts, StreetIndex index) {
RegularStop stop = ts.getStop();
var nearbyAreaEdgeList = index
.getEdgesForEnvelope(getEnvelope(ts))
.stream()
.filter(AreaEdge.class::isInstance)
.map(AreaEdge.class::cast)
Expand All @@ -155,33 +151,141 @@ private boolean connectVertexToStop(TransitStopVertex ts, StreetIndex index) {

// 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)
) {
for (var edgeList : nearbyAreaEdgeList) {
if (matchesReference(stop, edgeList.references)) {
var name = edgeList
.getAreas()
.stream()
.findFirst()
.map(NamedArea::getName)
.orElse(LOCALIZED_PLATFORM_NAME);
var label = "platform-centroid/%s".formatted(ts.getStop().getId().toString());
var centroid = edgeList.getGeometry().getCentroid();
var boardingLocation = vertexFactory.osmBoardingLocation(
new Coordinate(centroid.getX(), centroid.getY()),
label,
var boardingLocation = makeBoardingLocation(
stop,
edgeList.getGeometry().getCentroid(),
edgeList.references,
name
);
linker.addPermanentAreaVertex(boardingLocation, edgeList);
linkBoardingLocationToStop(ts, stopCode, boardingLocation);
linkBoardingLocationToStop(ts, stop.getCode(), boardingLocation);
return true;
}
}
return false;
}

/**
* Connect a transit stop vertex to a boarding location way in the index.
* <p>
* The vertex is connected to the center of the way if one is found, splitting it if needed.
*
* @return if the vertex has been connected
*/
private boolean connectVertexToWay(TransitStopVertex ts, StreetIndex index) {
var stop = ts.getStop();
var nearbyEdges = new HashMap<Platform, List<Edge>>();

for (var edge : index.getEdgesForEnvelope(getEnvelope(ts))) {
osmInfoGraphBuildService
.findPlatform(edge)
.ifPresent(platform -> {
if (matchesReference(stop, platform.references())) {
if (!nearbyEdges.containsKey(platform)) {
var list = new ArrayList<Edge>();
list.add(edge);
nearbyEdges.put(platform, list);
} else {
nearbyEdges.get(platform).add(edge);
}
}
});
}

for (var platformEdgeList : nearbyEdges.entrySet()) {
Platform platform = platformEdgeList.getKey();
var name = platform.name();
var boardingLocation = makeBoardingLocation(
stop,
platform.geometry().getCentroid(),
platform.references(),
name
);
for (var vertex : linker.linkToSpecificStreetEdgesPermanently(
boardingLocation,
new TraverseModeSet(TraverseMode.WALK),
LinkingDirection.BOTH_WAYS,
platformEdgeList.getValue().stream().map(StreetEdge.class::cast).collect(Collectors.toSet())
)) {
linkBoardingLocationToStop(ts, stop.getCode(), vertex);
}
return true;
}
return false;
}

/**
* Connect a transit stop vertex to a boarding location node.
* <p>
* The node is generated in the OSM processing step but we need to link it here.
*
* @return If the vertex has been connected.
*/
private boolean connectVertexToNode(TransitStopVertex ts, StreetIndex index) {
var nearbyBoardingLocations = index
.getVerticesForEnvelope(getEnvelope(ts))
.stream()
.filter(OsmBoardingLocationVertex.class::isInstance)
.map(OsmBoardingLocationVertex.class::cast)
.collect(Collectors.toSet());

for (var boardingLocation : nearbyBoardingLocations) {
if (matchesReference(ts.getStop(), boardingLocation.references)) {
if (!boardingLocation.isConnectedToStreetNetwork()) {
linker.linkVertexPermanently(
boardingLocation,
new TraverseModeSet(TraverseMode.WALK),
LinkingDirection.BOTH_WAYS,
(osmBoardingLocationVertex, splitVertex) ->
getConnectingEdges(boardingLocation, osmBoardingLocationVertex, splitVertex)
);
}
linkBoardingLocationToStop(ts, ts.getStop().getCode(), boardingLocation);
return true;
}
}
return false;
}

private OsmBoardingLocationVertex makeBoardingLocation(
RegularStop stop,
Point centroid,
Set<String> refs,
I18NString name
) {
var label = "platform-centroid/%s".formatted(stop.getId().toString());
return vertexFactory.osmBoardingLocation(
new Coordinate(centroid.getX(), centroid.getY()),
label,
refs,
name
);
}

private List<Edge> getConnectingEdges(
OsmBoardingLocationVertex boardingLocation,
Vertex osmBoardingLocationVertex,
StreetVertex splitVertex
) {
if (osmBoardingLocationVertex == splitVertex) {
return List.of();
}
// 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)
);
}

private StreetEdge linkBoardingLocationToStreetNetwork(StreetVertex from, StreetVertex to) {
var line = GeometryUtils.makeLineString(List.of(from.getCoordinate(), to.getCoordinate()));
return new StreetEdgeBuilder<>()
Expand All @@ -197,8 +301,8 @@ private StreetEdge linkBoardingLocationToStreetNetwork(StreetVertex from, Street

private void linkBoardingLocationToStop(
TransitStopVertex ts,
String stopCode,
OsmBoardingLocationVertex boardingLocation
@Nullable String stopCode,
StreetVertex boardingLocation
) {
BoardingLocationToStopLink.createBoardingLocationToStopLink(ts, boardingLocation);
BoardingLocationToStopLink.createBoardingLocationToStopLink(boardingLocation, ts);
Expand All @@ -210,4 +314,11 @@ private void linkBoardingLocationToStop(
boardingLocation.getCoordinate()
);
}

private boolean matchesReference(StationElement<?, ?> stop, Collection<String> references) {
var stopCode = stop.getCode();
var stopId = stop.getId().getId();

return (stopCode != null && references.contains(stopCode)) || references.contains(stopId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@
import org.opentripplanner.gtfs.graphbuilder.GtfsModule;
import org.opentripplanner.netex.NetexModule;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
import org.opentripplanner.service.osminfo.configure.OsmInfoGraphBuildServiceModule;
import org.opentripplanner.service.vehicleparking.VehicleParkingRepository;
import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository;
import org.opentripplanner.standalone.config.BuildConfig;
import org.opentripplanner.street.model.StreetLimitationParameters;
import org.opentripplanner.transit.service.TimetableRepository;

@Singleton
@Component(modules = { GraphBuilderModules.class })
@Component(modules = { GraphBuilderModules.class, OsmInfoGraphBuildServiceModule.class })
public interface GraphBuilderFactory {
//DataImportIssueStore issueStore();
GraphBuilder graphBuilder();
Expand Down Expand Up @@ -80,6 +82,9 @@ interface Builder {
@BindsInstance
Builder timetableRepository(TimetableRepository timetableRepository);

@BindsInstance
Builder osmInfoGraphBuildRepository(OsmInfoGraphBuildRepository osmInfoGraphBuildRepository);

@BindsInstance
Builder worldEnvelopeRepository(WorldEnvelopeRepository worldEnvelopeRepository);

Expand Down
Loading

0 comments on commit e61f455

Please sign in to comment.