Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Process boarding location for OSM ways (linear platforms) #6247

Merged
merged 18 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
miklcct marked this conversation as resolved.
Show resolved Hide resolved
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
Loading