args) {
+ if (args == null) {
+ return List.of();
+ }
+ return args.stream().filter(Objects::nonNull).toList();
+ }
}
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/application/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
index 0b071b64728..744a8209702 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
@@ -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;
@@ -62,6 +64,7 @@ public static GraphBuilder create(
BuildConfig config,
GraphBuilderDataSources dataSources,
Graph graph,
+ OsmInfoGraphBuildRepository osmInfoGraphBuildRepository,
TimetableRepository timetableRepository,
WorldEnvelopeRepository worldEnvelopeRepository,
VehicleParkingRepository vehicleParkingService,
@@ -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)
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..25e33b9c057 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,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;
@@ -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;
@@ -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);
}
@@ -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.
+ *
+ * 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)
@@ -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.
+ *
+ * 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>();
+
+ 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();
+ 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.
+ *
+ * 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 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 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 +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);
@@ -210,4 +314,11 @@ private void linkBoardingLocationToStop(
boardingLocation.getCoordinate()
);
}
+
+ private boolean matchesReference(StationElement, ?> stop, Collection 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/graph_builder/module/configure/GraphBuilderFactory.java b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java
index d4d00fdc2a0..4155ecf9614 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderFactory.java
@@ -30,6 +30,8 @@
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;
@@ -37,7 +39,7 @@
import org.opentripplanner.transit.service.TimetableRepository;
@Singleton
-@Component(modules = { GraphBuilderModules.class })
+@Component(modules = { GraphBuilderModules.class, OsmInfoGraphBuildServiceModule.class })
public interface GraphBuilderFactory {
//DataImportIssueStore issueStore();
GraphBuilder graphBuilder();
@@ -80,6 +82,9 @@ interface Builder {
@BindsInstance
Builder timetableRepository(TimetableRepository timetableRepository);
+ @BindsInstance
+ Builder osmInfoGraphBuildRepository(OsmInfoGraphBuildRepository osmInfoGraphBuildRepository);
+
@BindsInstance
Builder worldEnvelopeRepository(WorldEnvelopeRepository worldEnvelopeRepository);
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java
index 5371142d612..d464523a61a 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java
@@ -43,13 +43,14 @@
import org.opentripplanner.osm.OsmProvider;
import org.opentripplanner.routing.api.request.preference.WalkPreferences;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.VehicleParkingRepository;
import org.opentripplanner.standalone.config.BuildConfig;
import org.opentripplanner.street.model.StreetLimitationParameters;
import org.opentripplanner.transit.service.TimetableRepository;
/**
- * Configure all modules which is not simple enough to be injected.
+ * Configure all modules that are not simple enough to be injected.
*/
@Module
public class GraphBuilderModules {
@@ -60,7 +61,8 @@ static OsmModule provideOsmModule(
GraphBuilderDataSources dataSources,
BuildConfig config,
Graph graph,
- VehicleParkingRepository parkingService,
+ OsmInfoGraphBuildRepository osmInfoGraphBuildRepository,
+ VehicleParkingRepository vehicleParkingRepository,
DataImportIssueStore issueStore,
StreetLimitationParameters streetLimitationParameters
) {
@@ -71,6 +73,7 @@ static OsmModule provideOsmModule(
osmConfiguredDataSource.dataSource(),
osmConfiguredDataSource.config().osmTagMapper(),
osmConfiguredDataSource.config().timeZone(),
+ osmConfiguredDataSource.config().includeOsmSubwayEntrances(),
config.osmCacheDataInMem,
issueStore
)
@@ -78,7 +81,7 @@ static OsmModule provideOsmModule(
}
return OsmModule
- .of(providers, graph, parkingService)
+ .of(providers, graph, osmInfoGraphBuildRepository, vehicleParkingRepository)
.withEdgeNamer(config.edgeNamer)
.withAreaVisibility(config.areaVisibility)
.withPlatformEntriesLinking(config.platformEntriesLinking)
@@ -86,6 +89,7 @@ static OsmModule provideOsmModule(
.withStaticBikeParkAndRide(config.staticBikeParkAndRide)
.withMaxAreaNodes(config.maxAreaNodes)
.withBoardingAreaRefTags(config.boardingLocationTags)
+ .withIncludeOsmSubwayEntrances(config.osmDefaults.includeOsmSubwayEntrances())
.withIssueStore(issueStore)
.withStreetLimitationParameters(streetLimitationParameters)
.build();
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java
index 490d6a266b9..45ed01e4568 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/ElevatorProcessor.java
@@ -95,7 +95,7 @@ public void buildElevatorEdges(Graph graph) {
}
int travelTime = parseDuration(node).orElse(-1);
- var wheelchair = node.getWheelchairAccessibility();
+ var wheelchair = node.wheelchairAccessibility();
createElevatorHopEdges(
onboardVertices,
@@ -138,7 +138,7 @@ public void buildElevatorEdges(Graph graph) {
int travelTime = parseDuration(elevatorWay).orElse(-1);
int levels = nodes.size();
- var wheelchair = elevatorWay.getWheelchairAccessibility();
+ var wheelchair = elevatorWay.wheelchairAccessibility();
createElevatorHopEdges(
onboardVertices,
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java
index db495905041..3b9f411fec6 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java
@@ -8,6 +8,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
@@ -25,6 +26,8 @@
import org.opentripplanner.osm.wayproperty.WayProperties;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.util.ElevationUtils;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
+import org.opentripplanner.service.osminfo.model.Platform;
import org.opentripplanner.service.vehicleparking.VehicleParkingRepository;
import org.opentripplanner.service.vehicleparking.model.VehicleParking;
import org.opentripplanner.street.model.StreetLimitationParameters;
@@ -52,7 +55,9 @@ public class OsmModule implements GraphBuilderModule {
*/
private final List providers;
private final Graph graph;
+ private final OsmInfoGraphBuildRepository osmInfoGraphBuildRepository;
private final VehicleParkingRepository parkingRepository;
+
private final DataImportIssueStore issueStore;
private final OsmProcessingParameters params;
private final SafetyValueNormalizer normalizer;
@@ -63,36 +68,51 @@ public class OsmModule implements GraphBuilderModule {
OsmModule(
Collection providers,
Graph graph,
- VehicleParkingRepository parkingService,
+ OsmInfoGraphBuildRepository osmInfoGraphBuildRepository,
+ VehicleParkingRepository parkingRepository,
DataImportIssueStore issueStore,
StreetLimitationParameters streetLimitationParameters,
OsmProcessingParameters params
) {
this.providers = List.copyOf(providers);
this.graph = graph;
+ this.osmInfoGraphBuildRepository = osmInfoGraphBuildRepository;
+ this.parkingRepository = parkingRepository;
this.issueStore = issueStore;
this.params = params;
this.osmdb = new OsmDatabase(issueStore);
- this.vertexGenerator = new VertexGenerator(osmdb, graph, params.boardingAreaRefTags());
+ this.vertexGenerator =
+ new VertexGenerator(
+ osmdb,
+ graph,
+ params.boardingAreaRefTags(),
+ params.includeOsmSubwayEntrances()
+ );
this.normalizer = new SafetyValueNormalizer(graph, issueStore);
this.streetLimitationParameters = Objects.requireNonNull(streetLimitationParameters);
- this.parkingRepository = parkingService;
}
public static OsmModuleBuilder of(
Collection providers,
Graph graph,
- VehicleParkingRepository service
+ OsmInfoGraphBuildRepository osmInfoGraphBuildRepository,
+ VehicleParkingRepository vehicleParkingRepository
) {
- return new OsmModuleBuilder(providers, graph, service);
+ return new OsmModuleBuilder(
+ providers,
+ graph,
+ osmInfoGraphBuildRepository,
+ vehicleParkingRepository
+ );
}
public static OsmModuleBuilder of(
OsmProvider provider,
Graph graph,
- VehicleParkingRepository service
+ OsmInfoGraphBuildRepository osmInfoGraphBuildRepository,
+ VehicleParkingRepository vehicleParkingRepository
) {
- return of(List.of(provider), graph, service);
+ return of(List.of(provider), graph, osmInfoGraphBuildRepository, vehicleParkingRepository);
}
@Override
@@ -319,6 +339,8 @@ private void buildBasicGraph() {
// where the current edge should start
OsmNode osmStartNode = null;
+ var platform = getPlatform(way);
+
for (int i = 0; i < nodes.size() - 1; i++) {
OsmNode segmentStartOsmNode = osmdb.getNode(nodes.get(i));
@@ -341,7 +363,7 @@ private void buildBasicGraph() {
* We split segments at intersections, self-intersections, nodes with ele tags, and transit stops;
* the only processing we do on other nodes is to accumulate their geometry
*/
- if (segmentCoordinates.size() == 0) {
+ if (segmentCoordinates.isEmpty()) {
segmentCoordinates.add(osmStartNode.getCoordinate());
}
@@ -408,6 +430,11 @@ private void buildBasicGraph() {
StreetEdge backStreet = streets.back();
normalizer.applyWayProperties(street, backStreet, wayData, way);
+ platform.ifPresent(plat -> {
+ osmInfoGraphBuildRepository.addPlatform(street, plat);
+ osmInfoGraphBuildRepository.addPlatform(backStreet, plat);
+ });
+
applyEdgesToTurnRestrictions(way, startNode, endNode, street, backStreet);
startNode = endNode;
osmStartNode = osmdb.getNode(startNode);
@@ -422,6 +449,33 @@ private void buildBasicGraph() {
LOG.info(progress.completeMessage());
}
+ private Optional getPlatform(OsmWay way) {
+ if (way.isBoardingLocation()) {
+ var nodeRefs = way.getNodeRefs();
+ var size = nodeRefs.size();
+ var nodes = new Coordinate[size];
+ for (int i = 0; i < size; i++) {
+ nodes[i] = osmdb.getNode(nodeRefs.get(i)).getCoordinate();
+ }
+
+ var geometryFactory = GeometryUtils.getGeometryFactory();
+
+ var geometry = geometryFactory.createLineString(nodes);
+
+ var references = way.getMultiTagValues(params.boardingAreaRefTags());
+
+ return Optional.of(
+ new Platform(
+ params.edgeNamer().getNameForWay(way, "platform " + way.getId()),
+ geometry,
+ references
+ )
+ );
+ } else {
+ return Optional.empty();
+ }
+ }
+
private void validateBarriers() {
List vertices = graph.getVerticesOfType(BarrierVertex.class);
vertices.forEach(bv -> bv.makeBarrierAtEndReachable());
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java
index 2f7f4c506c9..ce50dbbde64 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java
@@ -8,6 +8,7 @@
import org.opentripplanner.graph_builder.services.osm.EdgeNamer;
import org.opentripplanner.osm.OsmProvider;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.VehicleParkingRepository;
import org.opentripplanner.street.model.StreetLimitationParameters;
@@ -19,6 +20,7 @@ public class OsmModuleBuilder {
private final Collection providers;
private final Graph graph;
private final VehicleParkingRepository parkingRepository;
+ private final OsmInfoGraphBuildRepository osmInfoGraphBuildRepository;
private Set boardingAreaRefTags = Set.of();
private DataImportIssueStore issueStore = DataImportIssueStore.NOOP;
private EdgeNamer edgeNamer = new DefaultNamer();
@@ -26,16 +28,19 @@ public class OsmModuleBuilder {
private boolean platformEntriesLinking = false;
private boolean staticParkAndRide = false;
private boolean staticBikeParkAndRide = false;
+ private boolean includeOsmSubwayEntrances = false;
private int maxAreaNodes;
private StreetLimitationParameters streetLimitationParameters = new StreetLimitationParameters();
OsmModuleBuilder(
Collection providers,
Graph graph,
+ OsmInfoGraphBuildRepository osmInfoGraphBuildRepository,
VehicleParkingRepository parkingRepository
) {
this.providers = providers;
this.graph = graph;
+ this.osmInfoGraphBuildRepository = osmInfoGraphBuildRepository;
this.parkingRepository = parkingRepository;
}
@@ -79,6 +84,11 @@ public OsmModuleBuilder withMaxAreaNodes(int maxAreaNodes) {
return this;
}
+ public OsmModuleBuilder withIncludeOsmSubwayEntrances(boolean includeOsmSubwayEntrances) {
+ this.includeOsmSubwayEntrances = includeOsmSubwayEntrances;
+ return this;
+ }
+
public OsmModuleBuilder withStreetLimitationParameters(StreetLimitationParameters parameters) {
this.streetLimitationParameters = parameters;
return this;
@@ -88,6 +98,7 @@ public OsmModule build() {
return new OsmModule(
providers,
graph,
+ osmInfoGraphBuildRepository,
parkingRepository,
issueStore,
streetLimitationParameters,
@@ -98,7 +109,8 @@ public OsmModule build() {
areaVisibility,
platformEntriesLinking,
staticParkAndRide,
- staticBikeParkAndRide
+ staticBikeParkAndRide,
+ includeOsmSubwayEntrances
)
);
}
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java
index e6fec74b798..8c707d005a9 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java
@@ -33,12 +33,19 @@ class VertexGenerator {
private final HashMap> multiLevelNodes = new HashMap<>();
private final OsmDatabase osmdb;
private final Set boardingAreaRefTags;
+ private final Boolean includeOsmSubwayEntrances;
private final VertexFactory vertexFactory;
- public VertexGenerator(OsmDatabase osmdb, Graph graph, Set boardingAreaRefTags) {
+ public VertexGenerator(
+ OsmDatabase osmdb,
+ Graph graph,
+ Set boardingAreaRefTags,
+ boolean includeOsmSubwayEntrances
+ ) {
this.osmdb = osmdb;
this.vertexFactory = new VertexFactory(graph);
this.boardingAreaRefTags = boardingAreaRefTags;
+ this.includeOsmSubwayEntrances = includeOsmSubwayEntrances;
}
/**
@@ -95,6 +102,11 @@ IntersectionVertex getVertexForOsmNode(OsmNode node, OsmWithTags way) {
iv = bv;
}
+ if (includeOsmSubwayEntrances && node.isSubwayEntrance()) {
+ String ref = node.getTag("ref");
+ iv = vertexFactory.stationEntrance(nid, coordinate, ref, node.wheelchairAccessibility());
+ }
+
if (iv == null) {
iv =
vertexFactory.osm(
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java
index 175b9c04c5b..a59147137f6 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java
@@ -11,16 +11,28 @@
* Example: {@code "osm" : [ {source: "file:///path/to/otp/norway.pbf"} ] }
*
*/
-public record OsmExtractParameters(URI source, OsmTagMapperSource osmTagMapper, ZoneId timeZone)
+public record OsmExtractParameters(
+ URI source,
+ OsmTagMapperSource osmTagMapper,
+ ZoneId timeZone,
+ boolean includeOsmSubwayEntrances
+)
implements DataSourceConfig {
public static final OsmTagMapperSource DEFAULT_OSM_TAG_MAPPER = OsmTagMapperSource.DEFAULT;
public static final ZoneId DEFAULT_TIME_ZONE = null;
+ public static final boolean DEFAULT_INCLUDE_OSM_SUBWAY_ENTRANCES = false;
+
public static final OsmExtractParameters DEFAULT = new OsmExtractParametersBuilder().build();
OsmExtractParameters(OsmExtractParametersBuilder builder) {
- this(builder.getSource(), builder.getOsmTagMapper(), builder.getTimeZone());
+ this(
+ builder.getSource(),
+ builder.getOsmTagMapper(),
+ builder.getTimeZone(),
+ builder.includeOsmSubwayEntrances()
+ );
}
@Override
@@ -37,6 +49,10 @@ public ZoneId timeZone() {
return timeZone;
}
+ public boolean includeOsmSubwayEntrances() {
+ return includeOsmSubwayEntrances;
+ }
+
public OsmExtractParametersBuilder copyOf() {
return new OsmExtractParametersBuilder(this);
}
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java
index 2d9bb71d9f5..66c65e05d81 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java
@@ -24,14 +24,18 @@ public class OsmExtractParametersBuilder {
*/
private ZoneId timeZone;
+ private boolean includeOsmSubwayEntrances;
+
public OsmExtractParametersBuilder() {
this.osmTagMapper = OsmExtractParameters.DEFAULT_OSM_TAG_MAPPER;
this.timeZone = OsmExtractParameters.DEFAULT_TIME_ZONE;
+ this.includeOsmSubwayEntrances = OsmExtractParameters.DEFAULT_INCLUDE_OSM_SUBWAY_ENTRANCES;
}
public OsmExtractParametersBuilder(OsmExtractParameters original) {
this.osmTagMapper = original.osmTagMapper();
this.timeZone = original.timeZone();
+ this.includeOsmSubwayEntrances = original.includeOsmSubwayEntrances();
}
public OsmExtractParametersBuilder withSource(URI source) {
@@ -49,6 +53,13 @@ public OsmExtractParametersBuilder withTimeZone(ZoneId timeZone) {
return this;
}
+ public OsmExtractParametersBuilder withIncludeOsmSubwayEntrances(
+ boolean includeOsmSubwayEntrances
+ ) {
+ this.includeOsmSubwayEntrances = includeOsmSubwayEntrances;
+ return this;
+ }
+
public URI getSource() {
return source;
}
@@ -61,6 +72,10 @@ public ZoneId getTimeZone() {
return timeZone;
}
+ public boolean includeOsmSubwayEntrances() {
+ return includeOsmSubwayEntrances;
+ }
+
public OsmExtractParameters build() {
return new OsmExtractParameters(this);
}
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java
index 52bf8d65314..a3fd14020e8 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java
@@ -13,6 +13,7 @@
* @param platformEntriesLinking Whether platform entries should be linked
* @param staticParkAndRide Whether we should create car P+R stations from OSM data.
* @param staticBikeParkAndRide Whether we should create bike P+R stations from OSM data.
+ * @param includeOsmSubwayEntrances Whether we should create subway entrances from OSM data.
*/
public record OsmProcessingParameters(
Set boardingAreaRefTags,
@@ -21,7 +22,8 @@ public record OsmProcessingParameters(
boolean areaVisibility,
boolean platformEntriesLinking,
boolean staticParkAndRide,
- boolean staticBikeParkAndRide
+ boolean staticBikeParkAndRide,
+ boolean includeOsmSubwayEntrances
) {
public OsmProcessingParameters {
boardingAreaRefTags = Set.copyOf(Objects.requireNonNull(boardingAreaRefTags));
diff --git a/application/src/main/java/org/opentripplanner/model/plan/RelativeDirection.java b/application/src/main/java/org/opentripplanner/model/plan/RelativeDirection.java
index ffc8993d0db..3ce16a45c11 100644
--- a/application/src/main/java/org/opentripplanner/model/plan/RelativeDirection.java
+++ b/application/src/main/java/org/opentripplanner/model/plan/RelativeDirection.java
@@ -21,6 +21,13 @@ public enum RelativeDirection {
UTURN_RIGHT,
ENTER_STATION,
EXIT_STATION,
+ /**
+ * We don't have a way to reliably tell if we are entering or exiting a station and therefore
+ * use this generic enum value. Please don't expose it in APIs.
+ *
+ * If we manage to figure it out in the future, we can remove this.
+ */
+ ENTER_OR_EXIT_STATION,
FOLLOW_SIGNS;
public static RelativeDirection calculate(
diff --git a/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java b/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java
index c2c2b2c609e..7ade16de39a 100644
--- a/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java
+++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java
@@ -8,6 +8,7 @@
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.note.StreetNote;
+import org.opentripplanner.transit.model.site.Entrance;
import org.opentripplanner.utils.lang.DoubleUtils;
import org.opentripplanner.utils.tostring.ToStringBuilder;
@@ -43,7 +44,8 @@ public final class WalkStep {
private final double angle;
private final boolean walkingBike;
- private final String exit;
+ private final String highwayExit;
+ private final Entrance entrance;
private final ElevationProfile elevationProfile;
private final boolean stayOn;
@@ -55,7 +57,8 @@ public final class WalkStep {
AbsoluteDirection absoluteDirection,
I18NString directionText,
Set streetNotes,
- String exit,
+ String highwayExit,
+ Entrance entrance,
ElevationProfile elevationProfile,
boolean nameIsDerived,
boolean walkingBike,
@@ -75,7 +78,8 @@ public final class WalkStep {
this.angle = DoubleUtils.roundTo2Decimals(angle);
this.walkingBike = walkingBike;
this.area = area;
- this.exit = exit;
+ this.highwayExit = highwayExit;
+ this.entrance = entrance;
this.elevationProfile = elevationProfile;
this.stayOn = stayOn;
this.edges = List.copyOf(Objects.requireNonNull(edges));
@@ -126,8 +130,15 @@ public Optional getAbsoluteDirection() {
/**
* When exiting a highway or traffic circle, the exit name/number.
*/
- public String getExit() {
- return exit;
+ public Optional highwayExit() {
+ return Optional.ofNullable(highwayExit);
+ }
+
+ /**
+ * Get information about a subway station entrance or exit.
+ */
+ public Optional entrance() {
+ return Optional.ofNullable(entrance);
}
/**
diff --git a/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java b/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java
index b2f9e1f7510..75589718861 100644
--- a/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java
+++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java
@@ -9,6 +9,7 @@
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.note.StreetNote;
+import org.opentripplanner.transit.model.site.Entrance;
import org.opentripplanner.utils.lang.DoubleUtils;
import org.opentripplanner.utils.lang.IntUtils;
@@ -25,6 +26,7 @@ public class WalkStepBuilder {
private RelativeDirection relativeDirection;
private ElevationProfile elevationProfile;
private String exit;
+ private Entrance entrance;
private boolean stayOn = false;
/**
* Distance used for appending elevation profiles
@@ -74,6 +76,11 @@ public WalkStepBuilder withExit(String exit) {
return this;
}
+ public WalkStepBuilder withEntrance(@Nullable Entrance entrance) {
+ this.entrance = entrance;
+ return this;
+ }
+
public WalkStepBuilder withStayOn(boolean stayOn) {
this.stayOn = stayOn;
return this;
@@ -159,6 +166,7 @@ public WalkStep build() {
directionText,
streetNotes,
exit,
+ entrance,
elevationProfile,
nameIsDerived,
walkingBike,
diff --git a/application/src/main/java/org/opentripplanner/osm/OsmProvider.java b/application/src/main/java/org/opentripplanner/osm/OsmProvider.java
index 91944a95b86..53d6acc87b9 100644
--- a/application/src/main/java/org/opentripplanner/osm/OsmProvider.java
+++ b/application/src/main/java/org/opentripplanner/osm/OsmProvider.java
@@ -37,6 +37,8 @@ public class OsmProvider {
private final OsmTagMapper osmTagMapper;
+ private boolean includeOsmSubwayEntrances = false;
+
private final WayPropertySet wayPropertySet;
private byte[] cachedBytes = null;
@@ -46,6 +48,7 @@ public OsmProvider(File file, boolean cacheDataInMem) {
new FileDataSource(file, FileType.OSM),
OsmTagMapperSource.DEFAULT,
null,
+ false,
cacheDataInMem,
DataImportIssueStore.NOOP
);
@@ -55,11 +58,13 @@ public OsmProvider(
DataSource dataSource,
OsmTagMapperSource tagMapperSource,
ZoneId zoneId,
+ boolean includeOsmSubwayEntrances,
boolean cacheDataInMem,
DataImportIssueStore issueStore
) {
this.source = dataSource;
this.zoneId = zoneId;
+ this.includeOsmSubwayEntrances = includeOsmSubwayEntrances;
this.osmTagMapper = tagMapperSource.getInstance();
this.wayPropertySet = new WayPropertySet(issueStore);
osmTagMapper.populateProperties(wayPropertySet);
@@ -152,6 +157,10 @@ public OsmTagMapper getOsmTagMapper() {
return osmTagMapper;
}
+ public boolean getIncludeOsmSubwayEntrances() {
+ return includeOsmSubwayEntrances;
+ }
+
public WayPropertySet getWayPropertySet() {
return wayPropertySet;
}
diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java b/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java
index c5539d1296e..cb9fcd679f0 100644
--- a/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java
+++ b/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java
@@ -63,6 +63,15 @@ public boolean isBarrier() {
);
}
+ /**
+ * Checks if this node is a subway station entrance.
+ *
+ * @return true if it is
+ */
+ public boolean isSubwayEntrance() {
+ return hasTag("railway") && "subway_entrance".equals(getTag("railway"));
+ }
+
/**
* Consider barrier tag in permissions. Leave the rest for the super class.
*/
diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmWithTags.java b/application/src/main/java/org/opentripplanner/osm/model/OsmWithTags.java
index 3f47d4454bd..10214460b15 100644
--- a/application/src/main/java/org/opentripplanner/osm/model/OsmWithTags.java
+++ b/application/src/main/java/org/opentripplanner/osm/model/OsmWithTags.java
@@ -139,7 +139,7 @@ public boolean isTagFalse(String tag) {
/**
* Returns the level of wheelchair access of the element.
*/
- public Accessibility getWheelchairAccessibility() {
+ public Accessibility wheelchairAccessibility() {
if (isTagTrue("wheelchair")) {
return Accessibility.POSSIBLE;
} else if (isTagFalse("wheelchair")) {
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
index 15e3307b4e9..683acdd1a9e 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
@@ -6,7 +6,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
import org.opentripplanner.astar.model.GraphPath;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.framework.geometry.GeometryUtils;
@@ -39,8 +38,8 @@
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.request.StreetSearchRequest;
import org.opentripplanner.street.search.request.StreetSearchRequestMapper;
-import org.opentripplanner.street.search.state.EdgeTraverser;
import org.opentripplanner.street.search.state.State;
+import org.opentripplanner.street.search.state.StateEditor;
import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate;
import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
import org.opentripplanner.transit.service.TransitService;
@@ -361,15 +360,24 @@ private List mapNonTransitLeg(
.build()
);
} else {
- var legTransferSearchRequest = transferStreetRequest
- .copyOf(createZonedDateTime(pathLeg.fromTime()).toInstant())
- .build();
- var initialStates = State.getInitialStates(
- Set.of(edges.getFirst().getFromVertex()),
- legTransferSearchRequest
- );
- var state = EdgeTraverser.traverseEdges(initialStates, edges);
- var graphPath = new GraphPath<>(state.get());
+ StateEditor se = new StateEditor(edges.get(0).getFromVertex(), transferStreetRequest);
+ se.setTimeSeconds(createZonedDateTime(pathLeg.fromTime()).toEpochSecond());
+
+ State s = se.makeState();
+ ArrayList transferStates = new ArrayList<>();
+ transferStates.add(s);
+ for (Edge e : edges) {
+ var states = e.traverse(s);
+ if (State.isEmpty(states)) {
+ s = null;
+ } else {
+ transferStates.add(states[0]);
+ s = states[0];
+ }
+ }
+
+ State[] states = transferStates.toArray(new State[0]);
+ var graphPath = new GraphPath<>(states[states.length - 1]);
Itinerary subItinerary = graphPathToItineraryMapper.generateItinerary(graphPath);
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java
index 4ce1c616e65..8ec6ac07e34 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java
@@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import javax.annotation.Nullable;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.opentripplanner.framework.geometry.DirectionUtils;
@@ -25,9 +26,11 @@
import org.opentripplanner.street.model.edge.StreetEdge;
import org.opentripplanner.street.model.edge.StreetTransitEntranceLink;
import org.opentripplanner.street.model.vertex.ExitVertex;
+import org.opentripplanner.street.model.vertex.StationEntranceVertex;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.state.State;
+import org.opentripplanner.transit.model.site.Entrance;
/**
* Process a list of states into a list of walking/driving instructions for a street leg.
@@ -158,7 +161,7 @@ private void processState(State backState, State forwardState) {
return;
} else if (edge instanceof StreetTransitEntranceLink link) {
var direction = relativeDirectionForTransitLink(link);
- createAndSaveStep(backState, forwardState, link.getName(), direction, edge);
+ createAndSaveStep(backState, forwardState, link.getName(), direction, edge, link.entrance());
return;
}
@@ -175,8 +178,18 @@ private void processState(State backState, State forwardState) {
if (edge instanceof ElevatorAlightEdge) {
addStep(createElevatorWalkStep(backState, forwardState, edge));
return;
+ } else if (backState.getVertex() instanceof StationEntranceVertex stationEntranceVertex) {
+ addStep(createStationEntranceWalkStep(backState, forwardState, stationEntranceVertex));
+ return;
} else if (edge instanceof PathwayEdge pwe && pwe.signpostedAs().isPresent()) {
- createAndSaveStep(backState, forwardState, pwe.signpostedAs().get(), FOLLOW_SIGNS, edge);
+ createAndSaveStep(
+ backState,
+ forwardState,
+ pwe.signpostedAs().get(),
+ FOLLOW_SIGNS,
+ edge,
+ null
+ );
return;
}
@@ -515,12 +528,33 @@ private WalkStepBuilder createElevatorWalkStep(State backState, State forwardSta
return step;
}
+ private WalkStepBuilder createStationEntranceWalkStep(
+ State backState,
+ State forwardState,
+ StationEntranceVertex vertex
+ ) {
+ Entrance entrance = Entrance
+ .of(vertex.id())
+ .withCode(vertex.code())
+ .withCoordinate(new WgsCoordinate(vertex.getCoordinate()))
+ .withWheelchairAccessibility(vertex.wheelchairAccessibility())
+ .build();
+
+ // don't care what came before or comes after
+ return createWalkStep(forwardState, backState)
+ // There is not a way to definitively determine if a user is entering or exiting the station,
+ // since the doors might be between or inside stations.
+ .withRelativeDirection(RelativeDirection.ENTER_OR_EXIT_STATION)
+ .withEntrance(entrance);
+ }
+
private void createAndSaveStep(
State backState,
State forwardState,
I18NString name,
RelativeDirection direction,
- Edge edge
+ Edge edge,
+ @Nullable Entrance entrance
) {
addStep(
createWalkStep(forwardState, backState)
@@ -528,6 +562,7 @@ private void createAndSaveStep(
.withNameIsDerived(false)
.withDirections(lastAngle, DirectionUtils.getFirstAngle(edge.getGeometry()), false)
.withRelativeDirection(direction)
+ .withEntrance(entrance)
.addDistance(edge.getDistanceMeters())
);
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java
index 8676e863911..fa44d793664 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java
@@ -5,12 +5,19 @@
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
+import java.util.stream.IntStream;
+import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.raptor.api.model.RaptorTransfer;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.street.search.request.StreetSearchRequest;
public class RaptorTransferIndex {
+ private enum RequestSource {
+ SETUP,
+ REQUEST_SCOPE,
+ }
+
private final List[] forwardTransfers;
private final List[] reversedTransfers;
@@ -24,19 +31,47 @@ public RaptorTransferIndex(
this.reversedTransfers = reversedTransfers.stream().map(List::copyOf).toArray(List[]::new);
}
- public static RaptorTransferIndex create(
+ /**
+ * Create an index for a route request configured in router-config.json
+ */
+ public static RaptorTransferIndex createInitialSetup(
+ List> transfersByStopIndex,
+ StreetSearchRequest request
+ ) {
+ return create(transfersByStopIndex, request, RequestSource.SETUP);
+ }
+
+ /**
+ * Create an index for a route request originated from the client
+ */
+ public static RaptorTransferIndex createRequestScope(
List> transfersByStopIndex,
StreetSearchRequest request
+ ) {
+ return create(transfersByStopIndex, request, RequestSource.REQUEST_SCOPE);
+ }
+
+ private static RaptorTransferIndex create(
+ List> transfersByStopIndex,
+ StreetSearchRequest request,
+ RequestSource requestSource
) {
var forwardTransfers = new ArrayList>(transfersByStopIndex.size());
var reversedTransfers = new ArrayList>(transfersByStopIndex.size());
StreetMode mode = request.mode();
for (int i = 0; i < transfersByStopIndex.size(); i++) {
+ forwardTransfers.add(new ArrayList<>());
reversedTransfers.add(new ArrayList<>());
}
- for (int fromStop = 0; fromStop < transfersByStopIndex.size(); fromStop++) {
+ var stopIndices = IntStream.range(0, transfersByStopIndex.size());
+ // we want to always parallelize the cache building during the startup
+ // and only parallelize during runtime requests if the feature flag is on
+ if (requestSource == RequestSource.SETUP || OTPFeature.ParallelRouting.isOn()) {
+ stopIndices = stopIndices.parallel();
+ }
+ stopIndices.forEach(fromStop -> {
// The transfers are filtered so that there is only one possible directional transfer
// for a stop pair.
var transfers = transfersByStopIndex
@@ -49,15 +84,18 @@ public static RaptorTransferIndex create(
)
.values();
- forwardTransfers.add(new ArrayList<>(transfers));
+ // forwardTransfers is not modified here, and no two threads will access the same element
+ // in it, so this is still thread safe.
+ forwardTransfers.get(fromStop).addAll(transfers);
+ });
- for (RaptorTransfer forwardTransfer : transfers) {
+ for (int fromStop = 0; fromStop < transfersByStopIndex.size(); fromStop++) {
+ for (var forwardTransfer : forwardTransfers.get(fromStop)) {
reversedTransfers
.get(forwardTransfer.stop())
.add(DefaultRaptorTransfer.reverseOf(fromStop, forwardTransfer));
}
}
-
return new RaptorTransferIndex(forwardTransfers, reversedTransfers);
}
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/Transfer.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/Transfer.java
index 2643067398e..20a36376ae7 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/Transfer.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/Transfer.java
@@ -15,7 +15,7 @@
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.search.request.StreetSearchRequest;
import org.opentripplanner.street.search.state.EdgeTraverser;
-import org.opentripplanner.street.search.state.State;
+import org.opentripplanner.street.search.state.StateEditor;
import org.opentripplanner.utils.logging.Throttle;
import org.opentripplanner.utils.tostring.ToStringBuilder;
import org.slf4j.Logger;
@@ -97,8 +97,10 @@ public Optional asRaptorTransfer(StreetSearchRequest request) {
);
}
- var initialStates = State.getInitialStates(Set.of(edges.getFirst().getFromVertex()), request);
- var state = EdgeTraverser.traverseEdges(initialStates, edges);
+ StateEditor se = new StateEditor(edges.get(0).getFromVertex(), request);
+ se.setTimeSeconds(0);
+
+ var state = EdgeTraverser.traverseEdges(se.makeState(), edges);
return state.map(s ->
new DefaultRaptorTransfer(
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRequestTransferCache.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRequestTransferCache.java
index d778f491142..80814fdeee2 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRequestTransferCache.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRequestTransferCache.java
@@ -36,7 +36,7 @@ public LoadingCache getTransferCache() {
public void put(List> transfersByStopIndex, RouteRequest request) {
final CacheKey cacheKey = new CacheKey(transfersByStopIndex, request);
- final RaptorTransferIndex raptorTransferIndex = RaptorTransferIndex.create(
+ final RaptorTransferIndex raptorTransferIndex = RaptorTransferIndex.createInitialSetup(
transfersByStopIndex,
cacheKey.request
);
@@ -58,7 +58,10 @@ private CacheLoader cacheLoader() {
@Override
public RaptorTransferIndex load(CacheKey cacheKey) {
LOG.info("Adding runtime request to cache: {}", cacheKey.options);
- return RaptorTransferIndex.create(cacheKey.transfersByStopIndex, cacheKey.request);
+ return RaptorTransferIndex.createRequestScope(
+ cacheKey.transfersByStopIndex,
+ cacheKey.request
+ );
}
};
}
diff --git a/application/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java b/application/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java
index a152b96682d..8565952e557 100644
--- a/application/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java
+++ b/application/src/main/java/org/opentripplanner/routing/graph/SerializedGraphObject.java
@@ -25,6 +25,7 @@
import org.opentripplanner.model.projectinfo.GraphFileHeader;
import org.opentripplanner.model.projectinfo.OtpProjectInfo;
import org.opentripplanner.routing.graph.kryosupport.KryoBuilder;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.VehicleParkingRepository;
import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository;
import org.opentripplanner.standalone.config.BuildConfig;
@@ -56,6 +57,10 @@ public class SerializedGraphObject implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(SerializedGraphObject.class);
public final Graph graph;
+
+ @Nullable
+ public final OsmInfoGraphBuildRepository osmInfoGraphBuildRepository;
+
public final TimetableRepository timetableRepository;
public final WorldEnvelopeRepository worldEnvelopeRepository;
private final Collection edges;
@@ -84,6 +89,7 @@ public class SerializedGraphObject implements Serializable {
public SerializedGraphObject(
Graph graph,
+ @Nullable OsmInfoGraphBuildRepository osmInfoGraphBuildRepository,
TimetableRepository timetableRepository,
WorldEnvelopeRepository worldEnvelopeRepository,
VehicleParkingRepository parkingRepository,
@@ -96,6 +102,7 @@ public SerializedGraphObject(
) {
this.graph = graph;
this.edges = graph.getEdges();
+ this.osmInfoGraphBuildRepository = osmInfoGraphBuildRepository;
this.timetableRepository = timetableRepository;
this.worldEnvelopeRepository = worldEnvelopeRepository;
this.parkingRepository = parkingRepository;
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..2f09d618ffd 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,43 @@ private DisposableEdgeCollection link(
return tempEdges;
}
+ /**
+ * Link a boarding location vertex to specific street edges.
+ *
+ * This is used if a platform is mapped as a linear way, where the given edges form the platform.
+ */
+ 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 +281,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 +317,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/service/osminfo/OsmInfoGraphBuildRepository.java b/application/src/main/java/org/opentripplanner/service/osminfo/OsmInfoGraphBuildRepository.java
new file mode 100644
index 00000000000..ac8f7276072
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/service/osminfo/OsmInfoGraphBuildRepository.java
@@ -0,0 +1,23 @@
+package org.opentripplanner.service.osminfo;
+
+import java.io.Serializable;
+import java.util.Optional;
+import org.opentripplanner.service.osminfo.model.Platform;
+import org.opentripplanner.street.model.edge.Edge;
+
+/**
+ * Store OSM data used during graph build, but discard it after it is complete.
+ *
+ * This is a repository to support the {@link OsmInfoGraphBuildService}.
+ */
+public interface OsmInfoGraphBuildRepository extends Serializable {
+ /**
+ * Associate the edge with a platform
+ */
+ void addPlatform(Edge edge, Platform platform);
+
+ /**
+ * Find the platform the edge belongs to
+ */
+ Optional findPlatform(Edge edge);
+}
diff --git a/application/src/main/java/org/opentripplanner/service/osminfo/OsmInfoGraphBuildService.java b/application/src/main/java/org/opentripplanner/service/osminfo/OsmInfoGraphBuildService.java
new file mode 100644
index 00000000000..6a50c3c92be
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/service/osminfo/OsmInfoGraphBuildService.java
@@ -0,0 +1,25 @@
+package org.opentripplanner.service.osminfo;
+
+import java.util.Optional;
+import org.opentripplanner.service.osminfo.model.Platform;
+import org.opentripplanner.street.model.edge.Edge;
+
+/**
+ * The responsibility of this service is to provide information from Open Street Map, which
+ * is NOT in the OTP street graph. The graph build happens in phases, and some data is read in
+ * from the OSM files, but needed later on. For example, we might need info from OSM to link street
+ * edges/vertexes with transit stops/platforms. We do not want to put data in the OTP street graph
+ * unless it is relevant for routing. So, for information that is read by the OsmGraphBuilder, but
+ * needed later on, we have this service.
+ *
+ * THIS SERVICE IS ONLY AVAILABLE DURING GRAPH BUILD, NOT DURING ROUTING. *
+ */
+public interface OsmInfoGraphBuildService {
+ /**
+ * Find the platform the given edge is part of.
+ *
+ * TODO: This service currently only stores linear platforms, but area platforms and
+ * node platforms should be supported as well.
+ */
+ Optional findPlatform(Edge edge);
+}
diff --git a/application/src/main/java/org/opentripplanner/service/osminfo/configure/OsmInfoGraphBuildRepositoryModule.java b/application/src/main/java/org/opentripplanner/service/osminfo/configure/OsmInfoGraphBuildRepositoryModule.java
new file mode 100644
index 00000000000..d8b9db5608e
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/service/osminfo/configure/OsmInfoGraphBuildRepositoryModule.java
@@ -0,0 +1,12 @@
+package org.opentripplanner.service.osminfo.configure;
+
+import dagger.Binds;
+import dagger.Module;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildRepository;
+
+@Module
+public interface OsmInfoGraphBuildRepositoryModule {
+ @Binds
+ OsmInfoGraphBuildRepository bind(DefaultOsmInfoGraphBuildRepository repository);
+}
diff --git a/application/src/main/java/org/opentripplanner/service/osminfo/configure/OsmInfoGraphBuildServiceModule.java b/application/src/main/java/org/opentripplanner/service/osminfo/configure/OsmInfoGraphBuildServiceModule.java
new file mode 100644
index 00000000000..c6ac5c31ec9
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/service/osminfo/configure/OsmInfoGraphBuildServiceModule.java
@@ -0,0 +1,12 @@
+package org.opentripplanner.service.osminfo.configure;
+
+import dagger.Binds;
+import dagger.Module;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildService;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildService;
+
+@Module
+public interface OsmInfoGraphBuildServiceModule {
+ @Binds
+ OsmInfoGraphBuildService bind(DefaultOsmInfoGraphBuildService service);
+}
diff --git a/application/src/main/java/org/opentripplanner/service/osminfo/internal/DefaultOsmInfoGraphBuildRepository.java b/application/src/main/java/org/opentripplanner/service/osminfo/internal/DefaultOsmInfoGraphBuildRepository.java
new file mode 100644
index 00000000000..6505fdd67a4
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/service/osminfo/internal/DefaultOsmInfoGraphBuildRepository.java
@@ -0,0 +1,37 @@
+package org.opentripplanner.service.osminfo.internal;
+
+import jakarta.inject.Inject;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
+import org.opentripplanner.service.osminfo.model.Platform;
+import org.opentripplanner.street.model.edge.Edge;
+
+public class DefaultOsmInfoGraphBuildRepository
+ implements OsmInfoGraphBuildRepository, Serializable {
+
+ private final Map platforms = new HashMap<>();
+
+ @Inject
+ public DefaultOsmInfoGraphBuildRepository() {}
+
+ @Override
+ public void addPlatform(Edge edge, Platform platform) {
+ Objects.requireNonNull(edge);
+ Objects.requireNonNull(platform);
+ this.platforms.put(edge, platform);
+ }
+
+ @Override
+ public Optional findPlatform(Edge edge) {
+ return Optional.ofNullable(platforms.get(edge));
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultOsmInfoGraphBuildRepository{platforms size = " + platforms.size() + "}";
+ }
+}
diff --git a/application/src/main/java/org/opentripplanner/service/osminfo/internal/DefaultOsmInfoGraphBuildService.java b/application/src/main/java/org/opentripplanner/service/osminfo/internal/DefaultOsmInfoGraphBuildService.java
new file mode 100644
index 00000000000..42eb5bf364f
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/service/osminfo/internal/DefaultOsmInfoGraphBuildService.java
@@ -0,0 +1,28 @@
+package org.opentripplanner.service.osminfo.internal;
+
+import jakarta.inject.Inject;
+import java.util.Optional;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildService;
+import org.opentripplanner.service.osminfo.model.Platform;
+import org.opentripplanner.street.model.edge.Edge;
+
+public class DefaultOsmInfoGraphBuildService implements OsmInfoGraphBuildService {
+
+ private final OsmInfoGraphBuildRepository repository;
+
+ @Inject
+ public DefaultOsmInfoGraphBuildService(OsmInfoGraphBuildRepository repository) {
+ this.repository = repository;
+ }
+
+ @Override
+ public Optional findPlatform(Edge edge) {
+ return repository.findPlatform(edge);
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultOsmInfoGraphBuildService{ repository=" + repository + '}';
+ }
+}
diff --git a/application/src/main/java/org/opentripplanner/service/osminfo/model/Platform.java b/application/src/main/java/org/opentripplanner/service/osminfo/model/Platform.java
new file mode 100644
index 00000000000..91d78385a34
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/service/osminfo/model/Platform.java
@@ -0,0 +1,7 @@
+package org.opentripplanner.service.osminfo.model;
+
+import java.util.Set;
+import org.locationtech.jts.geom.LineString;
+import org.opentripplanner.framework.i18n.I18NString;
+
+public record Platform(I18NString name, LineString geometry, Set references) {}
diff --git a/application/src/main/java/org/opentripplanner/standalone/OTPMain.java b/application/src/main/java/org/opentripplanner/standalone/OTPMain.java
index ade5067a981..25eea6df473 100644
--- a/application/src/main/java/org/opentripplanner/standalone/OTPMain.java
+++ b/application/src/main/java/org/opentripplanner/standalone/OTPMain.java
@@ -150,6 +150,7 @@ private static void startOTPServer(CommandLineParameters cli) {
// with using the embedded router config.
new SerializedGraphObject(
app.graph(),
+ app.osmInfoGraphBuildRepository(),
app.timetableRepository(),
app.worldEnvelopeRepository(),
app.vehicleParkingRepository(),
diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java
index 1b2ec0ed74d..5cc67844b50 100644
--- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java
+++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java
@@ -1,6 +1,7 @@
package org.opentripplanner.standalone.config.buildconfig;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2;
+import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7;
import org.opentripplanner.graph_builder.module.osm.parameters.OsmExtractParameters;
import org.opentripplanner.graph_builder.module.osm.parameters.OsmExtractParametersBuilder;
@@ -84,6 +85,14 @@ public static OsmExtractParametersBuilder mapOsmGenericParameters(
)
.docDefaultValue(docDefaults.timeZone())
.asZoneId(defaults.timeZone())
+ )
+ .withIncludeOsmSubwayEntrances(
+ node
+ .of("includeOsmSubwayEntrances")
+ .since(V2_7)
+ .summary("Whether to include subway entrances from the OSM data." + documentationAddition)
+ .docDefaultValue(docDefaults.includeOsmSubwayEntrances())
+ .asBoolean(defaults.includeOsmSubwayEntrances())
);
}
}
diff --git a/application/src/main/java/org/opentripplanner/standalone/config/framework/project/EnvironmentVariableReplacer.java b/application/src/main/java/org/opentripplanner/standalone/config/framework/project/EnvironmentVariableReplacer.java
index 17910fa62ca..c71c1237d3f 100644
--- a/application/src/main/java/org/opentripplanner/standalone/config/framework/project/EnvironmentVariableReplacer.java
+++ b/application/src/main/java/org/opentripplanner/standalone/config/framework/project/EnvironmentVariableReplacer.java
@@ -3,12 +3,12 @@
import static java.util.Map.entry;
import static org.opentripplanner.model.projectinfo.OtpProjectInfo.projectInfo;
-import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.annotation.Nullable;
import org.opentripplanner.framework.application.OtpAppException;
+import org.opentripplanner.utils.text.TextVariablesSubstitution;
/**
* Replaces environment variable placeholders specified on the format ${variable} in a text with the
@@ -58,46 +58,47 @@ public class EnvironmentVariableReplacer {
* Search for {@link #PATTERN}s and replace each placeholder with the value of the corresponding
* environment variable.
*
- * @param source is used only to generate human friendly error message in case the text contain a
- * placeholder which can not be found.
- * @throws IllegalArgumentException if a placeholder exist in the {@code text}, but the
- * environment variable do not exist.
+ * @param source is used only to generate a human friendly error message in case the text
+ * contains a placeholder which cannot be found.
+ * @throws IllegalArgumentException if a placeholder exists in the {@code text}, but the
+ * environment variable does not exist.
*/
public static String insertEnvironmentVariables(String text, String source) {
- return insertVariables(text, source, System::getenv);
+ return insertVariables(text, source, EnvironmentVariableReplacer::getEnvVarOrProjectInfo);
}
+ /**
+ * Same as {@link #insertEnvironmentVariables(String, String)}, but the caller mus provide the
+ * {@code variableResolver} - environment and project info variables are not available.
+ */
public static String insertVariables(
String text,
String source,
- Function getEnvVar
+ Function variableResolver
) {
- Map substitutions = new HashMap<>();
- Matcher matcher = PATTERN.matcher(text);
+ return TextVariablesSubstitution.insertVariables(
+ text,
+ variableResolver,
+ varName -> errorVariableNameNotFound(varName, source)
+ );
+ }
- while (matcher.find()) {
- String subKey = matcher.group(0);
- String nameOnly = matcher.group(1);
- if (!substitutions.containsKey(nameOnly)) {
- String value = getEnvVar.apply(nameOnly);
- if (value != null) {
- substitutions.put(subKey, value);
- } else if (PROJECT_INFO.containsKey(nameOnly)) {
- substitutions.put(subKey, PROJECT_INFO.get(nameOnly));
- } else {
- throw new OtpAppException(
- "Environment variable name '" +
- nameOnly +
- "' in config '" +
- source +
- "' not found in the system environment variables."
- );
- }
- }
+ @Nullable
+ private static String getEnvVarOrProjectInfo(String key) {
+ String value = System.getenv(key);
+ if (value == null) {
+ return PROJECT_INFO.get(key);
}
- for (Map.Entry entry : substitutions.entrySet()) {
- text = text.replace(entry.getKey(), entry.getValue());
- }
- return text;
+ return value;
+ }
+
+ private static void errorVariableNameNotFound(String variableName, String source) {
+ throw new OtpAppException(
+ "Environment variable name '" +
+ variableName +
+ "' in config '" +
+ source +
+ "' not found in the system environment variables."
+ );
}
}
diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/ServerConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/ServerConfig.java
index a75300f62a2..64921eb813b 100644
--- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/ServerConfig.java
+++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/ServerConfig.java
@@ -1,9 +1,12 @@
package org.opentripplanner.standalone.config.routerconfig;
+import static org.opentripplanner.standalone.config.framework.json.EnumMapper.docEnumValueList;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_4;
+import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7;
import java.time.Duration;
import java.util.List;
+import org.opentripplanner.apis.support.graphql.injectdoc.ApiDocumentationProfile;
import org.opentripplanner.framework.application.OtpAppException;
import org.opentripplanner.standalone.config.framework.json.NodeAdapter;
import org.opentripplanner.standalone.server.OTPWebApplicationParameters;
@@ -13,6 +16,7 @@ public class ServerConfig implements OTPWebApplicationParameters {
private final Duration apiProcessingTimeout;
private final List traceParameters;
+ private final ApiDocumentationProfile apiDocumentationProfile;
public ServerConfig(String parameterName, NodeAdapter root) {
NodeAdapter c = root
@@ -42,6 +46,14 @@ public ServerConfig(String parameterName, NodeAdapter root) {
)
.asDuration(Duration.ofSeconds(-1));
+ this.apiDocumentationProfile =
+ c
+ .of("apiDocumentationProfile")
+ .since(V2_7)
+ .summary(ApiDocumentationProfile.DEFAULT.typeDescription())
+ .description(docEnumValueList(ApiDocumentationProfile.values()))
+ .asEnum(ApiDocumentationProfile.DEFAULT);
+
this.traceParameters =
c
.of("traceParameters")
@@ -105,6 +117,15 @@ public Duration apiProcessingTimeout() {
return apiProcessingTimeout;
}
+ @Override
+ public List traceParameters() {
+ return traceParameters;
+ }
+
+ public ApiDocumentationProfile apiDocumentationProfile() {
+ return apiDocumentationProfile;
+ }
+
public void validate(Duration streetRoutingTimeout) {
if (
!apiProcessingTimeout.isNegative() &&
@@ -119,9 +140,4 @@ public void validate(Duration streetRoutingTimeout) {
);
}
}
-
- @Override
- public List traceParameters() {
- return traceParameters;
- }
}
diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java
index b4edbb36299..eeaaf6427cb 100644
--- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java
+++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java
@@ -18,6 +18,7 @@
import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerMapper;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerUpdater;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository;
import org.opentripplanner.service.vehicleparking.VehicleParkingRepository;
import org.opentripplanner.service.vehicleparking.VehicleParkingService;
@@ -64,6 +65,11 @@ public class ConstructApplication {
private final CommandLineParameters cli;
private final GraphBuilderDataSources graphBuilderDataSources;
+ /**
+ * The OSM Info is injected into the graph-builder, but not the web-server; Hence not part of
+ * the application context.
+ */
+ private final OsmInfoGraphBuildRepository osmInfoGraphBuildRepository;
private final ConstructApplicationFactory factory;
/**
@@ -72,6 +78,7 @@ public class ConstructApplication {
ConstructApplication(
CommandLineParameters cli,
Graph graph,
+ OsmInfoGraphBuildRepository osmInfoGraphBuildRepository,
TimetableRepository timetableRepository,
WorldEnvelopeRepository worldEnvelopeRepository,
ConfigModel config,
@@ -84,6 +91,7 @@ public class ConstructApplication {
) {
this.cli = cli;
this.graphBuilderDataSources = graphBuilderDataSources;
+ this.osmInfoGraphBuildRepository = osmInfoGraphBuildRepository;
// We create the optional GraphVisualizer here, because it would be significant more complex to
// use Dagger DI to do it - passing in a parameter to enable it or not.
@@ -130,7 +138,8 @@ public GraphBuilder createGraphBuilder() {
buildConfig(),
graphBuilderDataSources,
graph(),
- timetableRepository(),
+ osmInfoGraphBuildRepository,
+ factory.timetableRepository(),
factory.worldEnvelopeRepository(),
factory.vehicleParkingRepository(),
factory.emissionsDataModel(),
@@ -183,6 +192,7 @@ private void setupTransitRoutingServer() {
routerConfig().transmodelApi(),
timetableRepository(),
routerConfig().routingRequestDefaults(),
+ routerConfig().server().apiDocumentationProfile(),
routerConfig().transitTuningConfig()
);
}
@@ -261,6 +271,10 @@ public DataImportIssueSummary dataImportIssueSummary() {
return factory.dataImportIssueSummary();
}
+ public OsmInfoGraphBuildRepository osmInfoGraphBuildRepository() {
+ return osmInfoGraphBuildRepository;
+ }
+
public StopConsolidationRepository stopConsolidationRepository() {
return factory.stopConsolidationRepository();
}
diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java
index d6310c0c616..3d479f0fa63 100644
--- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java
+++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java
@@ -51,21 +51,21 @@
@Component(
modules = {
ConfigModule.class,
- TransitModule.class,
- WorldEnvelopeServiceModule.class,
+ ConstructApplicationModule.class,
+ EmissionsServiceModule.class,
+ GeocoderModule.class,
+ InteractiveLauncherModule.class,
RealtimeVehicleServiceModule.class,
RealtimeVehicleRepositoryModule.class,
- VehicleRentalServiceModule.class,
- VehicleRentalRepositoryModule.class,
- VehicleParkingServiceModule.class,
- ConstructApplicationModule.class,
RideHailingServicesModule.class,
- EmissionsServiceModule.class,
+ TransitModule.class,
+ VehicleParkingServiceModule.class,
+ VehicleRentalRepositoryModule.class,
+ VehicleRentalServiceModule.class,
SorlandsbanenNorwayModule.class,
StopConsolidationServiceModule.class,
- InteractiveLauncherModule.class,
StreetLimitationParametersServiceModule.class,
- GeocoderModule.class,
+ WorldEnvelopeServiceModule.class,
}
)
public interface ConstructApplicationFactory {
diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java b/application/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java
index 021af778345..ad1a7293855 100644
--- a/application/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java
+++ b/application/src/main/java/org/opentripplanner/standalone/configure/LoadApplication.java
@@ -8,6 +8,7 @@
import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.SerializedGraphObject;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.VehicleParkingRepository;
import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository;
import org.opentripplanner.standalone.config.CommandLineParameters;
@@ -20,7 +21,7 @@
* This is used to load the graph, and finally this class can create the
* {@link ConstructApplication} for the next phase.
*
- * By splitting the these two responsibilities into two separate phases we are sure all
+ * By splitting these two responsibilities into two separate phases we are sure all
* components (graph and transit model) created in the load phase will be available for
* creating the application using Dagger dependency injection.
*/
@@ -55,6 +56,7 @@ public DataSource getInputGraphDataStore() {
public ConstructApplication appConstruction(SerializedGraphObject obj) {
return createAppConstruction(
obj.graph,
+ obj.osmInfoGraphBuildRepository,
obj.timetableRepository,
obj.worldEnvelopeRepository,
obj.parkingRepository,
@@ -69,6 +71,7 @@ public ConstructApplication appConstruction(SerializedGraphObject obj) {
public ConstructApplication appConstruction() {
return createAppConstruction(
factory.emptyGraph(),
+ factory.emptyOsmInfoGraphBuildRepository(),
factory.emptyTimetableRepository(),
factory.emptyWorldEnvelopeRepository(),
factory.emptyVehicleParkingRepository(),
@@ -92,6 +95,7 @@ public ConfigModel config() {
private ConstructApplication createAppConstruction(
Graph graph,
+ OsmInfoGraphBuildRepository osmInfoGraphBuildRepository,
TimetableRepository timetableRepository,
WorldEnvelopeRepository worldEnvelopeRepository,
VehicleParkingRepository parkingRepository,
@@ -103,6 +107,7 @@ private ConstructApplication createAppConstruction(
return new ConstructApplication(
cli,
graph,
+ osmInfoGraphBuildRepository,
timetableRepository,
worldEnvelopeRepository,
config(),
diff --git a/application/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java b/application/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java
index b054fac3ca5..9fdbf59bfda 100644
--- a/application/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java
+++ b/application/src/main/java/org/opentripplanner/standalone/configure/LoadApplicationFactory.java
@@ -11,6 +11,8 @@
import org.opentripplanner.ext.stopconsolidation.configure.StopConsolidationRepositoryModule;
import org.opentripplanner.graph_builder.GraphBuilderDataSources;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
+import org.opentripplanner.service.osminfo.configure.OsmInfoGraphBuildRepositoryModule;
import org.opentripplanner.service.vehicleparking.VehicleParkingRepository;
import org.opentripplanner.service.vehicleparking.configure.VehicleParkingRepositoryModule;
import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository;
@@ -30,6 +32,7 @@
LoadConfigModule.class,
DataStoreModule.class,
GsDataSourceModule.class,
+ OsmInfoGraphBuildRepositoryModule.class,
WorldEnvelopeRepositoryModule.class,
StopConsolidationRepositoryModule.class,
VehicleParkingRepositoryModule.class,
@@ -43,6 +46,9 @@ public interface LoadApplicationFactory {
@Singleton
Graph emptyGraph();
+ @Singleton
+ OsmInfoGraphBuildRepository emptyOsmInfoGraphBuildRepository();
+
@Singleton
TimetableRepository emptyTimetableRepository();
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));
}
diff --git a/application/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntranceLink.java b/application/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntranceLink.java
index 7145f6183e4..34ca3faeeb3 100644
--- a/application/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntranceLink.java
+++ b/application/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntranceLink.java
@@ -2,6 +2,7 @@
import org.opentripplanner.street.model.vertex.StreetVertex;
import org.opentripplanner.street.model.vertex.TransitEntranceVertex;
+import org.opentripplanner.transit.model.site.Entrance;
/**
* This represents the connection between a street vertex and a transit vertex belonging the street
@@ -43,6 +44,18 @@ public boolean isExit() {
return !isEntrance;
}
+ /**
+ * Get the {@link Entrance} that this edge links to.
+ */
+ public Entrance entrance() {
+ if (getToVertex() instanceof TransitEntranceVertex tev) {
+ return tev.getEntrance();
+ } else if (getFromVertex() instanceof TransitEntranceVertex tev) {
+ return tev.getEntrance();
+ }
+ throw new IllegalStateException("%s doesn't link to an entrance.".formatted(this));
+ }
+
protected int getStreetToStopTime() {
return 0;
}
diff --git a/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java b/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java
new file mode 100644
index 00000000000..7b9a94b0725
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java
@@ -0,0 +1,58 @@
+package org.opentripplanner.street.model.vertex;
+
+import javax.annotation.Nullable;
+import org.opentripplanner.transit.model.basic.Accessibility;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+import org.opentripplanner.utils.tostring.ToStringBuilder;
+
+/**
+ * A station entrance extracted from OSM and therefore not (yet) associated with the transit
+ * entity {@link org.opentripplanner.transit.model.site.Station}.
+ */
+public class StationEntranceVertex extends OsmVertex {
+
+ private static final String FEED_ID = "osm";
+ private final String code;
+ private final Accessibility wheelchairAccessibility;
+
+ public StationEntranceVertex(
+ double lat,
+ double lon,
+ long nodeId,
+ String code,
+ Accessibility wheelchairAccessibility
+ ) {
+ super(lat, lon, nodeId);
+ this.code = code;
+ this.wheelchairAccessibility = wheelchairAccessibility;
+ }
+
+ /**
+ * The id of the entrance which may or may not be human-readable.
+ */
+ public FeedScopedId id() {
+ return new FeedScopedId(FEED_ID, String.valueOf(nodeId));
+ }
+
+ /**
+ * Short human-readable code of the exit, like A or H3.
+ * If we need a proper name like "Oranienplatz" we have to add a name field.
+ */
+ @Nullable
+ public String code() {
+ return code;
+ }
+
+ public Accessibility wheelchairAccessibility() {
+ return wheelchairAccessibility;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder
+ .of(StationEntranceVertex.class)
+ .addNum("nodeId", nodeId)
+ .addStr("code", code)
+ .toString();
+ }
+}
diff --git a/application/src/main/java/org/opentripplanner/street/model/vertex/VertexFactory.java b/application/src/main/java/org/opentripplanner/street/model/vertex/VertexFactory.java
index 422fc16c837..393502ba3be 100644
--- a/application/src/main/java/org/opentripplanner/street/model/vertex/VertexFactory.java
+++ b/application/src/main/java/org/opentripplanner/street/model/vertex/VertexFactory.java
@@ -11,6 +11,7 @@
import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace;
import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex;
import org.opentripplanner.street.model.edge.StreetEdge;
+import org.opentripplanner.transit.model.basic.Accessibility;
import org.opentripplanner.transit.model.site.BoardingArea;
import org.opentripplanner.transit.model.site.Entrance;
import org.opentripplanner.transit.model.site.PathwayNode;
@@ -94,6 +95,17 @@ public ExitVertex exit(long nid, Coordinate coordinate, String exitName) {
return addToGraph(new ExitVertex(coordinate.x, coordinate.y, nid, exitName));
}
+ public StationEntranceVertex stationEntrance(
+ long nid,
+ Coordinate coordinate,
+ String code,
+ Accessibility wheelchairAccessibility
+ ) {
+ return addToGraph(
+ new StationEntranceVertex(coordinate.x, coordinate.y, nid, code, wheelchairAccessibility)
+ );
+ }
+
public OsmVertex osm(
Coordinate coordinate,
OsmNode node,
diff --git a/application/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java b/application/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java
index df8933cd22d..c93ea598256 100644
--- a/application/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java
+++ b/application/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java
@@ -124,10 +124,6 @@ public DataOverlayContext dataOverlayContext() {
return dataOverlayContext;
}
- public StreetSearchRequestBuilder copyOf(Instant time) {
- return copyOf(this).withStartTime(time);
- }
-
public StreetSearchRequestBuilder copyOfReversed(Instant time) {
return copyOf(this).withStartTime(time).withArriveBy(!arriveBy);
}
diff --git a/application/src/main/java/org/opentripplanner/street/search/state/EdgeTraverser.java b/application/src/main/java/org/opentripplanner/street/search/state/EdgeTraverser.java
index 502d014e358..8755f014e14 100644
--- a/application/src/main/java/org/opentripplanner/street/search/state/EdgeTraverser.java
+++ b/application/src/main/java/org/opentripplanner/street/search/state/EdgeTraverser.java
@@ -2,10 +2,7 @@
import java.util.Collection;
import java.util.Optional;
-import org.opentripplanner.astar.model.ShortestPathTree;
import org.opentripplanner.street.model.edge.Edge;
-import org.opentripplanner.street.model.vertex.Vertex;
-import org.opentripplanner.street.search.strategy.DominanceFunctions;
/**
* This is a very reduced version of the A* algorithm: from an initial state a number of edges are
@@ -17,49 +14,24 @@
*/
public class EdgeTraverser {
- public static Optional traverseEdges(
- final Collection initialStates,
- final Collection edges
- ) {
- return traverseEdges(initialStates.toArray(new State[0]), edges);
- }
-
- public static Optional traverseEdges(
- final State[] initialStates,
- final Collection edges
- ) {
- if (edges.isEmpty()) {
- return Optional.of(initialStates[0]);
- }
-
- // The shortest path tree is used to prune dominated parallel states. For example,
- // CAR_PICKUP can return both a CAR/WALK state after each traversal of which only
- // the optimal states need to be continued.
- var dominanceFunction = new DominanceFunctions.MinimumWeight();
- var spt = new ShortestPathTree<>(dominanceFunction);
- for (State initialState : initialStates) {
- spt.add(initialState);
- }
-
- Vertex lastVertex = null;
- var isArriveBy = initialStates[0].getRequest().arriveBy();
+ public static Optional traverseEdges(final State s, final Collection edges) {
+ var state = s;
for (Edge e : edges) {
- var vertex = isArriveBy ? e.getToVertex() : e.getFromVertex();
- var fromStates = spt.getStates(vertex);
- if (fromStates == null || fromStates.isEmpty()) {
- return Optional.empty();
+ var afterTraversal = e.traverse(state);
+ if (afterTraversal.length > 1) {
+ throw new IllegalStateException(
+ "Expected only a single state returned from edge %s but received %s".formatted(
+ e,
+ afterTraversal.length
+ )
+ );
}
-
- for (State fromState : fromStates) {
- var newToStates = e.traverse(fromState);
- for (State newToState : newToStates) {
- spt.add(newToState);
- }
+ if (State.isEmpty(afterTraversal)) {
+ return Optional.empty();
+ } else {
+ state = afterTraversal[0];
}
-
- lastVertex = isArriveBy ? e.getFromVertex() : e.getToVertex();
}
-
- return Optional.ofNullable(lastVertex).map(spt::getState);
+ return Optional.ofNullable(state);
}
}
diff --git a/application/src/main/java/org/opentripplanner/transit/model/site/StationElementBuilder.java b/application/src/main/java/org/opentripplanner/transit/model/site/StationElementBuilder.java
index 7a7fc0e4621..ea90231bead 100644
--- a/application/src/main/java/org/opentripplanner/transit/model/site/StationElementBuilder.java
+++ b/application/src/main/java/org/opentripplanner/transit/model/site/StationElementBuilder.java
@@ -1,5 +1,6 @@
package org.opentripplanner.transit.model.site;
+import javax.annotation.Nullable;
import org.opentripplanner.framework.geometry.WgsCoordinate;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.transit.model.basic.Accessibility;
@@ -54,7 +55,7 @@ public String code() {
return code;
}
- public B withCode(String code) {
+ public B withCode(@Nullable String code) {
this.code = code;
return instance();
}
diff --git a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
index 537d9b680b4..ce808e546d1 100644
--- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
+++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
@@ -82,6 +82,9 @@ union CallStopLocation = Stop
"Rental place union that represents either a VehicleRentalStation or a RentalVehicle"
union RentalPlace = RentalVehicle | VehicleRentalStation
+"A feature for a step"
+union StepFeature = Entrance
+
union StopPosition = PositionAtStop | PositionBetweenStops
"A public transport agency"
@@ -488,6 +491,18 @@ type Emissions {
co2: Grams
}
+"Station entrance or exit, originating from OSM or GTFS data."
+type Entrance {
+ "ID of the entrance in the format of `FeedId:EntranceId`. If the `FeedId` is `osm`, the entrance originates from OSM data."
+ entranceId: String!
+ "Name of the entrance or exit."
+ name: String
+ "Short text or a number that identifies the entrance or exit for passengers. For example, `A` or `B`."
+ publicCode: String
+ "Whether the entrance or exit is accessible by wheelchair"
+ wheelchairAccessible: WheelchairBoarding
+}
+
"Real-time estimates for an arrival or departure at a certain place."
type EstimatedTime {
"""
@@ -2844,6 +2859,8 @@ type step {
elevationProfile: [elevationProfileComponent]
"When exiting a highway or traffic circle, the exit name/number."
exit: String
+ "Information about an feature associated with a step e.g. an station entrance or exit"
+ feature: StepFeature
"The latitude of the start of the step."
lat: Float
"The longitude of the start of the step."
@@ -3518,15 +3535,40 @@ enum RealtimeState {
UPDATED
}
-"Actions to take relative to the current position when engaging a walking/driving step."
+"""
+A direction that is not absolute but rather fuzzy and context-dependent.
+It provides the passenger with information what they should do in this step depending on where they
+were in the previous one.
+"""
enum RelativeDirection {
CIRCLE_CLOCKWISE
CIRCLE_COUNTERCLOCKWISE
+ """
+ Moving straight ahead in one of these cases
+
+ - Passing through a crossing or intersection.
+ - Passing through a station entrance or exit when it is not know whether the passenger is
+ entering or exiting. If it _is_ known then `ENTER_STATION`/`EXIT_STATION` is used.
+ More information about the entrance is in the `step.feature` field.
+ """
CONTINUE
DEPART
ELEVATOR
+ """
+ Entering a public transport station. If it's not known if the passenger is entering or exiting
+ then `CONTINUE` is used.
+
+ More information about the entrance is in the `step.feature` field.
+ """
ENTER_STATION
+ """
+ Exiting a public transport station. If it's not known if the passenger is entering or exiting
+ then `CONTINUE` is used.
+
+ More information about the entrance is in the `step.feature` field.
+ """
EXIT_STATION
+ "Follow the signs indicating a specific location like \"platform 1\" or \"exit B\"."
FOLLOW_SIGNS
HARD_LEFT
HARD_RIGHT
diff --git a/application/src/test/java/org/opentripplanner/ConstantsForTests.java b/application/src/test/java/org/opentripplanner/ConstantsForTests.java
index e5ab48cee54..3f188ff2c89 100644
--- a/application/src/test/java/org/opentripplanner/ConstantsForTests.java
+++ b/application/src/test/java/org/opentripplanner/ConstantsForTests.java
@@ -34,6 +34,7 @@
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.linking.LinkingDirection;
import org.opentripplanner.routing.linking.VertexLinker;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository;
import org.opentripplanner.service.vehiclerental.model.RentalVehicleType;
import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation;
@@ -137,9 +138,11 @@ public static TestOtpModel buildNewPortlandGraph(boolean withElevation) {
var timetableRepository = new TimetableRepository(new SiteRepository(), deduplicator);
// Add street data from OSM
{
- OsmProvider osmProvider = new OsmProvider(PORTLAND_CENTRAL_OSM, false);
- OsmModule osmModule = OsmModule
- .of(osmProvider, graph, new DefaultVehicleParkingRepository())
+ var osmProvider = new OsmProvider(PORTLAND_CENTRAL_OSM, false);
+ var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository();
+ var vehicleParkingRepository = new DefaultVehicleParkingRepository();
+ var osmModule = OsmModule
+ .of(osmProvider, graph, osmInfoRepository, vehicleParkingRepository)
.withStaticParkAndRide(true)
.withStaticBikeParkAndRide(true)
.build();
@@ -195,9 +198,11 @@ public static TestOtpModel buildOsmGraph(File osmFile) {
var graph = new Graph(deduplicator);
var timetableRepository = new TimetableRepository(siteRepository, deduplicator);
// Add street data from OSM
- OsmProvider osmProvider = new OsmProvider(osmFile, true);
- OsmModule osmModule = OsmModule
- .of(osmProvider, graph, new DefaultVehicleParkingRepository())
+ var osmProvider = new OsmProvider(osmFile, true);
+ var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository();
+ var vehicleParkingRepository = new DefaultVehicleParkingRepository();
+ var osmModule = OsmModule
+ .of(osmProvider, graph, osmInfoRepository, vehicleParkingRepository)
.build();
osmModule.buildGraph();
return new TestOtpModel(graph, timetableRepository);
@@ -245,8 +250,9 @@ public static TestOtpModel buildNewMinimalNetexGraph() {
var timetableRepository = new TimetableRepository(siteRepository, deduplicator);
// Add street data from OSM
{
- OsmProvider osmProvider = new OsmProvider(OSLO_EAST_OSM, false);
- OsmModule osmModule = OsmModule.of(osmProvider, graph, parkingService).build();
+ var osmProvider = new OsmProvider(OSLO_EAST_OSM, false);
+ var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository();
+ var osmModule = OsmModule.of(osmProvider, graph, osmInfoRepository, parkingService).build();
osmModule.buildGraph();
}
// Add transit data from Netex
diff --git a/application/src/test/java/org/opentripplanner/_support/geometry/Coordinates.java b/application/src/test/java/org/opentripplanner/_support/geometry/Coordinates.java
index 33569a34b2e..5a4526012c9 100644
--- a/application/src/test/java/org/opentripplanner/_support/geometry/Coordinates.java
+++ b/application/src/test/java/org/opentripplanner/_support/geometry/Coordinates.java
@@ -6,8 +6,6 @@ public class Coordinates {
public static final Coordinate BERLIN = of(52.5212, 13.4105);
public static final Coordinate BERLIN_BRANDENBURG_GATE = of(52.51627, 13.37770);
- public static final Coordinate BERLIN_FERNSEHTURM = of(52.52084, 13.40934);
- public static final Coordinate BERLIN_ADMIRALBRUCKE = of(52.49526, 13.415093);
public static final Coordinate HAMBURG = of(53.5566, 10.0003);
public static final Coordinate KONGSBERG_PLATFORM_1 = of(59.67216, 9.65107);
public static final Coordinate BOSTON = of(42.36541, -71.06129);
diff --git a/application/src/test/java/org/opentripplanner/_support/text/TextAssertions.java b/application/src/test/java/org/opentripplanner/_support/text/TextAssertions.java
new file mode 100644
index 00000000000..a009b76237c
--- /dev/null
+++ b/application/src/test/java/org/opentripplanner/_support/text/TextAssertions.java
@@ -0,0 +1,64 @@
+package org.opentripplanner._support.text;
+
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * This class contains test assert methods not supported by the standard JUnit
+ * framework.
+ */
+public final class TextAssertions {
+
+ private static final String LINE_DELIMITERS = "(\n|\r|\r\n)";
+ private static final int END_OF_TEXT = -111;
+
+ /**
+
+ * Assert to texts are equals line by line. Empty lines and white-space in the start and end of
+ * a line is ignored.
+ */
+ public static void assertLinesEquals(String expected, String actual) {
+ var expLines = expected.split(LINE_DELIMITERS);
+ var actLines = actual.split(LINE_DELIMITERS);
+
+ int i = -1;
+ int j = -1;
+
+ while (true) {
+ i = next(expLines, i);
+ j = next(actLines, j);
+
+ if (i == END_OF_TEXT && j == END_OF_TEXT) {
+ return;
+ }
+
+ var exp = getLine(expLines, i);
+ var act = getLine(actLines, j);
+
+ if (i == END_OF_TEXT || j == END_OF_TEXT || !exp.equals(act)) {
+ Assertions.fail(
+ "Expected%s: <%s>%n".formatted(lineText(i), exp) +
+ "Actual %s: <%s>%n".formatted(lineText(j), act)
+ );
+ }
+ }
+ }
+
+ private static String lineText(int index) {
+ return index < 0 ? "(@end-of-text)" : "(@line %d)".formatted(index);
+ }
+
+ private static String getLine(String[] lines, int i) {
+ return i == END_OF_TEXT ? "" : lines[i].trim();
+ }
+
+ private static int next(String[] lines, int index) {
+ ++index;
+ while (index < lines.length) {
+ if (!lines[index].isBlank()) {
+ return index;
+ }
+ ++index;
+ }
+ return END_OF_TEXT;
+ }
+}
diff --git a/application/src/test/java/org/opentripplanner/_support/text/TextAssertionsTest.java b/application/src/test/java/org/opentripplanner/_support/text/TextAssertionsTest.java
new file mode 100644
index 00000000000..739b7b59c4b
--- /dev/null
+++ b/application/src/test/java/org/opentripplanner/_support/text/TextAssertionsTest.java
@@ -0,0 +1,49 @@
+package org.opentripplanner._support.text;
+
+import static org.opentripplanner._support.text.TextAssertions.assertLinesEquals;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TextAssertionsTest {
+
+ @Test
+ void testIgnoreWhiteSpace() {
+ // Empty text
+ assertLinesEquals("", "\n\n");
+
+ // Text with white-space inserted
+ assertLinesEquals(
+ """
+ A Test
+ Line 2
+ DOS\r\n
+ line-shift
+ """,
+ """
+
+ A Test \t
+ \t
+
+ \tLine 2
+ DOS\rline-shift
+ """
+ );
+ }
+
+ @Test
+ void testEndOfText() {
+ var ex = Assertions.assertThrows(
+ org.opentest4j.AssertionFailedError.class,
+ () -> assertLinesEquals("A\n", "A\nExtra Line")
+ );
+ Assertions.assertTrue(
+ ex.getMessage().contains("Expected(@end-of-text)"),
+ "<" + ex.getMessage() + "> does not contain expected line."
+ );
+ Assertions.assertTrue(
+ ex.getMessage().contains("Actual (@line 1): "),
+ "<" + ex.getMessage() + "> does not contain actual line."
+ );
+ }
+}
diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
index 7e1bf24287a..2f190502ccc 100644
--- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
+++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java
@@ -66,7 +66,6 @@
import org.opentripplanner.routing.alertpatch.TimePeriod;
import org.opentripplanner.routing.alertpatch.TransitAlert;
import org.opentripplanner.routing.api.request.RouteRequest;
-import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graphfinder.GraphFinder;
import org.opentripplanner.routing.graphfinder.NearbyStop;
import org.opentripplanner.routing.graphfinder.PlaceAtDistance;
@@ -87,6 +86,7 @@
import org.opentripplanner.standalone.config.framework.json.JsonSupport;
import org.opentripplanner.test.support.FilePatternSource;
import org.opentripplanner.transit.model._data.TimetableRepositoryForTest;
+import org.opentripplanner.transit.model.basic.Accessibility;
import org.opentripplanner.transit.model.basic.Money;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.framework.AbstractBuilder;
@@ -96,6 +96,7 @@
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.organization.Agency;
+import org.opentripplanner.transit.model.site.Entrance;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.timetable.RealTimeTripTimes;
@@ -137,8 +138,6 @@ class GraphQLIntegrationTest {
.withSystem("Network-1", "https://foo.bar")
.build();
- static final Graph GRAPH = new Graph();
-
static final Instant ALERT_START_TIME = OffsetDateTime
.parse("2023-02-15T12:03:28+01:00")
.toInstant();
@@ -267,9 +266,20 @@ public Set findRoutes(StopLocation stop) {
.withAbsoluteDirection(20)
.build();
var step2 = walkStep("elevator").withRelativeDirection(RelativeDirection.ELEVATOR).build();
+ FeedScopedId entranceId = new FeedScopedId("osm", "123");
+ Entrance entrance = Entrance
+ .of(entranceId)
+ .withCoordinate(new WgsCoordinate(60, 80))
+ .withCode("A")
+ .withWheelchairAccessibility(Accessibility.POSSIBLE)
+ .build();
+ var step3 = walkStep("entrance")
+ .withRelativeDirection(RelativeDirection.ENTER_OR_EXIT_STATION)
+ .withEntrance(entrance)
+ .build();
Itinerary i1 = newItinerary(A, T11_00)
- .walk(20, B, List.of(step1, step2))
+ .walk(20, B, List.of(step1, step2, step3))
.bus(busRoute, 122, T11_01, T11_15, C)
.rail(439, T11_30, T11_50, D)
.carHail(D10m, E)
diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapperTest.java
index 2c69f3dca46..1dcd6e210a3 100644
--- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapperTest.java
+++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapperTest.java
@@ -23,6 +23,7 @@ void absoluteDirection() {
void relativeDirection() {
Arrays
.stream(RelativeDirection.values())
+ .filter(v -> v != RelativeDirection.ENTER_OR_EXIT_STATION)
.forEach(d -> {
var mapped = DirectionMapper.map(d);
assertEquals(d.toString(), mapped.toString());
diff --git a/application/src/test/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentationTest.java b/application/src/test/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentationTest.java
new file mode 100644
index 00000000000..dc9356530b6
--- /dev/null
+++ b/application/src/test/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentationTest.java
@@ -0,0 +1,76 @@
+package org.opentripplanner.apis.support.graphql.injectdoc;
+
+import static java.util.Optional.empty;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.jupiter.api.Test;
+
+class CustomDocumentationTest {
+
+ private static final String ORIGINAL_DOC = "Original";
+
+ // We use a HashMap to allow inserting 'null' values
+ private static final Map PROPERTIES = new HashMap<>(Map.ofEntries());
+
+ static {
+ PROPERTIES.put("Type1.description", "Doc 1");
+ PROPERTIES.put("Type2.description.append", "Doc 2");
+ PROPERTIES.put("Type3.description", null);
+ PROPERTIES.put("Type.field1.description", "Doc f1");
+ PROPERTIES.put("Type.field2.deprecated", "Deprecated f2");
+ PROPERTIES.put("Type.field3.description.append", "Doc f3");
+ PROPERTIES.put("Type.field4.deprecated.append", "Deprecated f4");
+ PROPERTIES.put("Type.field5.description", null);
+ }
+
+ private final CustomDocumentation subject = new CustomDocumentation(PROPERTIES);
+
+ @Test
+ void testCreate() {
+ var defaultDoc = CustomDocumentation.of(ApiDocumentationProfile.DEFAULT);
+ assertTrue(defaultDoc.isEmpty());
+
+ var enturDoc = CustomDocumentation.of(ApiDocumentationProfile.ENTUR);
+ assertFalse(enturDoc.isEmpty());
+ }
+
+ @Test
+ void testTypeDescriptionWithUnknownKey() {
+ assertEquals(empty(), subject.typeDescription("", ORIGINAL_DOC));
+ assertEquals(empty(), subject.typeDescription("ANY_KEY", ORIGINAL_DOC));
+ assertEquals(empty(), subject.typeDescription("ANY_KEY", null));
+ }
+
+ @Test
+ void testTypeDescription() {
+ assertEquals(Optional.of("Doc 1"), subject.typeDescription("Type1", ORIGINAL_DOC));
+ assertEquals(
+ Optional.of(ORIGINAL_DOC + "\n\nDoc 2"),
+ subject.typeDescription("Type2", ORIGINAL_DOC)
+ );
+ assertEquals(Optional.empty(), subject.typeDescription("Type3", ORIGINAL_DOC));
+ }
+
+ @Test
+ void testFieldDescription() {
+ assertEquals(Optional.of("Doc f1"), subject.fieldDescription("Type", "field1", ORIGINAL_DOC));
+ assertEquals(
+ Optional.of("Deprecated f2"),
+ subject.fieldDeprecatedReason("Type", "field2", ORIGINAL_DOC)
+ );
+ assertEquals(
+ Optional.of("Original\n\nDoc f3"),
+ subject.fieldDescription("Type", "field3", ORIGINAL_DOC)
+ );
+ assertEquals(
+ Optional.of("Original\n\nDeprecated f4"),
+ subject.fieldDeprecatedReason("Type", "field4", ORIGINAL_DOC)
+ );
+ assertEquals(Optional.empty(), subject.fieldDeprecatedReason("Type", "field5", ORIGINAL_DOC));
+ }
+}
diff --git a/application/src/test/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.java b/application/src/test/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.java
new file mode 100644
index 00000000000..44318d613a4
--- /dev/null
+++ b/application/src/test/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.java
@@ -0,0 +1,139 @@
+package org.opentripplanner.apis.support.graphql.injectdoc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import graphql.schema.Coercing;
+import graphql.schema.GraphQLScalarType;
+import graphql.schema.GraphQLSchema;
+import graphql.schema.SchemaTransformer;
+import graphql.schema.idl.RuntimeWiring;
+import graphql.schema.idl.SchemaGenerator;
+import graphql.schema.idl.SchemaParser;
+import graphql.schema.idl.SchemaPrinter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner._support.text.TextAssertions;
+
+/**
+ * This test reads in a schema file, injects documentation and convert the
+ * new schema to an SDL text string. The result is then compared to the
+ * "expected" SDL file. The input and expected files are found in the
+ * resources - with the same name as this test.
+ *
+ * Note! There is a bug in the Java GraphQL library. Existing deprecated reasons
+ * cannot be changed or replaced. This test adds test-cases for this, but excludes
+ * them from the expected result. If this is fixed in the GraphQL library, this
+ * test will fail, and should be updated by updating the expected result.
+ */
+class InjectCustomDocumentationTest {
+
+ private GraphQLSchema schema;
+ private String sdlExpected;
+
+ @BeforeEach
+ void setUp() throws IOException {
+ var sdl = loadSchemaResource(".graphql");
+ sdlExpected = loadSchemaResource(".graphql.expected");
+
+ var parser = new SchemaParser();
+ var generator = new SchemaGenerator();
+ var typeRegistry = parser.parse(sdl);
+ schema = generator.makeExecutableSchema(typeRegistry, buildRuntimeWiring());
+ }
+
+ private static RuntimeWiring buildRuntimeWiring() {
+ return RuntimeWiring
+ .newRuntimeWiring()
+ .type("QueryType", b -> b.dataFetcher("listE", e -> List.of()))
+ .type("En", b -> b.enumValues(n -> n))
+ .type("AB", b -> b.typeResolver(it -> null))
+ .type("AC", b -> b.typeResolver(it -> null))
+ .scalar(
+ GraphQLScalarType
+ .newScalar()
+ .name("Duration")
+ .coercing(new Coercing() {})
+ .build()
+ )
+ .build();
+ }
+
+ /**
+ * Return a map of documentation key/values. The
+ * value is the same as the key for easy recognition.
+ */
+ static Map text() {
+ return Stream
+ .of(
+ "AB.description",
+ "AC.description.append",
+ "AType.description",
+ "AType.a.description",
+ "AType.b.deprecated",
+ "BType.description",
+ "BType.a.description",
+ "BType.a.deprecated",
+ "CType.description.append",
+ "CType.a.description.append",
+ "CType.b.deprecated.append",
+ "QueryType.findAB.description",
+ "QueryType.getAC.deprecated",
+ "AEnum.description",
+ "AEnum.E1.description",
+ "AEnum.E2.deprecated",
+ "AEnum.E3.deprecated",
+ "Duration.description",
+ "InputType.description",
+ "InputType.a.description",
+ "InputType.b.deprecated",
+ "InputType.c.deprecated"
+ )
+ .collect(Collectors.toMap(e -> e, e -> e));
+ }
+
+ @Test
+ void test() {
+ Map texts = text();
+ var customDocumentation = new CustomDocumentation(texts);
+ var visitor = new InjectCustomDocumentation(customDocumentation);
+ var newSchema = SchemaTransformer.transformSchema(schema, visitor);
+ var p = new SchemaPrinter();
+ var result = p.print(newSchema);
+
+ var missingValues = texts
+ .values()
+ .stream()
+ .sorted()
+ .filter(it -> !result.contains(it))
+ .toList();
+
+ // There is a bug in the Java GraphQL API, existing deprecated
+ // doc is not updated or replaced.
+ var expected = List.of(
+ "AEnum.E3.deprecated",
+ "BType.a.deprecated",
+ "CType.b.deprecated.append",
+ "InputType.c.deprecated"
+ );
+
+ assertEquals(expected, missingValues);
+
+ TextAssertions.assertLinesEquals(sdlExpected, result);
+ }
+
+ @SuppressWarnings("DataFlowIssue")
+ String loadSchemaResource(String suffix) throws IOException {
+ var cl = getClass();
+ var name = cl.getName().replace('.', '/') + suffix;
+ return new String(
+ ClassLoader.getSystemResourceAsStream(name).readAllBytes(),
+ StandardCharsets.UTF_8
+ );
+ }
+}
diff --git a/application/src/test/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaTest.java b/application/src/test/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaTest.java
index 4cdb0586aa7..3fc33081cda 100644
--- a/application/src/test/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaTest.java
+++ b/application/src/test/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchemaTest.java
@@ -9,6 +9,7 @@
import java.io.File;
import org.junit.jupiter.api.Test;
import org.opentripplanner._support.time.ZoneIds;
+import org.opentripplanner.apis.support.graphql.injectdoc.ApiDocumentationProfile;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitTuningParameters;
import org.opentripplanner.routing.api.request.RouteRequest;
@@ -23,6 +24,7 @@ void testSchemaBuild() {
var schema = TransmodelGraphQLSchema.create(
new RouteRequest(),
ZoneIds.OSLO,
+ ApiDocumentationProfile.DEFAULT,
TransitTuningParameters.FOR_TEST
);
assertNotNull(schema);
diff --git a/application/src/test/java/org/opentripplanner/apis/transmodel/model/EnumTypesTest.java b/application/src/test/java/org/opentripplanner/apis/transmodel/model/EnumTypesTest.java
index 3d834fced58..9090cd1bdc5 100644
--- a/application/src/test/java/org/opentripplanner/apis/transmodel/model/EnumTypesTest.java
+++ b/application/src/test/java/org/opentripplanner/apis/transmodel/model/EnumTypesTest.java
@@ -1,14 +1,23 @@
package org.opentripplanner.apis.transmodel.model;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.opentripplanner.apis.transmodel.model.EnumTypes.RELATIVE_DIRECTION;
import static org.opentripplanner.apis.transmodel.model.EnumTypes.ROUTING_ERROR_CODE;
import static org.opentripplanner.apis.transmodel.model.EnumTypes.map;
+import graphql.GraphQLContext;
import java.util.EnumSet;
import java.util.List;
+import java.util.Locale;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.opentripplanner.apis.transmodel.mapping.RelativeDirectionMapper;
import org.opentripplanner.framework.doc.DocumentedEnum;
+import org.opentripplanner.model.plan.RelativeDirection;
import org.opentripplanner.routing.api.response.RoutingErrorCode;
class EnumTypesTest {
@@ -75,6 +84,18 @@ void testMap() {
assertEquals("DocumentedEnumMapping[apiName=iH, internal=Hi]", mapping.toString());
}
+ @ParameterizedTest
+ @EnumSource(RelativeDirection.class)
+ void serializeRelativeDirection(RelativeDirection direction) {
+ var value = RELATIVE_DIRECTION.serialize(
+ RelativeDirectionMapper.map(direction),
+ GraphQLContext.getDefault(),
+ Locale.ENGLISH
+ );
+ assertInstanceOf(String.class, value);
+ assertNotNull(value);
+ }
+
@Test
void assertAllRoutingErrorCodesAreMapped() {
var expected = EnumSet.allOf(RoutingErrorCode.class);
diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java
index c55e482e533..a4d5e86ced8 100644
--- a/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java
+++ b/application/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java
@@ -2,18 +2,27 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.opentripplanner.framework.geometry.SphericalDistanceLibrary;
+import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.i18n.NonLocalizedString;
import org.opentripplanner.graph_builder.module.osm.OsmModule;
import org.opentripplanner.osm.OsmProvider;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildRepository;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildService;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository;
import org.opentripplanner.street.model.edge.AreaEdge;
import org.opentripplanner.street.model.edge.BoardingLocationToStopLink;
@@ -32,25 +41,11 @@
import org.opentripplanner.transit.service.SiteRepository;
import org.opentripplanner.transit.service.TimetableRepository;
-/**
- * We test that the platform area at Herrenberg station (https://www.openstreetmap.org/way/27558650)
- * is correctly linked to the stop even though it is not the closest edge to the stop.
- */
class OsmBoardingLocationsModuleTest {
private final TimetableRepositoryForTest testModel = TimetableRepositoryForTest.of();
- File file = ResourceLoader
- .of(OsmBoardingLocationsModuleTest.class)
- .file("herrenberg-minimal.osm.pbf");
- RegularStop platform = testModel
- .stop("de:08115:4512:4:101")
- .withCoordinate(48.59328, 8.86128)
- .build();
- RegularStop busStop = testModel.stop("de:08115:4512:5:C", 48.59434, 8.86452).build();
- RegularStop floatingBusStop = testModel.stop("floating-bus-stop", 48.59417, 8.86464).build();
-
- static Stream testCases() {
+ static Stream herrenbergTestCases() {
return Stream.of(
Arguments.of(
false,
@@ -63,11 +58,25 @@ static Stream testCases() {
);
}
+ /**
+ * We test that the platform area at Herrenberg station (https://www.openstreetmap.org/way/27558650)
+ * is correctly linked to the stop even though it is not the closest edge to the stop.
+ */
@ParameterizedTest(
name = "add boarding locations and link them to platform edges when skipVisibility={0}"
)
- @MethodSource("testCases")
+ @MethodSource("herrenbergTestCases")
void addAndLinkBoardingLocations(boolean areaVisibility, Set linkedVertices) {
+ File file = ResourceLoader
+ .of(OsmBoardingLocationsModuleTest.class)
+ .file("herrenberg-minimal.osm.pbf");
+ RegularStop platform = testModel
+ .stop("de:08115:4512:4:101")
+ .withCoordinate(48.59328, 8.86128)
+ .build();
+ RegularStop busStop = testModel.stop("de:08115:4512:5:C", 48.59434, 8.86452).build();
+ RegularStop floatingBusStop = testModel.stop("floating-bus-stop", 48.59417, 8.86464).build();
+
var deduplicator = new Deduplicator();
var graph = new Graph(deduplicator);
var timetableRepository = new TimetableRepository(new SiteRepository(), deduplicator);
@@ -83,8 +92,10 @@ void addAndLinkBoardingLocations(boolean areaVisibility, Set linkedVerti
Set.of(floatingBusVertex.getStop().getId().getId()),
new NonLocalizedString("bus stop not connected to street network")
);
+ var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository();
+ var vehicleParkingRepository = new DefaultVehicleParkingRepository();
var osmModule = OsmModule
- .of(provider, graph, new DefaultVehicleParkingRepository())
+ .of(provider, graph, osmInfoRepository, vehicleParkingRepository)
.withBoardingAreaRefTags(Set.of("ref", "ref:IFOPT"))
.withAreaVisibility(areaVisibility)
.build();
@@ -107,7 +118,8 @@ void addAndLinkBoardingLocations(boolean areaVisibility, Set linkedVerti
assertEquals(0, platformVertex.getIncoming().size());
assertEquals(0, platformVertex.getOutgoing().size());
- new OsmBoardingLocationsModule(graph, timetableRepository).buildGraph();
+ var osmService = new DefaultOsmInfoGraphBuildService(osmInfoRepository);
+ new OsmBoardingLocationsModule(graph, osmService, timetableRepository).buildGraph();
var boardingLocations = graph.getVerticesOfType(OsmBoardingLocationVertex.class);
assertEquals(5, boardingLocations.size()); // 3 nodes connected to the street network, plus one "floating" and one area centroid created by the module
@@ -141,13 +153,13 @@ void addAndLinkBoardingLocations(boolean areaVisibility, Set linkedVerti
assertEquals(1, platformCentroids.size());
- var platform = platformCentroids.get(0);
+ var platformCentroid = platformCentroids.get(0);
- assertConnections(platform, Set.of(BoardingLocationToStopLink.class, AreaEdge.class));
+ assertConnections(platformCentroid, Set.of(BoardingLocationToStopLink.class, AreaEdge.class));
assertEquals(
linkedVertices,
- platform
+ platformCentroid
.getOutgoingStreetEdges()
.stream()
.map(Edge::getToVertex)
@@ -157,7 +169,7 @@ void addAndLinkBoardingLocations(boolean areaVisibility, Set linkedVerti
assertEquals(
linkedVertices,
- platform
+ platformCentroid
.getIncomingStreetEdges()
.stream()
.map(Edge::getFromVertex)
@@ -177,6 +189,201 @@ void addAndLinkBoardingLocations(boolean areaVisibility, Set linkedVerti
.forEach(e -> assertEquals("Platform 101;102", e.getName().toString()));
}
+ /**
+ * We test that the underground platforms at Moorgate station (https://www.openstreetmap.org/way/1328222021)
+ * is correctly linked to the stop even though it is not the closest edge to the stop.
+ */
+ @Test
+ void testLinearPlatforms() {
+ var deduplicator = new Deduplicator();
+ var graph = new Graph(deduplicator);
+ var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository();
+ var osmModule = OsmModule
+ .of(
+ new OsmProvider(
+ ResourceLoader.of(OsmBoardingLocationsModuleTest.class).file("moorgate.osm.pbf"),
+ false
+ ),
+ graph,
+ osmInfoRepository,
+ new DefaultVehicleParkingRepository()
+ )
+ .withBoardingAreaRefTags(Set.of("naptan:AtcoCode"))
+ .build();
+ osmModule.buildGraph();
+
+ var factory = new VertexFactory(graph);
+
+ class TestCase {
+
+ /**
+ * The linear platform to be tested
+ */
+ public final RegularStop platform;
+
+ /**
+ * The label of a vertex where the centroid should be connected to
+ */
+ public final VertexLabel beginLabel;
+
+ /**
+ * The label of the other vertex where the centroid should be connected to
+ */
+ public final VertexLabel endLabel;
+
+ private TransitStopVertex platformVertex = null;
+
+ public TestCase(RegularStop platform, VertexLabel beginLabel, VertexLabel endLabel) {
+ this.platform = platform;
+ this.beginLabel = beginLabel;
+ this.endLabel = endLabel;
+ }
+
+ /**
+ * Get a TransitStopVertex for the platform in the graph. It is made and added to the graph
+ * on the first call.
+ */
+ TransitStopVertex getPlatformVertex() {
+ if (platformVertex == null) {
+ platformVertex = factory.transitStop(TransitStopVertex.of().withStop(platform));
+ }
+ return platformVertex;
+ }
+ }
+
+ var testCases = List.of(
+ new TestCase(
+ testModel
+ .stop("9100MRGT9")
+ .withName(I18NString.of("Moorgate (Platform 9)"))
+ .withCoordinate(51.51922107872304, -0.08767468698832413)
+ .withPlatformCode("9")
+ .build(),
+ VertexLabel.osm(12288669589L),
+ VertexLabel.osm(12288675219L)
+ ),
+ new TestCase(
+ testModel
+ .stop("9400ZZLUMGT3")
+ .withName(I18NString.of("Moorgate (Platform 7)"))
+ .withCoordinate(51.51919235051611, -0.08769925990953176)
+ .withPlatformCode("7")
+ .build(),
+ VertexLabel.osm(12288669575L),
+ VertexLabel.osm(12288675230L)
+ )
+ );
+
+ for (var testCase : testCases) {
+ // test that the platforms are not connected
+ var platformVertex = testCase.getPlatformVertex();
+ assertEquals(0, platformVertex.getIncoming().size());
+ assertEquals(0, platformVertex.getOutgoing().size());
+
+ // test that the vertices to be connected by the centroid are currently connected
+ var fromVertex = Objects.requireNonNull(graph.getVertex(testCase.beginLabel));
+ var toVertex = Objects.requireNonNull(graph.getVertex(testCase.endLabel));
+ assertTrue(
+ getEdge(fromVertex, toVertex).isPresent(),
+ "malformed test: the vertices where the centroid is supposed to be located between aren't connected"
+ );
+ assertTrue(
+ getEdge(toVertex, fromVertex).isPresent(),
+ "malformed test: the vertices where the centroid is supposed to be located between aren't connected"
+ );
+ }
+
+ var timetableRepository = new TimetableRepository(new SiteRepository(), deduplicator);
+ new OsmBoardingLocationsModule(
+ graph,
+ new DefaultOsmInfoGraphBuildService(osmInfoRepository),
+ timetableRepository
+ )
+ .buildGraph();
+
+ var boardingLocations = graph.getVerticesOfType(OsmBoardingLocationVertex.class);
+
+ for (var testCase : testCases) {
+ var platformVertex = testCase.getPlatformVertex();
+ var fromVertex = Objects.requireNonNull(graph.getVertex(testCase.beginLabel));
+ var toVertex = Objects.requireNonNull(graph.getVertex(testCase.endLabel));
+
+ var centroid = boardingLocations
+ .stream()
+ .filter(b -> b.references.contains(testCase.platform.getId().getId()))
+ .findFirst()
+ .orElseThrow();
+
+ // TODO: we should ideally place the centroid vertex directly on the platform by splitting
+ // the platform edge, but it is too difficult to touch the splitter code to use a given
+ // centroid vertex instead of a generated split vertex, so what we actually do is to directly
+ // connect the platform vertex to the split vertex
+
+ // the actual centroid isn't used
+ assertEquals(0, centroid.getDegreeIn());
+ assertEquals(0, centroid.getDegreeOut());
+
+ for (var vertex : platformVertex.getIncoming()) {
+ assertSplitVertex(vertex.getFromVertex(), centroid, fromVertex, toVertex);
+ }
+
+ for (var vertex : platformVertex.getOutgoing()) {
+ assertSplitVertex(vertex.getToVertex(), centroid, fromVertex, toVertex);
+ }
+ }
+ }
+
+ /**
+ * Assert that a split vertex is near to the given centroid, and it is possible to travel between
+ * the original vertices through the split vertex in a straight line
+ */
+ private static void assertSplitVertex(
+ Vertex splitVertex,
+ OsmBoardingLocationVertex centroid,
+ Vertex begin,
+ Vertex end
+ ) {
+ var distance = SphericalDistanceLibrary.distance(
+ splitVertex.getCoordinate(),
+ centroid.getCoordinate()
+ );
+ // FIXME: I am not sure why the calculated centroid from the original OSM geometry is about 2 m
+ // from the platform
+ assertTrue(distance < 4, "The split vertex is more than 4 m apart from the centroid");
+ assertConnections(splitVertex, begin, end);
+
+ if (splitVertex != begin && splitVertex != end) {
+ var forwardEdges = getEdge(begin, splitVertex)
+ .flatMap(first -> getEdge(splitVertex, end).map(second -> List.of(first, second)));
+ var backwardEdges = getEdge(end, splitVertex)
+ .flatMap(first -> getEdge(splitVertex, begin).map(second -> List.of(first, second)));
+ for (var edgeList : List.of(forwardEdges, backwardEdges)) {
+ edgeList.ifPresent(edges ->
+ assertEquals(
+ edges.getFirst().getOutAngle(),
+ edges.getLast().getInAngle(),
+ "The split vertex is not on a straight line between the connected vertices"
+ )
+ );
+ }
+ }
+ }
+
+ /**
+ * Assert that there is a one-way path from the beginning through the given vertex to the end
+ * or vice versa.
+ */
+ private static void assertConnections(Vertex vertex, Vertex beginning, Vertex end) {
+ if (vertex == beginning || vertex == end) {
+ assertTrue(beginning.isConnected(end));
+ }
+
+ assertTrue(
+ (getEdge(beginning, vertex).isPresent() && getEdge(vertex, end).isPresent()) ||
+ (getEdge(end, vertex).isPresent() && getEdge(vertex, beginning).isPresent())
+ );
+ }
+
private void assertConnections(
OsmBoardingLocationVertex busBoardingLocation,
Set> expected
@@ -187,4 +394,12 @@ private void assertConnections(
assertEquals(expected, edges.stream().map(Edge::getClass).collect(Collectors.toSet()))
);
}
+
+ private static Optional getEdge(Vertex from, Vertex to) {
+ return from
+ .getOutgoingStreetEdges()
+ .stream()
+ .filter(edge -> edge.getToVertex() == to)
+ .findFirst();
+ }
}
diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/IslandPruningUtils.java b/application/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/IslandPruningUtils.java
index d71a60a972e..8e5a455095b 100644
--- a/application/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/IslandPruningUtils.java
+++ b/application/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/IslandPruningUtils.java
@@ -5,6 +5,7 @@
import org.opentripplanner.graph_builder.module.osm.OsmModule;
import org.opentripplanner.osm.OsmProvider;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository;
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.service.SiteRepository;
@@ -24,9 +25,11 @@ static Graph buildOsmGraph(
var graph = new Graph(deduplicator);
var timetableRepository = new TimetableRepository(new SiteRepository(), deduplicator);
// Add street data from OSM
- OsmProvider osmProvider = new OsmProvider(osmFile, true);
- OsmModule osmModule = OsmModule
- .of(osmProvider, graph, new DefaultVehicleParkingRepository())
+ var osmProvider = new OsmProvider(osmFile, true);
+ var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository();
+ var vehicleParkingRepository = new DefaultVehicleParkingRepository();
+ var osmModule = OsmModule
+ .of(osmProvider, graph, osmInfoRepository, vehicleParkingRepository)
.withEdgeNamer(new TestNamer())
.build();
diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java
index a6afa89707f..6c51235e703 100644
--- a/application/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java
+++ b/application/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java
@@ -21,6 +21,7 @@
import org.opentripplanner.graph_builder.module.osm.OsmModule;
import org.opentripplanner.osm.OsmProvider;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository;
import org.opentripplanner.street.model.StreetTraversalPermission;
import org.opentripplanner.street.model._data.StreetModelForTest;
@@ -152,16 +153,20 @@ public void testStopsLinkedIdentically() {
public static TestOtpModel buildGraphNoTransit() {
var deduplicator = new Deduplicator();
var siteRepository = new SiteRepository();
- var gg = new Graph(deduplicator);
+ var graph = new Graph(deduplicator);
var timetableRepository = new TimetableRepository(siteRepository, deduplicator);
File file = ResourceLoader.of(LinkingTest.class).file("columbus.osm.pbf");
- OsmProvider provider = new OsmProvider(file, false);
+ var provider = new OsmProvider(file, false);
+ var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository();
+ var vehicleParkingRepository = new DefaultVehicleParkingRepository();
- OsmModule osmModule = OsmModule.of(provider, gg, new DefaultVehicleParkingRepository()).build();
+ var osmModule = OsmModule
+ .of(provider, graph, osmInfoRepository, vehicleParkingRepository)
+ .build();
osmModule.buildGraph();
- return new TestOtpModel(gg, timetableRepository);
+ return new TestOtpModel(graph, timetableRepository);
}
private static List outgoingStls(final TransitStopVertex tsv) {
diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java
index 833b14ade9d..6de345ddd9c 100644
--- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java
+++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/OsmModuleTest.java
@@ -32,6 +32,7 @@
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.impl.GraphPathFinder;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.VehicleParkingRepository;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingService;
@@ -53,30 +54,35 @@ public class OsmModuleTest {
@Test
public void testGraphBuilder() {
var deduplicator = new Deduplicator();
- var gg = new Graph(deduplicator);
+ var graph = new Graph(deduplicator);
File file = RESOURCE_LOADER.file("map.osm.pbf");
OsmProvider provider = new OsmProvider(file, true);
OsmModule osmModule = OsmModule
- .of(provider, gg, new DefaultVehicleParkingRepository())
+ .of(
+ provider,
+ graph,
+ new DefaultOsmInfoGraphBuildRepository(),
+ new DefaultVehicleParkingRepository()
+ )
.withAreaVisibility(true)
.build();
osmModule.buildGraph();
// Kamiennogorska at south end of segment
- Vertex v1 = gg.getVertex(VertexLabel.osm(280592578));
+ Vertex v1 = graph.getVertex(VertexLabel.osm(280592578));
// Kamiennogorska at Mariana Smoluchowskiego
- Vertex v2 = gg.getVertex(VertexLabel.osm(288969929));
+ Vertex v2 = graph.getVertex(VertexLabel.osm(288969929));
// Mariana Smoluchowskiego, north end
- Vertex v3 = gg.getVertex(VertexLabel.osm(280107802));
+ Vertex v3 = graph.getVertex(VertexLabel.osm(280107802));
// Mariana Smoluchowskiego, south end (of segment connected to v2)
- Vertex v4 = gg.getVertex(VertexLabel.osm(288970952));
+ Vertex v4 = graph.getVertex(VertexLabel.osm(288970952));
assertNotNull(v1);
assertNotNull(v2);
@@ -117,9 +123,11 @@ public void testBuildGraphDetailed() {
var gg = new Graph(deduplicator);
File file = RESOURCE_LOADER.file("NYC_small.osm.pbf");
- OsmProvider provider = new OsmProvider(file, true);
- OsmModule osmModule = OsmModule
- .of(provider, gg, new DefaultVehicleParkingRepository())
+ var provider = new OsmProvider(file, true);
+ var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository();
+ var vehicleParkingRepository = new DefaultVehicleParkingRepository();
+ var osmModule = OsmModule
+ .of(provider, gg, osmInfoRepository, vehicleParkingRepository)
.withAreaVisibility(true)
.build();
@@ -315,7 +323,14 @@ void testBarrierAtEnd() {
File file = RESOURCE_LOADER.file("accessno-at-end.pbf");
OsmProvider provider = new OsmProvider(file, false);
- OsmModule loader = OsmModule.of(provider, graph, new DefaultVehicleParkingRepository()).build();
+ OsmModule loader = OsmModule
+ .of(
+ provider,
+ graph,
+ new DefaultOsmInfoGraphBuildRepository(),
+ new DefaultVehicleParkingRepository()
+ )
+ .build();
loader.buildGraph();
Vertex start = graph.getVertex(VertexLabel.osm(1));
@@ -339,7 +354,7 @@ private BuildResult buildParkingLots() {
.map(f -> new OsmProvider(f, false))
.toList();
var module = OsmModule
- .of(providers, graph, service)
+ .of(providers, graph, new DefaultOsmInfoGraphBuildRepository(), service)
.withStaticParkAndRide(true)
.withStaticBikeParkAndRide(true)
.build();
@@ -363,10 +378,12 @@ private void testBuildingAreas(boolean skipVisibility) {
var graph = new Graph(deduplicator);
File file = RESOURCE_LOADER.file("usf_area.osm.pbf");
- OsmProvider provider = new OsmProvider(file, false);
+ var provider = new OsmProvider(file, false);
+ var osmInfoRepository = new DefaultOsmInfoGraphBuildRepository();
+ var vehicleParkingRepository = new DefaultVehicleParkingRepository();
- OsmModule loader = OsmModule
- .of(provider, graph, new DefaultVehicleParkingRepository())
+ var loader = OsmModule
+ .of(provider, graph, osmInfoRepository, vehicleParkingRepository)
.withAreaVisibility(!skipVisibility)
.build();
diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/PlatformLinkerTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/PlatformLinkerTest.java
index f952bf90710..97ccbdd7719 100644
--- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/PlatformLinkerTest.java
+++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/PlatformLinkerTest.java
@@ -6,6 +6,7 @@
import org.junit.jupiter.api.Test;
import org.opentripplanner.osm.OsmProvider;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository;
import org.opentripplanner.street.model.edge.AreaEdge;
import org.opentripplanner.street.model.vertex.Vertex;
@@ -24,20 +25,25 @@ public void testLinkEntriesToPlatforms() {
var stairsEndpointLabel = VertexLabel.osm(1028861028);
var deduplicator = new Deduplicator();
- var gg = new Graph(deduplicator);
+ var graph = new Graph(deduplicator);
File file = ResourceLoader.of(this).file("skoyen.osm.pbf");
OsmProvider provider = new OsmProvider(file, false);
OsmModule osmModule = OsmModule
- .of(provider, gg, new DefaultVehicleParkingRepository())
+ .of(
+ provider,
+ graph,
+ new DefaultOsmInfoGraphBuildRepository(),
+ new DefaultVehicleParkingRepository()
+ )
.withPlatformEntriesLinking(true)
.build();
osmModule.buildGraph();
- Vertex stairsEndpoint = gg.getVertex(stairsEndpointLabel);
+ Vertex stairsEndpoint = graph.getVertex(stairsEndpointLabel);
// verify outgoing links
assertTrue(stairsEndpoint.getOutgoing().stream().anyMatch(AreaEdge.class::isInstance));
diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java
index ffc1f661dcc..1b8f7c9c58a 100644
--- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java
+++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java
@@ -21,6 +21,7 @@
import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter;
import org.opentripplanner.routing.api.request.request.filter.TransitFilter;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.vertex.Vertex;
@@ -52,7 +53,12 @@ public static void onlyOnce() {
File file = ResourceLoader.of(TriangleInequalityTest.class).file("NYC_small.osm.pbf");
OsmProvider provider = new OsmProvider(file, true);
OsmModule osmModule = OsmModule
- .of(provider, graph, new DefaultVehicleParkingRepository())
+ .of(
+ provider,
+ graph,
+ new DefaultOsmInfoGraphBuildRepository(),
+ new DefaultVehicleParkingRepository()
+ )
.withAreaVisibility(true)
.build();
osmModule.buildGraph();
diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/UnconnectedAreasTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/UnconnectedAreasTest.java
index 103dafa61b9..22b486d7cd6 100644
--- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/UnconnectedAreasTest.java
+++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/UnconnectedAreasTest.java
@@ -13,6 +13,7 @@
import org.opentripplanner.graph_builder.module.TestStreetLinkerModule;
import org.opentripplanner.osm.OsmProvider;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository;
import org.opentripplanner.street.model.edge.StreetVehicleParkingLink;
import org.opentripplanner.street.model.edge.VehicleParkingEdge;
@@ -163,7 +164,12 @@ private Graph buildOsmGraph(String osmFileName, DataImportIssueStore issueStore)
var timetableRepository = new TimetableRepository(siteRepository, deduplicator);
OsmProvider provider = new OsmProvider(RESOURCE_LOADER.file(osmFileName), false);
OsmModule loader = OsmModule
- .of(provider, graph, new DefaultVehicleParkingRepository())
+ .of(
+ provider,
+ graph,
+ new DefaultOsmInfoGraphBuildRepository(),
+ new DefaultVehicleParkingRepository()
+ )
.withIssueStore(issueStore)
.withAreaVisibility(true)
.withStaticParkAndRide(true)
diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/UnroutableTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/UnroutableTest.java
index 138c3e67181..835c5a7fbcb 100644
--- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/UnroutableTest.java
+++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/UnroutableTest.java
@@ -10,6 +10,7 @@
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.vertex.Vertex;
@@ -39,7 +40,12 @@ public void setUp() throws Exception {
var osmDataFile = ResourceLoader.of(UnroutableTest.class).file("bridge_construction.osm.pbf");
OsmProvider provider = new OsmProvider(osmDataFile, true);
OsmModule osmBuilder = OsmModule
- .of(provider, graph, new DefaultVehicleParkingRepository())
+ .of(
+ provider,
+ graph,
+ new DefaultOsmInfoGraphBuildRepository(),
+ new DefaultVehicleParkingRepository()
+ )
.withAreaVisibility(true)
.build();
osmBuilder.buildGraph();
diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java
index b712ee48c5a..1516c9df91a 100644
--- a/application/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java
+++ b/application/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java
@@ -48,7 +48,7 @@ public Graph buildGraph(final TestInfo testInfo) {
final WalkableAreaBuilder walkableAreaBuilder = new WalkableAreaBuilder(
graph,
osmdb,
- new VertexGenerator(osmdb, graph, Set.of()),
+ new VertexGenerator(osmdb, graph, Set.of(), false),
new DefaultNamer(),
new SafetyValueNormalizer(graph, DataImportIssueStore.NOOP),
DataImportIssueStore.NOOP,
diff --git a/application/src/test/java/org/opentripplanner/osm/model/OsmWithTagsTest.java b/application/src/test/java/org/opentripplanner/osm/model/OsmWithTagsTest.java
index 84b74b8f655..597593f7333 100644
--- a/application/src/test/java/org/opentripplanner/osm/model/OsmWithTagsTest.java
+++ b/application/src/test/java/org/opentripplanner/osm/model/OsmWithTagsTest.java
@@ -17,6 +17,7 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.osm.wayproperty.specifier.WayTestData;
+import org.opentripplanner.transit.model.basic.Accessibility;
public class OsmWithTagsTest {
@@ -215,6 +216,20 @@ void isWheelchairAccessible() {
assertTrue(osm3.isWheelchairAccessible());
}
+ @Test
+ void wheelchairAccessibility() {
+ var osm1 = new OsmWithTags();
+ assertEquals(Accessibility.NO_INFORMATION, osm1.wheelchairAccessibility());
+
+ var osm2 = new OsmWithTags();
+ osm2.addTag("wheelchair", "no");
+ assertEquals(Accessibility.NOT_POSSIBLE, osm2.wheelchairAccessibility());
+
+ var osm3 = new OsmWithTags();
+ osm3.addTag("wheelchair", "yes");
+ assertEquals(Accessibility.POSSIBLE, osm3.wheelchairAccessibility());
+ }
+
@Test
void isRoutable() {
assertFalse(WayTestData.zooPlatform().isRoutable());
diff --git a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapperTest.java b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapperTest.java
index de9fe21718a..a2bb428a78c 100644
--- a/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapperTest.java
+++ b/application/src/test/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapperTest.java
@@ -13,6 +13,7 @@
import org.opentripplanner.model.plan.WalkStep;
import org.opentripplanner.routing.services.notes.StreetNotesService;
import org.opentripplanner.street.search.state.TestStateBuilder;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
class StatesToWalkStepsMapperTest {
@@ -42,6 +43,7 @@ void enterStation() {
var walkSteps = buildWalkSteps(builder);
assertEquals(2, walkSteps.size());
var enter = walkSteps.get(1);
+ assertEquals(new FeedScopedId("F", "Lichterfelde-Ost"), enter.entrance().get().getId());
assertEquals(ENTER_STATION, enter.getRelativeDirection());
}
@@ -53,8 +55,9 @@ void exitStation() {
.exitStation("Lichterfelde-Ost");
var walkSteps = buildWalkSteps(builder);
assertEquals(3, walkSteps.size());
- var enter = walkSteps.get(2);
- assertEquals(EXIT_STATION, enter.getRelativeDirection());
+ var exit = walkSteps.get(2);
+ assertEquals(new FeedScopedId("F", "Lichterfelde-Ost"), exit.entrance().get().getId());
+ assertEquals(EXIT_STATION, exit.getRelativeDirection());
}
@Test
diff --git a/application/src/test/java/org/opentripplanner/routing/core/MoneyTest.java b/application/src/test/java/org/opentripplanner/routing/core/MoneyTest.java
index 4fdb74d5340..fdfa71c222b 100644
--- a/application/src/test/java/org/opentripplanner/routing/core/MoneyTest.java
+++ b/application/src/test/java/org/opentripplanner/routing/core/MoneyTest.java
@@ -11,6 +11,7 @@
import java.util.Currency;
import java.util.Locale;
import java.util.stream.Stream;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -39,6 +40,7 @@ static Stream testCases() {
);
}
+ @Disabled
@ParameterizedTest(name = "{0} with locale {1} should localise to \"{2}\"")
@MethodSource("testCases")
void localize(Money money, Locale locale, String expected) {
diff --git a/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java b/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java
index 400a9eba2ba..9ccd6177cfd 100644
--- a/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java
+++ b/application/src/test/java/org/opentripplanner/routing/graph/GraphSerializationTest.java
@@ -23,6 +23,8 @@
import org.opentripplanner.ext.emissions.EmissionsDataModel;
import org.opentripplanner.framework.geometry.HashGridSpatialIndex;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueSummary;
+import org.opentripplanner.service.osminfo.OsmInfoGraphBuildRepository;
+import org.opentripplanner.service.osminfo.internal.DefaultOsmInfoGraphBuildRepository;
import org.opentripplanner.service.vehicleparking.VehicleParkingRepository;
import org.opentripplanner.service.vehicleparking.internal.DefaultVehicleParkingRepository;
import org.opentripplanner.service.worldenvelope.WorldEnvelopeRepository;
@@ -67,11 +69,13 @@ public class GraphSerializationTest {
@Test
public void testRoundTripSerializationForGTFSGraph() throws Exception {
TestOtpModel model = ConstantsForTests.buildNewPortlandGraph(true);
+ var osmGraphBuildRepository = new DefaultOsmInfoGraphBuildRepository();
var weRepo = new DefaultWorldEnvelopeRepository();
var emissionsDataModel = new EmissionsDataModel();
var parkingRepository = new DefaultVehicleParkingRepository();
testRoundTrip(
model.graph(),
+ osmGraphBuildRepository,
model.timetableRepository(),
weRepo,
parkingRepository,
@@ -85,11 +89,13 @@ public void testRoundTripSerializationForGTFSGraph() throws Exception {
@Test
public void testRoundTripSerializationForNetexGraph() throws Exception {
TestOtpModel model = ConstantsForTests.buildNewMinimalNetexGraph();
+ var osmGraphBuildRepository = new DefaultOsmInfoGraphBuildRepository();
var worldEnvelopeRepository = new DefaultWorldEnvelopeRepository();
var emissionsDataModel = new EmissionsDataModel();
var parkingRepository = new DefaultVehicleParkingRepository();
testRoundTrip(
model.graph(),
+ osmGraphBuildRepository,
model.timetableRepository(),
worldEnvelopeRepository,
parkingRepository,
@@ -191,6 +197,7 @@ private static void assertNoDifferences(Graph g1, Graph g2) {
*/
private void testRoundTrip(
Graph originalGraph,
+ OsmInfoGraphBuildRepository osmInfoGraphBuildRepository,
TimetableRepository originalTimetableRepository,
WorldEnvelopeRepository worldEnvelopeRepository,
VehicleParkingRepository vehicleParkingRepository,
@@ -202,6 +209,7 @@ private void testRoundTrip(
streetLimitationParameters.initMaxCarSpeed(40);
SerializedGraphObject serializedObj = new SerializedGraphObject(
originalGraph,
+ osmInfoGraphBuildRepository,
originalTimetableRepository,
worldEnvelopeRepository,
vehicleParkingRepository,
diff --git a/application/src/test/java/org/opentripplanner/street/search/state/EdgeTraverserTest.java b/application/src/test/java/org/opentripplanner/street/search/state/EdgeTraverserTest.java
deleted file mode 100644
index a2cd7e61b62..00000000000
--- a/application/src/test/java/org/opentripplanner/street/search/state/EdgeTraverserTest.java
+++ /dev/null
@@ -1,191 +0,0 @@
-package org.opentripplanner.street.search.state;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.opentripplanner.street.model._data.StreetModelForTest.intersectionVertex;
-
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Function;
-import org.junit.jupiter.api.Test;
-import org.opentripplanner._support.geometry.Coordinates;
-import org.opentripplanner.routing.api.request.StreetMode;
-import org.opentripplanner.street.model.StreetTraversalPermission;
-import org.opentripplanner.street.model._data.StreetModelForTest;
-import org.opentripplanner.street.model.edge.Edge;
-import org.opentripplanner.street.model.vertex.IntersectionVertex;
-import org.opentripplanner.street.search.TraverseMode;
-import org.opentripplanner.street.search.request.StreetSearchRequest;
-
-class EdgeTraverserTest {
-
- private static final IntersectionVertex BERLIN_V = intersectionVertex(Coordinates.BERLIN);
- private static final IntersectionVertex BRANDENBURG_GATE_V = intersectionVertex(
- Coordinates.BERLIN_BRANDENBURG_GATE
- );
- private static final IntersectionVertex FERNSEHTURM_V = intersectionVertex(
- Coordinates.BERLIN_FERNSEHTURM
- );
- private static final IntersectionVertex ADMIRALBRUCKE_V = intersectionVertex(
- Coordinates.BERLIN_ADMIRALBRUCKE
- );
-
- @Test
- void emptyEdges() {
- var request = StreetSearchRequest
- .of()
- .withStartTime(Instant.EPOCH)
- .withMode(StreetMode.WALK)
- .build();
- var initialStates = State.getInitialStates(Set.of(BERLIN_V), request);
- var traversedState = EdgeTraverser.traverseEdges(initialStates, List.of());
-
- assertSame(initialStates.iterator().next(), traversedState.get());
- }
-
- @Test
- void failedTraversal() {
- var edge = StreetModelForTest
- .streetEdge(BERLIN_V, BRANDENBURG_GATE_V)
- .toBuilder()
- .withPermission(StreetTraversalPermission.NONE)
- .buildAndConnect();
-
- var edges = List.of(edge);
- var request = StreetSearchRequest
- .of()
- .withStartTime(Instant.EPOCH)
- .withMode(StreetMode.WALK)
- .build();
- var initialStates = State.getInitialStates(Set.of(edge.getFromVertex()), request);
- var traversedState = EdgeTraverser.traverseEdges(initialStates, edges);
-
- assertTrue(traversedState.isEmpty());
- }
-
- @Test
- void withSingleState() {
- var edge = StreetModelForTest
- .streetEdge(BERLIN_V, BRANDENBURG_GATE_V)
- .toBuilder()
- .withPermission(StreetTraversalPermission.ALL)
- .buildAndConnect();
-
- var edges = List.of(edge);
- var request = StreetSearchRequest
- .of()
- .withStartTime(Instant.EPOCH)
- .withMode(StreetMode.WALK)
- .build();
- var initialStates = State.getInitialStates(Set.of(edge.getFromVertex()), request);
- var traversedState = EdgeTraverser.traverseEdges(initialStates, edges).get();
-
- assertEquals(List.of(TraverseMode.WALK), stateValues(traversedState, State::getBackMode));
- assertEquals(1719, traversedState.getElapsedTimeSeconds());
- }
-
- @Test
- void withSingleArriveByState() {
- var edge = StreetModelForTest
- .streetEdge(BERLIN_V, BRANDENBURG_GATE_V)
- .toBuilder()
- .withPermission(StreetTraversalPermission.ALL)
- .buildAndConnect();
-
- var edges = List.of(edge);
- var request = StreetSearchRequest
- .of()
- .withStartTime(Instant.EPOCH)
- .withMode(StreetMode.WALK)
- .withArriveBy(true)
- .build();
- var initialStates = State.getInitialStates(Set.of(edge.getToVertex()), request);
- var traversedState = EdgeTraverser.traverseEdges(initialStates, edges).get();
-
- assertSame(BERLIN_V, traversedState.getVertex());
- assertEquals(List.of(TraverseMode.WALK), stateValues(traversedState, State::getBackMode));
- assertEquals(1719, traversedState.getElapsedTimeSeconds());
- }
-
- @Test
- void withMultipleStates() {
- // CAR_PICKUP creates parallel walking and driving states
- // This tests that of the two states (WALKING, CAR) the least weight (CAR) is selected
- var edge = StreetModelForTest
- .streetEdge(BERLIN_V, BRANDENBURG_GATE_V)
- .toBuilder()
- .withPermission(StreetTraversalPermission.ALL)
- .buildAndConnect();
-
- var edges = List.of(edge);
- var request = StreetSearchRequest
- .of()
- .withStartTime(Instant.EPOCH)
- .withMode(StreetMode.CAR_PICKUP)
- .build();
- var initialStates = State.getInitialStates(Set.of(edge.getFromVertex()), request);
- var traversedState = EdgeTraverser.traverseEdges(initialStates, edges).get();
-
- assertEquals(List.of(TraverseMode.CAR), stateValues(traversedState, State::getBackMode));
- assertEquals(205, traversedState.getElapsedTimeSeconds());
- }
-
- @Test
- void withDominatedStates() {
- // CAR_PICKUP creates parallel walking and driving states
- // This tests that the most optimal (walking and driving the last stretch) is found after
- // discarding the initial driving state for edge1
- var edge1 = StreetModelForTest
- .streetEdge(FERNSEHTURM_V, BERLIN_V)
- .toBuilder()
- .withPermission(StreetTraversalPermission.ALL)
- .buildAndConnect();
- var edge2 = StreetModelForTest
- .streetEdge(BERLIN_V, BRANDENBURG_GATE_V)
- .toBuilder()
- .withPermission(StreetTraversalPermission.PEDESTRIAN)
- .buildAndConnect();
- var edge3 = StreetModelForTest
- .streetEdge(BRANDENBURG_GATE_V, ADMIRALBRUCKE_V)
- .toBuilder()
- .withPermission(StreetTraversalPermission.ALL)
- .buildAndConnect();
-
- var edges = List.of(edge1, edge2, edge3);
- var request = StreetSearchRequest
- .of()
- .withStartTime(Instant.EPOCH)
- .withMode(StreetMode.CAR_PICKUP)
- .build();
- var initialStates = State.getInitialStates(Set.of(edge1.getFromVertex()), request);
- var traversedState = EdgeTraverser.traverseEdges(initialStates, edges).get();
-
- assertEquals(
- List.of(88.103, 2286.029, 3444.28),
- stateValues(
- traversedState,
- state -> state.getBackEdge() != null ? state.getBackEdge().getDistanceMeters() : null
- )
- );
- assertEquals(
- List.of(TraverseMode.WALK, TraverseMode.WALK, TraverseMode.CAR),
- stateValues(traversedState, State::getBackMode)
- );
- assertEquals(2169, traversedState.getElapsedTimeSeconds());
- }
-
- private List stateValues(State state, Function extractor) {
- var values = new ArrayList();
- while (state != null) {
- var value = extractor.apply(state);
- if (value != null) {
- values.add(value);
- }
- state = state.getBackState();
- }
- return values.reversed();
- }
-}
diff --git a/application/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java b/application/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java
index 7ea56f66145..9605d950ae0 100644
--- a/application/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java
+++ b/application/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java
@@ -202,7 +202,7 @@ public TestStateBuilder elevator() {
currentState =
EdgeTraverser
- .traverseEdges(new State[] { currentState }, List.of(link, boardEdge, hopEdge, alightEdge))
+ .traverseEdges(currentState, List.of(link, boardEdge, hopEdge, alightEdge))
.orElseThrow();
return this;
}
diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/LoadModel.java b/application/src/test/java/org/opentripplanner/transit/speed_test/LoadModel.java
new file mode 100644
index 00000000000..5e5823c842b
--- /dev/null
+++ b/application/src/test/java/org/opentripplanner/transit/speed_test/LoadModel.java
@@ -0,0 +1,7 @@
+package org.opentripplanner.transit.speed_test;
+
+import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.standalone.config.BuildConfig;
+import org.opentripplanner.transit.service.TimetableRepository;
+
+record LoadModel(Graph graph, TimetableRepository timetableRepository, BuildConfig buildConfig) {}
diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/SetupHelper.java b/application/src/test/java/org/opentripplanner/transit/speed_test/SetupHelper.java
new file mode 100644
index 00000000000..86383d6d94e
--- /dev/null
+++ b/application/src/test/java/org/opentripplanner/transit/speed_test/SetupHelper.java
@@ -0,0 +1,41 @@
+package org.opentripplanner.transit.speed_test;
+
+import java.io.File;
+import java.net.URI;
+import javax.annotation.Nullable;
+import org.opentripplanner.datastore.OtpDataStore;
+import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.routing.graph.SerializedGraphObject;
+import org.opentripplanner.standalone.config.ConfigModel;
+import org.opentripplanner.standalone.config.OtpConfigLoader;
+import org.opentripplanner.transit.service.TimetableRepository;
+import org.opentripplanner.transit.speed_test.options.SpeedTestCmdLineOpts;
+
+/**
+ * A package-private helper class for setting up speed tests.
+ */
+class SetupHelper {
+
+ static LoadModel loadGraph(File baseDir, @Nullable URI path) {
+ File file = path == null
+ ? OtpDataStore.graphFile(baseDir)
+ : path.isAbsolute() ? new File(path) : new File(baseDir, path.getPath());
+ SerializedGraphObject serializedGraphObject = SerializedGraphObject.load(file);
+ Graph graph = serializedGraphObject.graph;
+
+ if (graph == null) {
+ throw new IllegalStateException(
+ "Could not find graph at %s".formatted(file.getAbsolutePath())
+ );
+ }
+
+ TimetableRepository timetableRepository = serializedGraphObject.timetableRepository;
+ timetableRepository.index();
+ graph.index(timetableRepository.getSiteRepository());
+ return new LoadModel(graph, timetableRepository, serializedGraphObject.buildConfig);
+ }
+
+ static void loadOtpFeatures(SpeedTestCmdLineOpts opts) {
+ ConfigModel.initializeOtpFeatures(new OtpConfigLoader(opts.rootDir()).loadOtpConfig());
+ }
+}
diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java
index ca4e85eed84..2a3add223e8 100644
--- a/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java
+++ b/application/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java
@@ -27,7 +27,6 @@
import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService;
import org.opentripplanner.standalone.OtpStartupInfo;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
-import org.opentripplanner.standalone.config.BuildConfig;
import org.opentripplanner.standalone.config.ConfigModel;
import org.opentripplanner.standalone.config.DebugUiConfig;
import org.opentripplanner.standalone.config.OtpConfigLoader;
@@ -153,8 +152,8 @@ public static void main(String[] args) {
// Given the following setup
SpeedTestCmdLineOpts opts = new SpeedTestCmdLineOpts(args);
var config = SpeedTestConfig.config(opts.rootDir());
- loadOtpFeatures(opts);
- var model = loadGraph(opts.rootDir(), config.graph);
+ SetupHelper.loadOtpFeatures(opts);
+ var model = SetupHelper.loadGraph(opts.rootDir(), config.graph);
var timetableRepository = model.timetableRepository();
var buildConfig = model.buildConfig();
var graph = model.graph();
@@ -192,6 +191,9 @@ public void runTest() {
}
updateTimersWithGlobalCounters();
+
+ timer.finishUp();
+
printProfileStatistics();
saveTestCasesToResultFile();
System.err.println("\nSpeedTest done! " + projectInfo().getVersionString());
@@ -267,27 +269,6 @@ private RoutingResponse performRouting(TestCase testCase) {
/* setup helper methods */
- private static void loadOtpFeatures(SpeedTestCmdLineOpts opts) {
- ConfigModel.initializeOtpFeatures(new OtpConfigLoader(opts.rootDir()).loadOtpConfig());
- }
-
- private static LoadModel loadGraph(File baseDir, URI path) {
- File file = path == null
- ? OtpDataStore.graphFile(baseDir)
- : path.isAbsolute() ? new File(path) : new File(baseDir, path.getPath());
- SerializedGraphObject serializedGraphObject = SerializedGraphObject.load(file);
- Graph graph = serializedGraphObject.graph;
-
- if (graph == null) {
- throw new IllegalStateException();
- }
-
- TimetableRepository timetableRepository = serializedGraphObject.timetableRepository;
- timetableRepository.index();
- graph.index(timetableRepository.getSiteRepository());
- return new LoadModel(graph, timetableRepository, serializedGraphObject.buildConfig);
- }
-
private void initProfileStatistics() {
for (SpeedTestProfile key : opts.profiles()) {
workerResults.put(key, new ArrayList<>());
@@ -352,7 +333,6 @@ private void updateTimersWithGlobalCounters() {
timer.globalCount("jvm_max_memory", runtime.maxMemory());
timer.globalCount("jvm_total_memory", runtime.totalMemory());
timer.globalCount("jvm_used_memory", runtime.totalMemory() - runtime.freeMemory());
- timer.finishUp();
}
/**
@@ -368,8 +348,4 @@ private List trimItineraries(RoutingResponse routingResponse) {
}
return stream.limit(opts.numOfItineraries()).toList();
}
-
- /* inline classes */
-
- record LoadModel(Graph graph, TimetableRepository timetableRepository, BuildConfig buildConfig) {}
}
diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/TransferCacheTest.java b/application/src/test/java/org/opentripplanner/transit/speed_test/TransferCacheTest.java
new file mode 100644
index 00000000000..e6c8de67688
--- /dev/null
+++ b/application/src/test/java/org/opentripplanner/transit/speed_test/TransferCacheTest.java
@@ -0,0 +1,68 @@
+package org.opentripplanner.transit.speed_test;
+
+import static org.opentripplanner.standalone.configure.ConstructApplication.creatTransitLayerForRaptor;
+import static org.opentripplanner.transit.speed_test.support.AssertSpeedTestSetup.assertTestDateHasData;
+
+import java.util.stream.IntStream;
+import org.opentripplanner.framework.application.OtpAppException;
+import org.opentripplanner.routing.api.request.RouteRequest;
+import org.opentripplanner.standalone.OtpStartupInfo;
+import org.opentripplanner.transit.service.TimetableRepository;
+import org.opentripplanner.transit.speed_test.model.timer.SpeedTestTimer;
+import org.opentripplanner.transit.speed_test.options.SpeedTestCmdLineOpts;
+import org.opentripplanner.transit.speed_test.options.SpeedTestConfig;
+
+/**
+ * Test how long it takes to compute the transfer cache.
+ */
+public class TransferCacheTest {
+
+ public static void main(String[] args) {
+ try {
+ OtpStartupInfo.logInfo("Run transfer cache test");
+ // Given the following setup
+ SpeedTestCmdLineOpts opts = new SpeedTestCmdLineOpts(args);
+ var config = SpeedTestConfig.config(opts.rootDir());
+ SetupHelper.loadOtpFeatures(opts);
+ var model = SetupHelper.loadGraph(opts.rootDir(), config.graph);
+ var timetableRepository = model.timetableRepository();
+ var buildConfig = model.buildConfig();
+
+ var timer = new SpeedTestTimer();
+ timer.setUp(false);
+
+ // Creating transitLayerForRaptor should be integrated into the TimetableRepository, but for now
+ // we do it manually here
+ creatTransitLayerForRaptor(timetableRepository, config.transitRoutingParams);
+
+ assertTestDateHasData(timetableRepository, config, buildConfig);
+
+ measureTransferCacheComputation(timer, timetableRepository);
+
+ timer.finishUp();
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ e.printStackTrace(System.err);
+ System.exit(1);
+ }
+ }
+
+ /**
+ * Measure how long it takes to compute the transfer cache.
+ */
+ private static void measureTransferCacheComputation(
+ SpeedTestTimer timer,
+ TimetableRepository timetableRepository
+ ) {
+ IntStream
+ .range(1, 7)
+ .forEach(reluctance -> {
+ RouteRequest routeRequest = new RouteRequest();
+ routeRequest.withPreferences(b -> b.withWalk(c -> c.withReluctance(reluctance)));
+ timer.recordTimer(
+ "transfer_cache_computation",
+ () -> timetableRepository.getTransitLayer().initTransferCacheForRequest(routeRequest)
+ );
+ });
+ }
+}
diff --git a/application/src/test/java/org/opentripplanner/transit/speed_test/model/timer/SpeedTestTimer.java b/application/src/test/java/org/opentripplanner/transit/speed_test/model/timer/SpeedTestTimer.java
index 48a0548d28e..80970eaad0a 100644
--- a/application/src/test/java/org/opentripplanner/transit/speed_test/model/timer/SpeedTestTimer.java
+++ b/application/src/test/java/org/opentripplanner/transit/speed_test/model/timer/SpeedTestTimer.java
@@ -39,6 +39,7 @@ public class SpeedTestTimer {
List.of(loggerRegistry)
);
private final MeterRegistry uploadRegistry = MeterRegistrySetup.getRegistry().orElse(null);
+
private boolean groupResultByTestCaseCategory = false;
public static int nanosToMillisecond(long nanos) {
@@ -136,6 +137,18 @@ public void globalCount(String meterName, long count) {
}
}
+ /**
+ * Execute the runnable and record its runtime in the meter name passed in.
+ */
+ public void recordTimer(String meterName, Runnable runnable) {
+ if (uploadRegistry != null) {
+ registry.add(uploadRegistry);
+ var timer = registry.timer(meterName);
+ timer.record(runnable);
+ registry.remove(uploadRegistry);
+ }
+ }
+
/**
* Calculate the total time mean for the given timer. If the timer is not
* found {@link #NOT_AVAILABLE} is returned. This can be the case in unit tests,
@@ -175,7 +188,7 @@ public String name(String name, Meter.Type type, String unit) {
}
private String capitalize(String name) {
- if (name.length() != 0 && !Character.isUpperCase(name.charAt(0))) {
+ if (!name.isEmpty() && !Character.isUpperCase(name.charAt(0))) {
char[] chars = name.toCharArray();
chars[0] = Character.toUpperCase(chars[0]);
return new String(chars);
@@ -208,8 +221,8 @@ public static Result merge(Collection results) {
for (Result it : results) {
any = it;
- min = it.min < min ? it.min : min;
- max = it.max > max ? it.max : max;
+ min = Math.min(it.min, min);
+ max = Math.max(it.max, max);
totTime += it.totTime;
count += it.count;
}
diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/walk-steps.json b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/walk-steps.json
index 49908207d44..95adec34ea8 100644
--- a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/walk-steps.json
+++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/walk-steps.json
@@ -11,13 +11,27 @@
"streetName": "street",
"area": false,
"relativeDirection": "DEPART",
- "absoluteDirection": "NORTHEAST"
+ "absoluteDirection": "NORTHEAST",
+ "feature": null
},
{
"streetName": "elevator",
"area": false,
"relativeDirection": "ELEVATOR",
- "absoluteDirection": null
+ "absoluteDirection": null,
+ "feature": null
+ },
+ {
+ "streetName": "entrance",
+ "area": false,
+ "relativeDirection": "CONTINUE",
+ "absoluteDirection": null,
+ "feature": {
+ "__typename": "Entrance",
+ "publicCode": "A",
+ "entranceId": "osm:123",
+ "wheelchairAccessible": "POSSIBLE"
+ }
}
]
},
diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/walk-steps.graphql b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/walk-steps.graphql
index dd2b96395ad..18cb5a8d49d 100644
--- a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/walk-steps.graphql
+++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/walk-steps.graphql
@@ -20,6 +20,14 @@
area
relativeDirection
absoluteDirection
+ feature {
+ __typename
+ ... on Entrance {
+ publicCode
+ entranceId
+ wheelchairAccessible
+ }
+ }
}
}
}
diff --git a/application/src/test/resources/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.graphql b/application/src/test/resources/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.graphql
new file mode 100644
index 00000000000..33deaa2a364
--- /dev/null
+++ b/application/src/test/resources/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.graphql
@@ -0,0 +1,54 @@
+schema {
+ query: QueryType
+}
+
+"REPLACE"
+union AB = AType | BType
+
+"APPEND TO"
+union AC = AType | BType
+
+# Add doc to an undocumented type
+type AType {
+ a: Duration
+ b: String
+}
+
+# Replace existing doc
+"REPLACE"
+type BType {
+ a: String @deprecated(reason: "REPLACE")
+}
+
+# Append doc to existing documentation
+"APPEND TO"
+type CType {
+ "APPENT TO"
+ a: Duration
+ b: String @deprecated(reason: "APPEND TO")
+}
+
+type QueryType {
+ # Add doc to method - args is currently not supported
+ findAB(args: InputType): AB
+ getAC: AC
+ listCs: CType
+ listEs: [AEnum]
+}
+
+# Add doc to enums
+enum AEnum {
+ E1
+ E2
+ E3 @deprecated(reason: "REPLACE")
+}
+
+# Add doc to scalar
+scalar Duration
+
+# Add doc to input type
+input InputType {
+ a: String
+ b: String
+ c: String @deprecated(reason: "REPLACE")
+}
diff --git a/application/src/test/resources/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.graphql.expected b/application/src/test/resources/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.graphql.expected
new file mode 100644
index 00000000000..47319e07ae0
--- /dev/null
+++ b/application/src/test/resources/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.graphql.expected
@@ -0,0 +1,95 @@
+schema {
+ query: QueryType
+}
+
+"Marks the field, argument, input field or enum value as deprecated"
+directive @deprecated(
+ "The reason for the deprecation"
+ reason: String = "No longer supported"
+ ) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION
+
+"Directs the executor to include this field or fragment only when the `if` argument is true"
+directive @include(
+ "Included when true."
+ if: Boolean!
+ ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+"Indicates an Input Object is a OneOf Input Object."
+directive @oneOf on INPUT_OBJECT
+
+"Directs the executor to skip this field or fragment when the `if` argument is true."
+directive @skip(
+ "Skipped when true."
+ if: Boolean!
+ ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+"Exposes a URL that specifies the behaviour of this scalar."
+directive @specifiedBy(
+ "The URL that specifies the behaviour of this scalar."
+ url: String!
+ ) on SCALAR
+
+"AB.description"
+union AB = AType | BType
+
+"""
+APPEND TO
+
+AC.description.append
+"""
+union AC = AType | BType
+
+"AType.description"
+type AType {
+ "AType.a.description"
+ a: Duration
+ b: String @deprecated(reason : "AType.b.deprecated")
+}
+
+"BType.description"
+type BType {
+ "BType.a.description"
+ a: String @deprecated(reason : "REPLACE")
+}
+
+"""
+APPEND TO
+
+CType.description.append
+"""
+type CType {
+ """
+ APPENT TO
+
+ CType.a.description.append
+ """
+ a: Duration
+ b: String @deprecated(reason : "APPEND TO")
+}
+
+type QueryType {
+ "QueryType.findAB.description"
+ findAB(args: InputType): AB
+ getAC: AC @deprecated(reason : "QueryType.getAC.deprecated")
+ listCs: CType
+ listEs: [AEnum]
+}
+
+"AEnum.description"
+enum AEnum {
+ "AEnum.E1.description"
+ E1
+ E2 @deprecated(reason : "AEnum.E2.deprecated")
+ E3 @deprecated(reason : "REPLACE")
+}
+
+"Duration.description"
+scalar Duration
+
+"InputType.description"
+input InputType {
+ "InputType.a.description"
+ a: String
+ b: String @deprecated(reason : "InputType.b.deprecated")
+ c: String @deprecated(reason : "REPLACE")
+}
diff --git a/application/src/test/resources/org/opentripplanner/graph_builder/module/moorgate.osm.pbf b/application/src/test/resources/org/opentripplanner/graph_builder/module/moorgate.osm.pbf
new file mode 100644
index 00000000000..ee95a0e7c51
Binary files /dev/null and b/application/src/test/resources/org/opentripplanner/graph_builder/module/moorgate.osm.pbf differ
diff --git a/client/package-lock.json b/client/package-lock.json
index bb145274bd4..b82002a5b50 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -30,9 +30,9 @@
"@types/react": "19.0.4",
"@types/react-dom": "19.0.2",
"@vitejs/plugin-react": "4.3.4",
- "@vitest/coverage-v8": "2.1.8",
+ "@vitest/coverage-v8": "3.0.2",
"eslint": "9.18.0",
- "eslint-config-prettier": "9.1.0",
+ "eslint-config-prettier": "10.0.1",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-react": "7.37.3",
@@ -44,7 +44,7 @@
"typescript": "5.7.3",
"typescript-eslint": "8.19.1",
"vite": "6.0.7",
- "vitest": "2.1.8"
+ "vitest": "3.0.2"
}
},
"node_modules/@ampproject/remapping": {
@@ -1026,10 +1026,14 @@
}
},
"node_modules/@bcoe/v8-coverage": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
- "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
- "dev": true
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
+ "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
},
"node_modules/@csstools/color-helpers": {
"version": "5.0.1",
@@ -4166,30 +4170,31 @@
}
},
"node_modules/@vitest/coverage-v8": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz",
- "integrity": "sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.2.tgz",
+ "integrity": "sha512-U+hZYb0FtgNDb6B3E9piAHzXXIuxuBw2cd6Lvepc9sYYY4KjgiwCBmo3Sird9ZRu3ggLpLBTfw1ZRr77ipiSfw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
- "@bcoe/v8-coverage": "^0.2.3",
- "debug": "^4.3.7",
+ "@bcoe/v8-coverage": "^1.0.2",
+ "debug": "^4.4.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^5.0.6",
"istanbul-reports": "^3.1.7",
- "magic-string": "^0.30.12",
+ "magic-string": "^0.30.17",
"magicast": "^0.3.5",
"std-env": "^3.8.0",
"test-exclude": "^7.0.1",
- "tinyrainbow": "^1.2.0"
+ "tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "2.1.8",
- "vitest": "2.1.8"
+ "@vitest/browser": "3.0.2",
+ "vitest": "3.0.2"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -4198,64 +4203,96 @@
}
},
"node_modules/@vitest/expect": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz",
- "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.2.tgz",
+ "integrity": "sha512-dKSHLBcoZI+3pmP5hiZ7I5grNru2HRtEW8Z5Zp4IXog8QYcxhlox7JUPyIIFWfN53+3HW3KPLIl6nSzUGgKSuQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@vitest/spy": "2.1.8",
- "@vitest/utils": "2.1.8",
+ "@vitest/spy": "3.0.2",
+ "@vitest/utils": "3.0.2",
"chai": "^5.1.2",
- "tinyrainbow": "^1.2.0"
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.2.tgz",
+ "integrity": "sha512-Hr09FoBf0jlwwSyzIF4Xw31OntpO3XtZjkccpcBf8FeVW3tpiyKlkeUzxS/txzHqpUCNIX157NaTySxedyZLvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.0.2",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
},
"funding": {
"url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
}
},
"node_modules/@vitest/pretty-format": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz",
- "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.2.tgz",
+ "integrity": "sha512-yBohcBw/T/p0/JRgYD+IYcjCmuHzjC3WLAKsVE4/LwiubzZkE8N49/xIQ/KGQwDRA8PaviF8IRO8JMWMngdVVQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "tinyrainbow": "^1.2.0"
+ "tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/runner": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz",
- "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.2.tgz",
+ "integrity": "sha512-GHEsWoncrGxWuW8s405fVoDfSLk6RF2LCXp6XhevbtDjdDme1WV/eNmUueDfpY1IX3MJaCRelVCEXsT9cArfEg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@vitest/utils": "2.1.8",
- "pathe": "^1.1.2"
+ "@vitest/utils": "3.0.2",
+ "pathe": "^2.0.1"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/snapshot": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz",
- "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.2.tgz",
+ "integrity": "sha512-h9s67yD4+g+JoYG0zPCo/cLTabpDqzqNdzMawmNPzDStTiwxwkyYM1v5lWE8gmGv3SVJ2DcxA2NpQJZJv9ym3g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "2.1.8",
- "magic-string": "^0.30.12",
- "pathe": "^1.1.2"
+ "@vitest/pretty-format": "3.0.2",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.1"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/spy": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz",
- "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.2.tgz",
+ "integrity": "sha512-8mI2iUn+PJFMT44e3ISA1R+K6ALVs47W6eriDTfXe6lFqlflID05MB4+rIFhmDSLBj8iBsZkzBYlgSkinxLzSQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"tinyspy": "^3.0.2"
},
@@ -4264,14 +4301,15 @@
}
},
"node_modules/@vitest/utils": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz",
- "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.2.tgz",
+ "integrity": "sha512-Qu01ZYZlgHvDP02JnMBRpX43nRaZtNpIzw3C1clDXmn8eakgX6iQVGzTQ/NjkIr64WD8ioqOjkaYRVvHQI5qiw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "2.1.8",
+ "@vitest/pretty-format": "3.0.2",
"loupe": "^3.1.2",
- "tinyrainbow": "^1.2.0"
+ "tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
@@ -5992,12 +6030,13 @@
}
},
"node_modules/eslint-config-prettier": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
- "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz",
+ "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==",
"dev": true,
+ "license": "MIT",
"bin": {
- "eslint-config-prettier": "bin/cli.js"
+ "eslint-config-prettier": "build/bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
@@ -9055,10 +9094,11 @@
}
},
"node_modules/pathe": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
- "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
- "dev": true
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz",
+ "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/pathval": {
"version": "2.0.0",
@@ -10457,10 +10497,11 @@
"integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="
},
"node_modules/tinyrainbow": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
- "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=14.0.0"
}
@@ -10470,6 +10511,7 @@
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
"integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=14.0.0"
}
@@ -10995,533 +11037,70 @@
}
},
"node_modules/vite-node": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz",
- "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.2.tgz",
+ "integrity": "sha512-hsEQerBAHvVAbv40m3TFQe/lTEbOp7yDpyqMJqr2Tnd+W58+DEYOt+fluQgekOePcsNBmR77lpVAnIU2Xu4SvQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"cac": "^6.7.14",
- "debug": "^4.3.7",
- "es-module-lexer": "^1.5.4",
- "pathe": "^1.1.2",
- "vite": "^5.0.0"
+ "debug": "^4.4.0",
+ "es-module-lexer": "^1.6.0",
+ "pathe": "^2.0.1",
+ "vite": "^5.0.0 || ^6.0.0"
},
"bin": {
"vite-node": "vite-node.mjs"
},
"engines": {
- "node": "^18.0.0 || >=20.0.0"
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/vite-node/node_modules/@esbuild/aix-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
- "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/android-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
- "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/android-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
- "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/android-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
- "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/darwin-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
- "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/darwin-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
- "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
- "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/freebsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
- "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/linux-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
- "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/linux-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
- "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/linux-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
- "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/linux-loong64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
- "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/linux-mips64el": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
- "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/linux-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
- "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/linux-riscv64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
- "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/linux-s390x": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
- "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
- "cpu": [
- "s390x"
- ],
+ "node_modules/vitest": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.2.tgz",
+ "integrity": "sha512-5bzaHakQ0hmVVKLhfh/jXf6oETDBtgPo8tQCHYB+wftNgFJ+Hah67IsWc8ivx4vFL025Ow8UiuTf4W57z4izvQ==",
"dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "3.0.2",
+ "@vitest/mocker": "3.0.2",
+ "@vitest/pretty-format": "^3.0.2",
+ "@vitest/runner": "3.0.2",
+ "@vitest/snapshot": "3.0.2",
+ "@vitest/spy": "3.0.2",
+ "@vitest/utils": "3.0.2",
+ "chai": "^5.1.2",
+ "debug": "^4.4.0",
+ "expect-type": "^1.1.0",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.1",
+ "std-env": "^3.8.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinypool": "^1.0.2",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0",
+ "vite-node": "3.0.2",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
"engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/linux-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
- "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/netbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
- "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/openbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
- "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/sunos-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
- "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/win32-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
- "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/win32-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
- "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/@esbuild/win32-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
- "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vite-node/node_modules/esbuild": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
- "dev": true,
- "hasInstallScript": true,
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.21.5",
- "@esbuild/android-arm": "0.21.5",
- "@esbuild/android-arm64": "0.21.5",
- "@esbuild/android-x64": "0.21.5",
- "@esbuild/darwin-arm64": "0.21.5",
- "@esbuild/darwin-x64": "0.21.5",
- "@esbuild/freebsd-arm64": "0.21.5",
- "@esbuild/freebsd-x64": "0.21.5",
- "@esbuild/linux-arm": "0.21.5",
- "@esbuild/linux-arm64": "0.21.5",
- "@esbuild/linux-ia32": "0.21.5",
- "@esbuild/linux-loong64": "0.21.5",
- "@esbuild/linux-mips64el": "0.21.5",
- "@esbuild/linux-ppc64": "0.21.5",
- "@esbuild/linux-riscv64": "0.21.5",
- "@esbuild/linux-s390x": "0.21.5",
- "@esbuild/linux-x64": "0.21.5",
- "@esbuild/netbsd-x64": "0.21.5",
- "@esbuild/openbsd-x64": "0.21.5",
- "@esbuild/sunos-x64": "0.21.5",
- "@esbuild/win32-arm64": "0.21.5",
- "@esbuild/win32-ia32": "0.21.5",
- "@esbuild/win32-x64": "0.21.5"
- }
- },
- "node_modules/vite-node/node_modules/vite": {
- "version": "5.4.11",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
- "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
- "dev": true,
- "dependencies": {
- "esbuild": "^0.21.3",
- "postcss": "^8.4.43",
- "rollup": "^4.20.0"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^18.0.0 || >=20.0.0",
- "less": "*",
- "lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.4.0"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- }
- }
- },
- "node_modules/vitest": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz",
- "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==",
- "dev": true,
- "dependencies": {
- "@vitest/expect": "2.1.8",
- "@vitest/mocker": "2.1.8",
- "@vitest/pretty-format": "^2.1.8",
- "@vitest/runner": "2.1.8",
- "@vitest/snapshot": "2.1.8",
- "@vitest/spy": "2.1.8",
- "@vitest/utils": "2.1.8",
- "chai": "^5.1.2",
- "debug": "^4.3.7",
- "expect-type": "^1.1.0",
- "magic-string": "^0.30.12",
- "pathe": "^1.1.2",
- "std-env": "^3.8.0",
- "tinybench": "^2.9.0",
- "tinyexec": "^0.3.1",
- "tinypool": "^1.0.1",
- "tinyrainbow": "^1.2.0",
- "vite": "^5.0.0",
- "vite-node": "2.1.8",
- "why-is-node-running": "^2.3.0"
- },
- "bin": {
- "vitest": "vitest.mjs"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@edge-runtime/vm": "*",
- "@types/node": "^18.0.0 || >=20.0.0",
- "@vitest/browser": "2.1.8",
- "@vitest/ui": "2.1.8",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.0.2",
+ "@vitest/ui": "3.0.2",
"happy-dom": "*",
"jsdom": "*"
},
@@ -11546,497 +11125,6 @@
}
}
},
- "node_modules/vitest/node_modules/@esbuild/aix-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
- "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/android-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
- "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/android-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
- "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/android-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
- "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/darwin-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
- "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/darwin-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
- "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
- "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/freebsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
- "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/linux-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
- "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/linux-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
- "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/linux-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
- "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/linux-loong64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
- "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/linux-mips64el": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
- "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/linux-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
- "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/linux-riscv64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
- "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/linux-s390x": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
- "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/linux-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
- "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/netbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
- "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/openbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
- "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/sunos-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
- "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/win32-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
- "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/win32-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
- "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@esbuild/win32-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
- "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/vitest/node_modules/@vitest/mocker": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz",
- "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==",
- "dev": true,
- "dependencies": {
- "@vitest/spy": "2.1.8",
- "estree-walker": "^3.0.3",
- "magic-string": "^0.30.12"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "msw": "^2.4.9",
- "vite": "^5.0.0"
- },
- "peerDependenciesMeta": {
- "msw": {
- "optional": true
- },
- "vite": {
- "optional": true
- }
- }
- },
- "node_modules/vitest/node_modules/esbuild": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
- "dev": true,
- "hasInstallScript": true,
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.21.5",
- "@esbuild/android-arm": "0.21.5",
- "@esbuild/android-arm64": "0.21.5",
- "@esbuild/android-x64": "0.21.5",
- "@esbuild/darwin-arm64": "0.21.5",
- "@esbuild/darwin-x64": "0.21.5",
- "@esbuild/freebsd-arm64": "0.21.5",
- "@esbuild/freebsd-x64": "0.21.5",
- "@esbuild/linux-arm": "0.21.5",
- "@esbuild/linux-arm64": "0.21.5",
- "@esbuild/linux-ia32": "0.21.5",
- "@esbuild/linux-loong64": "0.21.5",
- "@esbuild/linux-mips64el": "0.21.5",
- "@esbuild/linux-ppc64": "0.21.5",
- "@esbuild/linux-riscv64": "0.21.5",
- "@esbuild/linux-s390x": "0.21.5",
- "@esbuild/linux-x64": "0.21.5",
- "@esbuild/netbsd-x64": "0.21.5",
- "@esbuild/openbsd-x64": "0.21.5",
- "@esbuild/sunos-x64": "0.21.5",
- "@esbuild/win32-arm64": "0.21.5",
- "@esbuild/win32-ia32": "0.21.5",
- "@esbuild/win32-x64": "0.21.5"
- }
- },
- "node_modules/vitest/node_modules/vite": {
- "version": "5.4.11",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
- "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
- "dev": true,
- "dependencies": {
- "esbuild": "^0.21.3",
- "postcss": "^8.4.43",
- "rollup": "^4.20.0"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^18.0.0 || >=20.0.0",
- "less": "*",
- "lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.4.0"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- }
- }
- },
"node_modules/vt-pbf": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
diff --git a/client/package.json b/client/package.json
index 78180de6b6a..f34ae361197 100644
--- a/client/package.json
+++ b/client/package.json
@@ -39,9 +39,9 @@
"@types/react": "19.0.4",
"@types/react-dom": "19.0.2",
"@vitejs/plugin-react": "4.3.4",
- "@vitest/coverage-v8": "2.1.8",
+ "@vitest/coverage-v8": "3.0.2",
"eslint": "9.18.0",
- "eslint-config-prettier": "9.1.0",
+ "eslint-config-prettier": "10.0.1",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-react": "7.37.3",
@@ -53,6 +53,6 @@
"typescript": "5.7.3",
"typescript-eslint": "8.19.1",
"vite": "6.0.7",
- "vitest": "2.1.8"
+ "vitest": "3.0.2"
}
}
diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md
index 99e98066e73..c5fdfa8095b 100644
--- a/doc/user/BuildConfiguration.md
+++ b/doc/user/BuildConfiguration.md
@@ -84,10 +84,12 @@ Sections follow that describe particular settings in more depth.
| [sharedGroupFilePattern](#nd_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 |
| [ferryIdsNotAllowedForBicycle](#nd_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 |
| [osm](#osm) | `object[]` | Configure properties for a given OpenStreetMap feed. | *Optional* | | 2.2 |
+| includeOsmSubwayEntrances | `boolean` | Whether to include subway entrances from the OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | `false` | 2.7 |
| [osmTagMapping](#osm_0_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. Overrides the value specified in `osmDefaults`. | *Optional* | `"default"` | 2.2 |
| source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 |
| timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | | 2.2 |
| osmDefaults | `object` | Default properties for OpenStreetMap feeds. | *Optional* | | 2.2 |
+| includeOsmSubwayEntrances | `boolean` | Whether to include subway entrances from the OSM data. | *Optional* | `false` | 2.7 |
| [osmTagMapping](#od_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. | *Optional* | `"default"` | 2.2 |
| timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. | *Optional* | | 2.2 |
| [transferRequests](RouteRequest.md) | `object[]` | Routing requests to use for pre-calculating stop-to-stop transfers. | *Optional* | | 2.1 |
diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md
index 5f4e1a054e2..9a5f2731733 100644
--- a/doc/user/Changelog.md
+++ b/doc/user/Changelog.md
@@ -72,6 +72,12 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle
- Add a matcher API for filters in the transit service used for regularStop lookup [#6234](https://github.com/opentripplanner/OpenTripPlanner/pull/6234)
- Make all polling updaters wait for graph update finish [#6262](https://github.com/opentripplanner/OpenTripPlanner/pull/6262)
- When using ScheduledTransitLeg's copy builder, also copy alerts [#6368](https://github.com/opentripplanner/OpenTripPlanner/pull/6368)
+- Process boarding location for OSM ways (linear platforms) [#6247](https://github.com/opentripplanner/OpenTripPlanner/pull/6247)
+- Fix `bookWhen` field is `null` in the Transmodel API [#6385](https://github.com/opentripplanner/OpenTripPlanner/pull/6385)
+- Make it possible to add custom API documentation based on the deployment location [#6355](https://github.com/opentripplanner/OpenTripPlanner/pull/6355)
+- If configured, add subway station entrances from OSM to walk steps [#6343](https://github.com/opentripplanner/OpenTripPlanner/pull/6343)
+- Revert allow multiple states during transfer edge traversals [#6357](https://github.com/opentripplanner/OpenTripPlanner/pull/6357)
+- Generate Raptor transfer cache in parallel [#6326](https://github.com/opentripplanner/OpenTripPlanner/pull/6326)
[](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE)
## 2.6.0 (2024-09-18)
diff --git a/doc/user/RouterConfiguration.md b/doc/user/RouterConfiguration.md
index 82d14f36392..b5cbf15a4a5 100644
--- a/doc/user/RouterConfiguration.md
+++ b/doc/user/RouterConfiguration.md
@@ -31,45 +31,46 @@ A full list of them can be found in the [RouteRequest](RouteRequest.md).
-| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
-|-------------------------------------------------------------------------------------------|:---------------------:|-------------------------------------------------------------------------------------------------------|:----------:|---------------|:-----:|
-| [configVersion](#configVersion) | `string` | Deployment version of the *router-config.json*. | *Optional* | | 2.1 |
-| [flex](sandbox/Flex.md) | `object` | Configuration for flex routing. | *Optional* | | 2.1 |
-| [rideHailingServices](sandbox/RideHailing.md) | `object[]` | Configuration for interfaces to external ride hailing services like Uber. | *Optional* | | 2.3 |
-| [routingDefaults](RouteRequest.md) | `object` | The default parameters for the routing query. | *Optional* | | 2.0 |
-| [server](#server) | `object` | Configuration for router server. | *Optional* | | 2.4 |
-| [apiProcessingTimeout](#server_apiProcessingTimeout) | `duration` | Maximum processing time for an API request | *Optional* | `"PT-1S"` | 2.4 |
-| [traceParameters](#server_traceParameters) | `object[]` | Trace OTP request using HTTP request/response parameter(s) combined with logging. | *Optional* | | 2.4 |
-| generateIdIfMissing | `boolean` | If `true` a unique value is generated if no http request header is provided, or the value is missing. | *Optional* | `false` | 2.4 |
-| httpRequestHeader | `string` | The header-key to use when fetching the trace parameter value | *Optional* | | 2.4 |
-| httpResponseHeader | `string` | The header-key to use when saving the value back into the http response | *Optional* | | 2.4 |
-| [logKey](#server_traceParameters_0_logKey) | `string` | The log event key used. | *Optional* | | 2.4 |
-| timetableUpdates | `object` | Global configuration for timetable updaters. | *Optional* | | 2.2 |
-| [maxSnapshotFrequency](#timetableUpdates_maxSnapshotFrequency) | `duration` | How long a snapshot should be cached. | *Optional* | `"PT1S"` | 2.2 |
-| purgeExpiredData | `boolean` | Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 |
-| [transit](#transit) | `object` | Configuration for transit searches with RAPTOR. | *Optional* | | na |
-| [iterationDepartureStepInSeconds](#transit_iterationDepartureStepInSeconds) | `integer` | Step for departure times between each RangeRaptor iterations. | *Optional* | `60` | na |
-| [maxNumberOfTransfers](#transit_maxNumberOfTransfers) | `integer` | This parameter is used to allocate enough memory space for Raptor. | *Optional* | `12` | na |
-| [maxSearchWindow](#transit_maxSearchWindow) | `duration` | Upper limit of the request parameter searchWindow. | *Optional* | `"PT24H"` | 2.4 |
-| [scheduledTripBinarySearchThreshold](#transit_scheduledTripBinarySearchThreshold) | `integer` | This threshold is used to determine when to perform a binary trip schedule search. | *Optional* | `50` | na |
-| [searchThreadPoolSize](#transit_searchThreadPoolSize) | `integer` | Split a travel search in smaller jobs and run them in parallel to improve performance. | *Optional* | `0` | na |
-| [transferCacheMaxSize](#transit_transferCacheMaxSize) | `integer` | The maximum number of distinct transfers parameters to cache pre-calculated transfers for. | *Optional* | `25` | na |
-| [dynamicSearchWindow](#transit_dynamicSearchWindow) | `object` | The dynamic search window coefficients used to calculate the EDT, LAT and SW. | *Optional* | | 2.1 |
-| [maxWindow](#transit_dynamicSearchWindow_maxWindow) | `duration` | Upper limit for the search-window calculation. | *Optional* | `"PT3H"` | 2.2 |
-| [minTransitTimeCoefficient](#transit_dynamicSearchWindow_minTransitTimeCoefficient) | `double` | The coefficient to multiply with `minTransitTime`. | *Optional* | `0.5` | 2.1 |
-| [minWaitTimeCoefficient](#transit_dynamicSearchWindow_minWaitTimeCoefficient) | `double` | The coefficient to multiply with `minWaitTime`. | *Optional* | `0.5` | 2.1 |
-| [minWindow](#transit_dynamicSearchWindow_minWindow) | `duration` | The constant minimum duration for a raptor-search-window. | *Optional* | `"PT40M"` | 2.2 |
-| [stepMinutes](#transit_dynamicSearchWindow_stepMinutes) | `integer` | Used to set the steps the search-window is rounded to. | *Optional* | `10` | 2.1 |
-| [pagingSearchWindowAdjustments](#transit_pagingSearchWindowAdjustments) | `duration[]` | The provided array of durations is used to increase the search-window for the next/previous page. | *Optional* | | na |
-| [stopBoardAlightDuringTransferCost](#transit_stopBoardAlightDuringTransferCost) | `enum map of integer` | Costs for boarding and alighting during transfers at stops with a given transfer priority. | *Optional* | | 2.0 |
-| [transferCacheRequests](#transit_transferCacheRequests) | `object[]` | Routing requests to use for pre-filling the stop-to-stop transfer cache. | *Optional* | | 2.3 |
-| transmodelApi | `object` | Configuration for the Transmodel GraphQL API. | *Optional* | | 2.1 |
-| [hideFeedId](#transmodelApi_hideFeedId) | `boolean` | Hide the FeedId in all API output, and add it to input. | *Optional* | `false` | na |
-| [maxNumberOfResultFields](#transmodelApi_maxNumberOfResultFields) | `integer` | The maximum number of fields in a GraphQL result | *Optional* | `1000000` | 2.6 |
-| [tracingHeaderTags](#transmodelApi_tracingHeaderTags) | `string[]` | Used to group requests when monitoring OTP. | *Optional* | | na |
-| [updaters](UpdaterConfig.md) | `object[]` | Configuration for the updaters that import various types of data into OTP. | *Optional* | | 1.5 |
-| [vectorTiles](sandbox/MapboxVectorTilesApi.md) | `object` | Vector tile configuration | *Optional* | | na |
-| [vehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) | `object` | Configuration for the vehicle rental service directory. | *Optional* | | 2.0 |
+| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
+|-------------------------------------------------------------------------------------------|:---------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|---------------|:-----:|
+| [configVersion](#configVersion) | `string` | Deployment version of the *router-config.json*. | *Optional* | | 2.1 |
+| [flex](sandbox/Flex.md) | `object` | Configuration for flex routing. | *Optional* | | 2.1 |
+| [rideHailingServices](sandbox/RideHailing.md) | `object[]` | Configuration for interfaces to external ride hailing services like Uber. | *Optional* | | 2.3 |
+| [routingDefaults](RouteRequest.md) | `object` | The default parameters for the routing query. | *Optional* | | 2.0 |
+| [server](#server) | `object` | Configuration for router server. | *Optional* | | 2.4 |
+| [apiDocumentationProfile](#server_apiDocumentationProfile) | `enum` | List of available custom documentation profiles. A profile is used to inject custom documentation like type and field description or a deprecated reason. Currently, ONLY the Transmodel API supports this feature. | *Optional* | `"default"` | 2.7 |
+| [apiProcessingTimeout](#server_apiProcessingTimeout) | `duration` | Maximum processing time for an API request | *Optional* | `"PT-1S"` | 2.4 |
+| [traceParameters](#server_traceParameters) | `object[]` | Trace OTP request using HTTP request/response parameter(s) combined with logging. | *Optional* | | 2.4 |
+| generateIdIfMissing | `boolean` | If `true` a unique value is generated if no http request header is provided, or the value is missing. | *Optional* | `false` | 2.4 |
+| httpRequestHeader | `string` | The header-key to use when fetching the trace parameter value | *Optional* | | 2.4 |
+| httpResponseHeader | `string` | The header-key to use when saving the value back into the http response | *Optional* | | 2.4 |
+| [logKey](#server_traceParameters_0_logKey) | `string` | The log event key used. | *Optional* | | 2.4 |
+| timetableUpdates | `object` | Global configuration for timetable updaters. | *Optional* | | 2.2 |
+| [maxSnapshotFrequency](#timetableUpdates_maxSnapshotFrequency) | `duration` | How long a snapshot should be cached. | *Optional* | `"PT1S"` | 2.2 |
+| purgeExpiredData | `boolean` | Should expired real-time data be purged from the graph. Apply to GTFS-RT and Siri updates. | *Optional* | `true` | 2.2 |
+| [transit](#transit) | `object` | Configuration for transit searches with RAPTOR. | *Optional* | | na |
+| [iterationDepartureStepInSeconds](#transit_iterationDepartureStepInSeconds) | `integer` | Step for departure times between each RangeRaptor iterations. | *Optional* | `60` | na |
+| [maxNumberOfTransfers](#transit_maxNumberOfTransfers) | `integer` | This parameter is used to allocate enough memory space for Raptor. | *Optional* | `12` | na |
+| [maxSearchWindow](#transit_maxSearchWindow) | `duration` | Upper limit of the request parameter searchWindow. | *Optional* | `"PT24H"` | 2.4 |
+| [scheduledTripBinarySearchThreshold](#transit_scheduledTripBinarySearchThreshold) | `integer` | This threshold is used to determine when to perform a binary trip schedule search. | *Optional* | `50` | na |
+| [searchThreadPoolSize](#transit_searchThreadPoolSize) | `integer` | Split a travel search in smaller jobs and run them in parallel to improve performance. | *Optional* | `0` | na |
+| [transferCacheMaxSize](#transit_transferCacheMaxSize) | `integer` | The maximum number of distinct transfers parameters to cache pre-calculated transfers for. | *Optional* | `25` | na |
+| [dynamicSearchWindow](#transit_dynamicSearchWindow) | `object` | The dynamic search window coefficients used to calculate the EDT, LAT and SW. | *Optional* | | 2.1 |
+| [maxWindow](#transit_dynamicSearchWindow_maxWindow) | `duration` | Upper limit for the search-window calculation. | *Optional* | `"PT3H"` | 2.2 |
+| [minTransitTimeCoefficient](#transit_dynamicSearchWindow_minTransitTimeCoefficient) | `double` | The coefficient to multiply with `minTransitTime`. | *Optional* | `0.5` | 2.1 |
+| [minWaitTimeCoefficient](#transit_dynamicSearchWindow_minWaitTimeCoefficient) | `double` | The coefficient to multiply with `minWaitTime`. | *Optional* | `0.5` | 2.1 |
+| [minWindow](#transit_dynamicSearchWindow_minWindow) | `duration` | The constant minimum duration for a raptor-search-window. | *Optional* | `"PT40M"` | 2.2 |
+| [stepMinutes](#transit_dynamicSearchWindow_stepMinutes) | `integer` | Used to set the steps the search-window is rounded to. | *Optional* | `10` | 2.1 |
+| [pagingSearchWindowAdjustments](#transit_pagingSearchWindowAdjustments) | `duration[]` | The provided array of durations is used to increase the search-window for the next/previous page. | *Optional* | | na |
+| [stopBoardAlightDuringTransferCost](#transit_stopBoardAlightDuringTransferCost) | `enum map of integer` | Costs for boarding and alighting during transfers at stops with a given transfer priority. | *Optional* | | 2.0 |
+| [transferCacheRequests](#transit_transferCacheRequests) | `object[]` | Routing requests to use for pre-filling the stop-to-stop transfer cache. | *Optional* | | 2.3 |
+| transmodelApi | `object` | Configuration for the Transmodel GraphQL API. | *Optional* | | 2.1 |
+| [hideFeedId](#transmodelApi_hideFeedId) | `boolean` | Hide the FeedId in all API output, and add it to input. | *Optional* | `false` | na |
+| [maxNumberOfResultFields](#transmodelApi_maxNumberOfResultFields) | `integer` | The maximum number of fields in a GraphQL result | *Optional* | `1000000` | 2.6 |
+| [tracingHeaderTags](#transmodelApi_tracingHeaderTags) | `string[]` | Used to group requests when monitoring OTP. | *Optional* | | na |
+| [updaters](UpdaterConfig.md) | `object[]` | Configuration for the updaters that import various types of data into OTP. | *Optional* | | 1.5 |
+| [vectorTiles](sandbox/MapboxVectorTilesApi.md) | `object` | Vector tile configuration | *Optional* | | na |
+| [vehicleRentalServiceDirectory](sandbox/VehicleRentalServiceDirectory.md) | `object` | Configuration for the vehicle rental service directory. | *Optional* | | 2.0 |
@@ -108,6 +109,22 @@ These parameters are used to configure the router server. Many parameters are sp
domain, these are set in the routing request.
+apiDocumentationProfile
+
+**Since version:** `2.7` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"default"`
+**Path:** /server
+**Enum values:** `default` | `entur`
+
+List of available custom documentation profiles. A profile is used to inject custom
+documentation like type and field description or a deprecated reason.
+
+Currently, ONLY the Transmodel API supports this feature.
+
+
+ - `default` Default documentation is used.
+ - `entur` Entur specific documentation. This deprecate features not supported at Entur, Norway.
+
+
apiProcessingTimeout
**Since version:** `2.4` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"PT-1S"`
diff --git a/pom.xml b/pom.xml
index e68b6ae48a0..3a7d7e3703e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,11 +58,11 @@
- 176
+ 177
32.1
- 2.53
+ 2.54
2.18.2
4.0.5
3.1.10
diff --git a/renovate.json5 b/renovate.json5
index 100a91d959a..30a5c6a99c1 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -104,6 +104,7 @@
"io.micrometer:micrometer-registry-influx",
"com.google.protobuf:protobuf-java"
],
+ "matchUpdateTypes": ["major", "minor"],
"schedule": "on the 7th through 8th day of the month"
},
{
@@ -144,11 +145,19 @@
"schedule": "on the 4th day of the month"
},
{
- "groupName": "Low-risk dependencies (patch)",
+ "groupName": "highly trusted dependencies (patch)",
"matchUpdateTypes": ["patch"],
"schedule": ["on the 27th day of the month"],
"matchPackageNames": [
+ "org.onebusaway:onebusaway-gtfs",
"org.glassfish.jersey.{/,}**",
+ "com.google.guava:guava",
+ "com.google.cloud:libraries-bom",
+ "com.google.protobuf:protobuf-java",
+ "io.micrometer:micrometer-registry-prometheus",
+ "io.micrometer:micrometer-registry-influx",
+ "com.fasterxml.jackson:{/,}**",
+ "com.fasterxml.jackson.datatype::{/,}**"
]
},
{
diff --git a/utils/src/main/java/org/opentripplanner/utils/text/TextVariablesSubstitution.java b/utils/src/main/java/org/opentripplanner/utils/text/TextVariablesSubstitution.java
new file mode 100644
index 00000000000..95226ed4bd0
--- /dev/null
+++ b/utils/src/main/java/org/opentripplanner/utils/text/TextVariablesSubstitution.java
@@ -0,0 +1,102 @@
+package org.opentripplanner.utils.text;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This utility class substitute variable placeholders in a given text on the format ${variable}.
+ *
+ * The pattern matching a placeholder must start with '${' and end with '}'. The variable name
+ * must consist of only alphanumerical characters (a-z, A-Z, 0-9), dot `.` and underscore '_'.
+ */
+public class TextVariablesSubstitution {
+
+ private static final Pattern PATTERN = Pattern.compile("\\$\\{([.\\w]+)}");
+
+ /**
+ * This method uses the {@link #insertVariables(String, Function, Consumer)} to substitute
+ * all variable tokens in all values in the given {@code properties}. It supports nesting, but
+ * you must avoid cyclic references.
+ *
+ * Example:
+ *
+ * a -> My car is a ${b} car, with an ${c} look.
+ * b -> good old ${c}
+ * c -> fancy
+ *
+ * This will resolve to:
+ *
+ * a -> My car is a good old fancy car, with an fancy look.
+ * b -> good old fancy
+ * c -> fancy
+ *
+ */
+ public static Map insertVariables(
+ Map properties,
+ Consumer errorHandler
+ ) {
+ var result = new HashMap(properties);
+
+ for (String key : result.keySet()) {
+ var value = result.get(key);
+ var sub = insertVariables(value, result::get, errorHandler);
+ if (!value.equals(sub)) {
+ result.put(key, sub);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Replace all variables({@code ${variable.name}}) in the given {@code text}. The given
+ * {@code variableProvider} is used to look up values to insert into the text replacing the
+ * variable token.
+ *
+ * @param errorHandler The error handler is called if a variable key does not exist in the
+ * {@code variableProvider}.
+ * @return the new value with all variables replaced.
+ */
+ public static String insertVariables(
+ String text,
+ Function variableProvider,
+ Consumer errorHandler
+ ) {
+ return insert(text, PATTERN.matcher(text), variableProvider, errorHandler);
+ }
+
+ private static String insert(
+ String text,
+ Matcher matcher,
+ Function variableProvider,
+ Consumer errorHandler
+ ) {
+ boolean matchFound = matcher.find();
+ if (!matchFound) {
+ return text;
+ }
+
+ Map substitutions = new HashMap<>();
+
+ while (matchFound) {
+ String subKey = matcher.group(0);
+ String nameOnly = matcher.group(1);
+ if (!substitutions.containsKey(nameOnly)) {
+ String value = variableProvider.apply(nameOnly);
+ if (value != null) {
+ substitutions.put(subKey, value);
+ } else {
+ errorHandler.accept(nameOnly);
+ }
+ }
+ matchFound = matcher.find();
+ }
+ for (Map.Entry entry : substitutions.entrySet()) {
+ text = text.replace(entry.getKey(), entry.getValue());
+ }
+ return insert(text, PATTERN.matcher(text), variableProvider, errorHandler);
+ }
+}
diff --git a/utils/src/test/java/org/opentripplanner/utils/text/TextVariablesSubstitutionTest.java b/utils/src/test/java/org/opentripplanner/utils/text/TextVariablesSubstitutionTest.java
new file mode 100644
index 00000000000..5c1c2014cc2
--- /dev/null
+++ b/utils/src/test/java/org/opentripplanner/utils/text/TextVariablesSubstitutionTest.java
@@ -0,0 +1,56 @@
+package org.opentripplanner.utils.text;
+
+import static java.util.Map.entry;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.opentripplanner.utils.text.TextVariablesSubstitution.insertVariables;
+
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+class TextVariablesSubstitutionTest {
+
+ @Test
+ void testInsertVariablesInProperties() {
+ Map map = Map.ofEntries(
+ entry("a", "A"),
+ entry("b", "B"),
+ entry("ab", "${a}${b}"),
+ entry("ab2", "${ab} - ${a} - ${b}")
+ );
+
+ var result = insertVariables(map, this::errorHandler);
+
+ assertEquals("A", result.get("a"));
+ assertEquals("B", result.get("b"));
+ assertEquals("AB", result.get("ab"));
+ assertEquals("AB - A - B", result.get("ab2"));
+ }
+
+ @Test
+ void testInsertVariablesInValue() {
+ var map = Map.ofEntries(
+ entry("a", "A"),
+ entry("b", "B"),
+ entry("ab", "${a}${b}"),
+ entry("ab2", "${ab} - ${a} - ${b}")
+ );
+
+ assertEquals(
+ "No substitution",
+ insertVariables("No substitution", map::get, this::errorHandler)
+ );
+ assertEquals("A B", insertVariables("${a} ${b}", map::get, this::errorHandler));
+ assertEquals("AB", insertVariables("${ab}", map::get, this::errorHandler));
+ assertEquals("AB - A - B", insertVariables("${ab2}", map::get, this::errorHandler));
+ var ex = assertThrows(
+ IllegalArgumentException.class,
+ () -> insertVariables("${c}", map::get, this::errorHandler)
+ );
+ assertEquals("c", ex.getMessage());
+ }
+
+ private void errorHandler(String name) {
+ throw new IllegalArgumentException(name);
+ }
+}