From 2d423a896f599c4acc77fcfc19766732fc374ff9 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Mon, 11 Nov 2024 17:12:28 +0000 Subject: [PATCH] link linear platforms to stops --- .../module/OsmBoardingLocationsModule.java | 114 +++++++++++++----- .../routing/linking/VertexLinker.java | 49 +++++++- .../edge/BoardingLocationToStopLink.java | 10 +- 3 files changed, 137 insertions(+), 36 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java index c4acabefd6c..eb91d0b1d30 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModule.java @@ -1,8 +1,12 @@ package org.opentripplanner.graph_builder.module; import jakarta.inject.Inject; +import java.util.ArrayList; +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.opentripplanner.framework.geometry.GeometryUtils; @@ -17,6 +21,8 @@ import org.opentripplanner.street.model.edge.AreaEdge; import org.opentripplanner.street.model.edge.BoardingLocationToStopLink; import org.opentripplanner.street.model.edge.Edge; +import org.opentripplanner.street.model.edge.LinearPlatform; +import org.opentripplanner.street.model.edge.LinearPlatformEdge; import org.opentripplanner.street.model.edge.NamedArea; import org.opentripplanner.street.model.edge.StreetEdge; import org.opentripplanner.street.model.edge.StreetEdgeBuilder; @@ -24,9 +30,11 @@ 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.StationElement; import org.opentripplanner.transit.service.TimetableRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -99,14 +107,15 @@ public void buildGraph() { } private boolean connectVertexToStop(TransitStopVertex ts, StreetIndex index) { - var stopCode = ts.getStop().getCode(); - var stopId = ts.getStop().getId().getId(); + var stop = ts.getStop(); + var stopCode = stop.getCode(); + var stopId = stop.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 + // if the boarding location is a node it's generated in the OSM processing step but we need // link it here var nearbyBoardingLocations = index .getVerticesForEnvelope(envelope) @@ -116,26 +125,14 @@ private boolean connectVertexToStop(TransitStopVertex ts, StreetIndex index) { .collect(Collectors.toSet()); for (var boardingLocation : nearbyBoardingLocations) { - if ( - (stopCode != null && boardingLocation.references.contains(stopCode)) || - boardingLocation.references.contains(stopId) - ) { + if (matchesReference(stop, boardingLocation.references)) { 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) - ); - } + (osmBoardingLocationVertex, splitVertex) -> + getConnectingEdges(boardingLocation, osmBoardingLocationVertex, splitVertex) ); } linkBoardingLocationToStop(ts, stopCode, boardingLocation); @@ -143,9 +140,50 @@ private boolean connectVertexToStop(TransitStopVertex ts, StreetIndex index) { } } - // if the boarding location is an OSM way (an area) then we are generating the vertex here and + // if the boarding location is a non-area way we are finding the vertex representing the + // center of the way, splitting if needed + var nearbyLinearPlatformEdges = new HashMap>(); + + for (var edge : index.getEdgesForEnvelope(envelope)) { + if (edge instanceof LinearPlatformEdge platformEdge) { + var platform = platformEdge.platform; + if (matchesReference(stop, platform.references())) { + if (!nearbyLinearPlatformEdges.containsKey(platform)) { + var list = new ArrayList(); + list.add(platformEdge); + nearbyLinearPlatformEdges.put(platform, list); + } else { + nearbyLinearPlatformEdges.get(platform).add(platformEdge); + } + } + } + } + + for (var platformEdgeList : nearbyLinearPlatformEdges.entrySet()) { + LinearPlatform platform = platformEdgeList.getKey(); + var name = platform.name(); + var label = "platform-centroid/%s".formatted(stop.getId().toString()); + var centroid = platform.geometry().getCentroid(); + var boardingLocation = vertexFactory.osmBoardingLocation( + new Coordinate(centroid.getX(), centroid.getY()), + label, + 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, stopCode, vertex); + } + return true; + } + + // if the boarding location is 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 + var nearbyAreaEdgeList = index .getEdgesForEnvelope(envelope) .stream() .filter(AreaEdge.class::isInstance) @@ -155,18 +193,15 @@ 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 label = "platform-centroid/%s".formatted(stop.getId().toString()); var centroid = edgeList.getGeometry().getCentroid(); var boardingLocation = vertexFactory.osmBoardingLocation( new Coordinate(centroid.getX(), centroid.getY()), @@ -182,6 +217,22 @@ private boolean connectVertexToStop(TransitStopVertex ts, StreetIndex index) { return false; } + private List 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<>() @@ -197,8 +248,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); @@ -210,4 +261,11 @@ private void linkBoardingLocationToStop( boardingLocation.getCoordinate() ); } + + private boolean matchesReference(StationElement stop, Set references) { + var stopCode = stop.getCode(); + var stopId = stop.getId().getId(); + + return (stopCode != null && references.contains(stopCode)) || references.contains(stopId); + } } diff --git a/application/src/main/java/org/opentripplanner/routing/linking/VertexLinker.java b/application/src/main/java/org/opentripplanner/routing/linking/VertexLinker.java index 48f5ff997c8..a433f3882e1 100644 --- a/application/src/main/java/org/opentripplanner/routing/linking/VertexLinker.java +++ b/application/src/main/java/org/opentripplanner/routing/linking/VertexLinker.java @@ -7,6 +7,7 @@ import java.util.Set; import java.util.function.BiFunction; 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.Geometry; @@ -226,20 +227,38 @@ private DisposableEdgeCollection link( return tempEdges; } + public Set linkToSpecificStreetEdgesPermanently( + Vertex vertex, + TraverseModeSet traverseModes, + LinkingDirection direction, + Set edges + ) { + var xscale = getXscale(vertex); + return linkToCandidateEdges( + vertex, + traverseModes, + direction, + Scope.PERMANENT, + null, + edges.stream().map(e -> new DistanceTo<>(e, distance(vertex, e, xscale))).toList(), + xscale + ); + } + private Set linkToStreetEdges( Vertex vertex, TraverseModeSet traverseModes, LinkingDirection direction, Scope scope, int radiusMeters, - DisposableEdgeCollection tempEdges + @Nullable DisposableEdgeCollection tempEdges ) { final double radiusDeg = SphericalDistanceLibrary.metersToDegrees(radiusMeters); Envelope env = new Envelope(vertex.getCoordinate()); // Perform a simple local equirectangular projection, so distances are expressed in degrees latitude. - final double xscale = Math.cos(vertex.getLat() * Math.PI / 180); + final double xscale = getXscale(vertex); // Expand more in the longitude direction than the latitude direction to account for converging meridians. env.expandBy(radiusDeg / xscale, radiusDeg); @@ -257,6 +276,30 @@ private Set linkToStreetEdges( .filter(ead -> ead.distanceDegreesLat < radiusDeg) .toList(); + return linkToCandidateEdges( + vertex, + traverseModes, + direction, + scope, + tempEdges, + candidateEdges, + xscale + ); + } + + private static double getXscale(Vertex vertex) { + return Math.cos(vertex.getLat() * Math.PI / 180); + } + + private Set linkToCandidateEdges( + Vertex vertex, + TraverseModeSet traverseModes, + LinkingDirection direction, + Scope scope, + @Nullable DisposableEdgeCollection tempEdges, + List> candidateEdges, + double xscale + ) { if (candidateEdges.isEmpty()) { return Set.of(); } @@ -269,7 +312,7 @@ private Set linkToStreetEdges( return closestEdges .stream() .map(ce -> link(vertex, ce.item, xscale, scope, direction, tempEdges, linkedAreas)) - .filter(v -> v != null) + .filter(Objects::nonNull) .collect(Collectors.toSet()); } diff --git a/application/src/main/java/org/opentripplanner/street/model/edge/BoardingLocationToStopLink.java b/application/src/main/java/org/opentripplanner/street/model/edge/BoardingLocationToStopLink.java index 2b306b63cf3..235ec7c6be5 100644 --- a/application/src/main/java/org/opentripplanner/street/model/edge/BoardingLocationToStopLink.java +++ b/application/src/main/java/org/opentripplanner/street/model/edge/BoardingLocationToStopLink.java @@ -3,7 +3,7 @@ import java.util.List; import org.locationtech.jts.geom.LineString; import org.opentripplanner.framework.geometry.GeometryUtils; -import org.opentripplanner.street.model.vertex.OsmBoardingLocationVertex; +import org.opentripplanner.street.model.vertex.StreetVertex; import org.opentripplanner.street.model.vertex.TransitStopVertex; /** @@ -12,16 +12,16 @@ */ public class BoardingLocationToStopLink extends StreetTransitEntityLink { - private BoardingLocationToStopLink(OsmBoardingLocationVertex fromv, TransitStopVertex tov) { + private BoardingLocationToStopLink(StreetVertex fromv, TransitStopVertex tov) { super(fromv, tov, tov.getWheelchairAccessibility()); } - private BoardingLocationToStopLink(TransitStopVertex fromv, OsmBoardingLocationVertex tov) { + private BoardingLocationToStopLink(TransitStopVertex fromv, StreetVertex tov) { super(fromv, tov, fromv.getWheelchairAccessibility()); } public static BoardingLocationToStopLink createBoardingLocationToStopLink( - OsmBoardingLocationVertex fromv, + StreetVertex fromv, TransitStopVertex tov ) { return connectToGraph(new BoardingLocationToStopLink(fromv, tov)); @@ -29,7 +29,7 @@ public static BoardingLocationToStopLink createBoardingLocationToStopLink( public static BoardingLocationToStopLink createBoardingLocationToStopLink( TransitStopVertex fromv, - OsmBoardingLocationVertex tov + StreetVertex tov ) { return connectToGraph(new BoardingLocationToStopLink(fromv, tov)); }