From 1d19a052a8bf2523c3af537edfc92fe9a72e8c62 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Wed, 18 Sep 2024 17:22:19 +0300 Subject: [PATCH 001/195] Add subway station entrances to walk steps --- .../apis/gtfs/datafetchers/stepImpl.java | 5 +++++ .../gtfs/generated/GraphQLDataFetchers.java | 11 +++++++++++ .../apis/gtfs/generated/GraphQLTypes.java | 1 + .../module/osm/VertexGenerator.java | 9 +++++++++ .../opentripplanner/model/plan/WalkStep.java | 10 ++++++++++ .../model/plan/WalkStepBuilder.java | 7 +++++++ .../openstreetmap/model/OSMNode.java | 9 +++++++++ .../mapping/StatesToWalkStepsMapper.java | 18 ++++++++++++++++++ .../model/vertex/StationEntranceVertex.java | 19 +++++++++++++++++++ .../street/model/vertex/VertexFactory.java | 9 +++++++++ .../opentripplanner/apis/gtfs/schema.graphqls | 2 ++ 11 files changed, 100 insertions(+) create mode 100644 src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java index 6bd51ae5f29..d79e224e51e 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java @@ -53,6 +53,11 @@ public DataFetcher exit() { return environment -> getSource(environment).getExit(); } + @Override + public DataFetcher entrance() { + return environment -> getSource(environment).getEntrance(); + } + @Override public DataFetcher lat() { return environment -> getSource(environment).getStartLocation().latitude(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 67944543580..3c162b14112 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -1,9 +1,11 @@ //THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. package org.opentripplanner.apis.gtfs.generated; +import graphql.relay.Connection; import graphql.relay.Connection; import graphql.relay.DefaultEdge; import graphql.relay.Edge; +import graphql.relay.Edge; import graphql.schema.DataFetcher; import graphql.schema.TypeResolver; import java.util.Currency; @@ -24,8 +26,12 @@ import org.opentripplanner.apis.gtfs.model.FeedPublisher; import org.opentripplanner.apis.gtfs.model.PlanPageInfo; import org.opentripplanner.apis.gtfs.model.RideHailingProvider; +import org.opentripplanner.apis.gtfs.model.RouteTypeModel; +import org.opentripplanner.apis.gtfs.model.StopOnRouteModel; +import org.opentripplanner.apis.gtfs.model.StopOnTripModel; import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.apis.gtfs.model.TripOccupancy; +import org.opentripplanner.apis.gtfs.model.UnknownModel; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.ridehailing.model.RideEstimate; import org.opentripplanner.model.StopTimesInPattern; @@ -48,6 +54,8 @@ import org.opentripplanner.routing.graphfinder.PatternAtStop; import org.opentripplanner.routing.graphfinder.PlaceAtDistance; import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; import org.opentripplanner.routing.vehicle_parking.VehicleParkingState; import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; @@ -58,6 +66,7 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; +import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; import org.opentripplanner.transit.model.basic.Money; @@ -1419,6 +1428,8 @@ public interface GraphQLStep { public DataFetcher> elevationProfile(); + public DataFetcher entrance(); + public DataFetcher exit(); public DataFetcher lat(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 67051444cdf..8edc0cce870 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -1,6 +1,7 @@ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. package org.opentripplanner.apis.gtfs.generated; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index 14489777dd4..df9c4376871 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java @@ -95,6 +95,15 @@ IntersectionVertex getVertexForOsmNode(OSMNode node, OSMWithTags way) { iv = bv; } + if (node.isSubwayEntrance()) { + String ref = node.getTag("ref"); + if (ref != null) { + iv = vertexFactory.stationEntrance(nid, coordinate, ref); + } else { + iv = vertexFactory.stationEntrance(nid, coordinate, "MAIN_ENTRANCE"); + } + } + if (iv == null) { iv = vertexFactory.osm( diff --git a/src/main/java/org/opentripplanner/model/plan/WalkStep.java b/src/main/java/org/opentripplanner/model/plan/WalkStep.java index 13249d5da52..9a650ecef3e 100644 --- a/src/main/java/org/opentripplanner/model/plan/WalkStep.java +++ b/src/main/java/org/opentripplanner/model/plan/WalkStep.java @@ -44,6 +44,7 @@ public final class WalkStep { private final boolean walkingBike; private final String exit; + private final String entrance; private final ElevationProfile elevationProfile; private final boolean stayOn; @@ -56,6 +57,7 @@ public final class WalkStep { I18NString directionText, Set streetNotes, String exit, + String entrance, ElevationProfile elevationProfile, boolean bogusName, boolean walkingBike, @@ -76,6 +78,7 @@ public final class WalkStep { this.walkingBike = walkingBike; this.area = area; this.exit = exit; + this.entrance = entrance; this.elevationProfile = elevationProfile; this.stayOn = stayOn; this.edges = List.copyOf(Objects.requireNonNull(edges)); @@ -130,6 +133,13 @@ public String getExit() { return exit; } + /** + * When entering or exiting a public transport station, the entrance name + */ + public String getEntrance() { + return entrance; + } + /** * Indicates whether a street changes direction at an intersection. */ diff --git a/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java b/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java index 25c6ee25b6b..8b5d2cedb11 100644 --- a/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java +++ b/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java @@ -25,6 +25,7 @@ public class WalkStepBuilder { private RelativeDirection relativeDirection; private ElevationProfile elevationProfile; private String exit; + private String entrance; private boolean stayOn = false; /** * Distance used for appending elevation profiles @@ -74,6 +75,11 @@ public WalkStepBuilder withExit(String exit) { return this; } + public WalkStepBuilder withEntrance(String entrance) { + this.entrance = entrance; + return this; + } + public WalkStepBuilder withStayOn(boolean stayOn) { this.stayOn = stayOn; return this; @@ -156,6 +162,7 @@ public WalkStep build() { directionText, streetNotes, exit, + entrance, elevationProfile, bogusName, walkingBike, diff --git a/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java b/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java index d181cde4564..371751d5e4a 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java +++ b/src/main/java/org/opentripplanner/openstreetmap/model/OSMNode.java @@ -63,6 +63,15 @@ public boolean isBarrier() { ); } + /** + * Checks if this node is an subway station entrance + * + * @return true if it does + */ + 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/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java index 94905bb840a..d49755bb548 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java @@ -26,6 +26,7 @@ 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; @@ -258,6 +259,8 @@ private void processState(State backState, State forwardState) { setMotorwayExit(backState); + setStationEntrance(backState); + if (createdNewStep && !modeTransition) { // check last three steps for zag int lastIndex = steps.size() - 1; @@ -380,6 +383,21 @@ private void setMotorwayExit(State backState) { } } + /** + * Update the walk step with the name of the station entrance if set from OSM + */ + private void setStationEntrance(State backState) { + State entranceState = backState; + Edge entranceEdge = entranceState.getBackEdge(); + while (entranceEdge instanceof FreeEdge) { + entranceState = entranceState.getBackState(); + entranceEdge = entranceState.getBackEdge(); + } + if (entranceState.getVertex() instanceof StationEntranceVertex) { + current.withEntrance(((StationEntranceVertex) entranceState.getVertex()).getEntranceName()); + } + } + /** * Is it possible to turn to another street from this previous state */ diff --git a/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java b/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java new file mode 100644 index 00000000000..af1e824a7bd --- /dev/null +++ b/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java @@ -0,0 +1,19 @@ +package org.opentripplanner.street.model.vertex; + +public class StationEntranceVertex extends OsmVertex { + + private final String entranceName; + + public StationEntranceVertex(double x, double y, long nodeId, String entranceName) { + super(x, y, nodeId); + this.entranceName = entranceName; + } + + public String getEntranceName() { + return entranceName; + } + + public String toString() { + return "StationEntranceVertex(" + super.toString() + ")"; + } +} diff --git a/src/main/java/org/opentripplanner/street/model/vertex/VertexFactory.java b/src/main/java/org/opentripplanner/street/model/vertex/VertexFactory.java index c7e38ca0032..3fe201070d4 100644 --- a/src/main/java/org/opentripplanner/street/model/vertex/VertexFactory.java +++ b/src/main/java/org/opentripplanner/street/model/vertex/VertexFactory.java @@ -103,6 +103,15 @@ public ExitVertex exit(long nid, Coordinate coordinate, String exitName) { return addToGraph(new ExitVertex(coordinate.x, coordinate.y, nid, exitName)); } + @Nonnull + public StationEntranceVertex stationEntrance( + long nid, + Coordinate coordinate, + String entranceName + ) { + return addToGraph(new StationEntranceVertex(coordinate.x, coordinate.y, nid, entranceName)); + } + @Nonnull public OsmVertex osm( Coordinate coordinate, diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 927af19f8b1..12decc1e3d5 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2628,6 +2628,8 @@ type step { elevationProfile: [elevationProfileComponent] "When exiting a highway or traffic circle, the exit name/number." exit: String + "Name of entrance to a public transport station" + entrance: String "The latitude of the start of the step." lat: Float "The longitude of the start of the step." From 67f4b1b54ef64b40c0853bfd8e5d10a172534e72 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Wed, 2 Oct 2024 16:03:23 +0300 Subject: [PATCH 002/195] Add entity to walk steps --- .../apis/gtfs/GtfsGraphQLIndex.java | 2 ++ .../apis/gtfs/datafetchers/stepImpl.java | 4 ++-- .../gtfs/generated/GraphQLDataFetchers.java | 10 +++++++++- .../apis/gtfs/model/StepEntity.java | 19 +++++++++++++++++++ .../opentripplanner/model/plan/WalkStep.java | 13 +++++++------ .../model/plan/WalkStepBuilder.java | 10 ++++++---- .../mapping/StatesToWalkStepsMapper.java | 5 ++++- .../opentripplanner/apis/gtfs/schema.graphqls | 13 +++++++++++-- 8 files changed, 60 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 43a8399e70c..ed3242b5823 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -81,6 +81,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.serviceTimeRangeImpl; import org.opentripplanner.apis.gtfs.datafetchers.stepImpl; import org.opentripplanner.apis.gtfs.datafetchers.stopAtDistanceImpl; +import org.opentripplanner.apis.gtfs.model.StepEntity; import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.apis.support.graphql.LoggingDataFetcherExceptionHandler; import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation; @@ -124,6 +125,7 @@ protected static GraphQLSchema buildSchema() { .type("Node", type -> type.typeResolver(new NodeTypeResolver())) .type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver())) .type("StopPosition", type -> type.typeResolver(new StopPosition() {})) + .type("StepEntity", type -> type.typeResolver(new StepEntity() {})) .type("FareProduct", type -> type.typeResolver(new FareProductTypeResolver())) .type("AlertEntity", type -> type.typeResolver(new AlertEntityTypeResolver())) .type(typeWiring.build(AgencyImpl.class)) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java index d79e224e51e..658f2d321ad 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java @@ -54,8 +54,8 @@ public DataFetcher exit() { } @Override - public DataFetcher entrance() { - return environment -> getSource(environment).getEntrance(); + public DataFetcher entity() { + return environment -> getSource(environment).getEntity(); } @Override diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 3c162b14112..efc59f72d42 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -365,6 +365,11 @@ public interface GraphQLEmissions { public DataFetcher co2(); } + /** Station entrance/exit */ + public interface GraphQLEntrance { + public DataFetcher name(); + } + /** A 'medium' that a fare product applies to, for example cash, 'Oyster Card' or 'DB Navigator App'. */ public interface GraphQLFareMedium { public DataFetcher id(); @@ -977,6 +982,9 @@ public interface GraphQLRoutingError { public DataFetcher inputField(); } + /** Entity to a step */ + public interface GraphQLStepEntity extends TypeResolver {} + /** * Stop can represent either a single public transport stop, where passengers can * board and/or disembark vehicles, or a station, which contains multiple stops. @@ -1428,7 +1436,7 @@ public interface GraphQLStep { public DataFetcher> elevationProfile(); - public DataFetcher entrance(); + public DataFetcher entity(); public DataFetcher exit(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java b/src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java new file mode 100644 index 00000000000..5e10f4e08a6 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java @@ -0,0 +1,19 @@ +package org.opentripplanner.apis.gtfs.model; + +import graphql.TypeResolutionEnvironment; +import graphql.schema.GraphQLObjectType; +import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; + +public interface StepEntity extends GraphQLDataFetchers.GraphQLStepEntity { + record Entrance(String name) implements StepEntity {} + + @Override + default GraphQLObjectType getType(TypeResolutionEnvironment env) { + var schema = env.getSchema(); + Object o = env.getObject(); + if (o instanceof Entrance) { + return schema.getObjectType("Entrance"); + } + return null; + } +} diff --git a/src/main/java/org/opentripplanner/model/plan/WalkStep.java b/src/main/java/org/opentripplanner/model/plan/WalkStep.java index 9a650ecef3e..3e9e9fe14dc 100644 --- a/src/main/java/org/opentripplanner/model/plan/WalkStep.java +++ b/src/main/java/org/opentripplanner/model/plan/WalkStep.java @@ -4,6 +4,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import org.opentripplanner.apis.gtfs.model.StepEntity; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.lang.DoubleUtils; @@ -44,7 +45,7 @@ public final class WalkStep { private final boolean walkingBike; private final String exit; - private final String entrance; + private final StepEntity entity; private final ElevationProfile elevationProfile; private final boolean stayOn; @@ -57,7 +58,7 @@ public final class WalkStep { I18NString directionText, Set streetNotes, String exit, - String entrance, + StepEntity entity, ElevationProfile elevationProfile, boolean bogusName, boolean walkingBike, @@ -78,7 +79,7 @@ public final class WalkStep { this.walkingBike = walkingBike; this.area = area; this.exit = exit; - this.entrance = entrance; + this.entity = entity; this.elevationProfile = elevationProfile; this.stayOn = stayOn; this.edges = List.copyOf(Objects.requireNonNull(edges)); @@ -134,10 +135,10 @@ public String getExit() { } /** - * When entering or exiting a public transport station, the entrance name + * Entity related to a step e.g. building entrance/exit. */ - public String getEntrance() { - return entrance; + public Object getEntity() { + return entity; } /** diff --git a/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java b/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java index 8b5d2cedb11..d020f8d0113 100644 --- a/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java +++ b/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java @@ -5,6 +5,8 @@ import java.util.List; import java.util.Set; import javax.annotation.Nullable; +import org.opentripplanner.apis.gtfs.model.StepEntity; +import org.opentripplanner.apis.gtfs.model.StepEntity.Entrance; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.lang.DoubleUtils; @@ -25,7 +27,7 @@ public class WalkStepBuilder { private RelativeDirection relativeDirection; private ElevationProfile elevationProfile; private String exit; - private String entrance; + private StepEntity entity; private boolean stayOn = false; /** * Distance used for appending elevation profiles @@ -75,8 +77,8 @@ public WalkStepBuilder withExit(String exit) { return this; } - public WalkStepBuilder withEntrance(String entrance) { - this.entrance = entrance; + public WalkStepBuilder withEntrance(Entrance entrance) { + this.entity = entrance; return this; } @@ -162,7 +164,7 @@ public WalkStep build() { directionText, streetNotes, exit, - entrance, + entity, elevationProfile, bogusName, walkingBike, diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java index d49755bb548..7e0ff3696ca 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java @@ -10,6 +10,7 @@ import javax.annotation.Nonnull; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; +import org.opentripplanner.apis.gtfs.model.StepEntity.Entrance; import org.opentripplanner.framework.geometry.DirectionUtils; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; @@ -394,7 +395,9 @@ private void setStationEntrance(State backState) { entranceEdge = entranceState.getBackEdge(); } if (entranceState.getVertex() instanceof StationEntranceVertex) { - current.withEntrance(((StationEntranceVertex) entranceState.getVertex()).getEntranceName()); + current.withEntrance( + new Entrance(((StationEntranceVertex) entranceState.getVertex()).getEntranceName()) + ); } } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 12decc1e3d5..b9b3fddb6e4 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2607,6 +2607,15 @@ type serviceTimeRange { start: Long } +"Station entrance/exit" +type Entrance { + "Name of a station entrance/exit" + name: String +} + +"Entity to a step" +union StepEntity = Entrance + type step { "The cardinal (compass) direction (e.g. north, northeast) taken when engaging this step." absoluteDirection: AbsoluteDirection @@ -2628,8 +2637,6 @@ type step { elevationProfile: [elevationProfileComponent] "When exiting a highway or traffic circle, the exit name/number." exit: String - "Name of entrance to a public transport station" - entrance: String "The latitude of the start of the step." lat: Float "The longitude of the start of the step." @@ -2642,6 +2649,8 @@ type step { streetName: String "Is this step walking with a bike?" walkingBike: Boolean + "Step entity e.g. an entrance" + entity: StepEntity } type stopAtDistance implements Node { From 9d18269db5311d5802b79e0410ece8fd24e6583b Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Thu, 3 Oct 2024 14:52:42 +0300 Subject: [PATCH 003/195] Add more parameters to Entrance --- .../apis/gtfs/generated/GraphQLDataFetchers.java | 4 ++++ .../org/opentripplanner/apis/gtfs/model/StepEntity.java | 6 +++++- .../routing/algorithm/mapping/StatesToWalkStepsMapper.java | 6 ++++-- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 6 +++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index efc59f72d42..dfe2527715f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -367,6 +367,10 @@ public interface GraphQLEmissions { /** Station entrance/exit */ public interface GraphQLEntrance { + public DataFetcher code(); + + public DataFetcher gtfsId(); + public DataFetcher name(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java b/src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java index 5e10f4e08a6..e1f53cf6842 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java @@ -5,7 +5,11 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; public interface StepEntity extends GraphQLDataFetchers.GraphQLStepEntity { - record Entrance(String name) implements StepEntity {} + record Entrance(String code, String name, String gtfsId) implements StepEntity { + public static Entrance withCode(String code) { + return new Entrance(code, null, null); + } + } @Override default GraphQLObjectType getType(TypeResolutionEnvironment env) { diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java index 7e0ff3696ca..eeb091125aa 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java @@ -10,7 +10,7 @@ import javax.annotation.Nonnull; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; -import org.opentripplanner.apis.gtfs.model.StepEntity.Entrance; +import org.opentripplanner.apis.gtfs.model.StepEntity; import org.opentripplanner.framework.geometry.DirectionUtils; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; @@ -396,7 +396,9 @@ private void setStationEntrance(State backState) { } if (entranceState.getVertex() instanceof StationEntranceVertex) { current.withEntrance( - new Entrance(((StationEntranceVertex) entranceState.getVertex()).getEntranceName()) + StepEntity.Entrance.withCode( + ((StationEntranceVertex) entranceState.getVertex()).getEntranceName() + ) ); } } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index b9b3fddb6e4..9fef54c3965 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2609,8 +2609,12 @@ type serviceTimeRange { "Station entrance/exit" type Entrance { - "Name of a station entrance/exit" + "Code of entrance/exit eg A or B" + code: String + "Name of entrance/exit" name: String + "Gtfs id of entrance/exit" + gtfsId: String } "Entity to a step" From 3b8828831c3e0409fbe3da49a7d238fa0888a884 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Mon, 7 Oct 2024 17:22:54 +0300 Subject: [PATCH 004/195] Move StepEntity classes --- .../apis/gtfs/GtfsGraphQLIndex.java | 4 +-- .../datafetchers/StepEntityTypeResolver.java | 30 +++++++++++++++++++ .../apis/gtfs/model/StepEntity.java | 23 -------------- .../opentripplanner/model/plan/Entrance.java | 18 +++++++++++ .../model/plan/StepEntity.java | 3 ++ .../opentripplanner/model/plan/WalkStep.java | 2 +- .../model/plan/WalkStepBuilder.java | 4 +-- .../mapping/StatesToWalkStepsMapper.java | 6 ++-- 8 files changed, 58 insertions(+), 32 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java delete mode 100644 src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java create mode 100644 src/main/java/org/opentripplanner/model/plan/Entrance.java create mode 100644 src/main/java/org/opentripplanner/model/plan/StepEntity.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index ed3242b5823..5b288762262 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -58,6 +58,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.RouteImpl; import org.opentripplanner.apis.gtfs.datafetchers.RouteTypeImpl; import org.opentripplanner.apis.gtfs.datafetchers.RoutingErrorImpl; +import org.opentripplanner.apis.gtfs.datafetchers.StepEntityTypeResolver; import org.opentripplanner.apis.gtfs.datafetchers.StopGeometriesImpl; import org.opentripplanner.apis.gtfs.datafetchers.StopImpl; import org.opentripplanner.apis.gtfs.datafetchers.StopOnRouteImpl; @@ -81,7 +82,6 @@ import org.opentripplanner.apis.gtfs.datafetchers.serviceTimeRangeImpl; import org.opentripplanner.apis.gtfs.datafetchers.stepImpl; import org.opentripplanner.apis.gtfs.datafetchers.stopAtDistanceImpl; -import org.opentripplanner.apis.gtfs.model.StepEntity; import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.apis.support.graphql.LoggingDataFetcherExceptionHandler; import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation; @@ -125,7 +125,7 @@ protected static GraphQLSchema buildSchema() { .type("Node", type -> type.typeResolver(new NodeTypeResolver())) .type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver())) .type("StopPosition", type -> type.typeResolver(new StopPosition() {})) - .type("StepEntity", type -> type.typeResolver(new StepEntity() {})) + .type("StepEntity", type -> type.typeResolver(new StepEntityTypeResolver())) .type("FareProduct", type -> type.typeResolver(new FareProductTypeResolver())) .type("AlertEntity", type -> type.typeResolver(new AlertEntityTypeResolver())) .type(typeWiring.build(AgencyImpl.class)) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java new file mode 100644 index 00000000000..5fc8a123226 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java @@ -0,0 +1,30 @@ +package org.opentripplanner.apis.gtfs.datafetchers; + +import graphql.TypeResolutionEnvironment; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLSchema; +import graphql.schema.TypeResolver; +import org.opentripplanner.apis.gtfs.model.RouteTypeModel; +import org.opentripplanner.apis.gtfs.model.StopOnRouteModel; +import org.opentripplanner.apis.gtfs.model.StopOnTripModel; +import org.opentripplanner.apis.gtfs.model.UnknownModel; +import org.opentripplanner.model.plan.Entrance; +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.RegularStop; +import org.opentripplanner.transit.model.timetable.Trip; + +public class StepEntityTypeResolver implements TypeResolver { + + @Override + public GraphQLObjectType getType(TypeResolutionEnvironment environment) { + Object o = environment.getObject(); + GraphQLSchema schema = environment.getSchema(); + + if (o instanceof Entrance) { + return schema.getObjectType("Entrance"); + } + return null; + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java b/src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java deleted file mode 100644 index e1f53cf6842..00000000000 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/StepEntity.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.opentripplanner.apis.gtfs.model; - -import graphql.TypeResolutionEnvironment; -import graphql.schema.GraphQLObjectType; -import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; - -public interface StepEntity extends GraphQLDataFetchers.GraphQLStepEntity { - record Entrance(String code, String name, String gtfsId) implements StepEntity { - public static Entrance withCode(String code) { - return new Entrance(code, null, null); - } - } - - @Override - default GraphQLObjectType getType(TypeResolutionEnvironment env) { - var schema = env.getSchema(); - Object o = env.getObject(); - if (o instanceof Entrance) { - return schema.getObjectType("Entrance"); - } - return null; - } -} diff --git a/src/main/java/org/opentripplanner/model/plan/Entrance.java b/src/main/java/org/opentripplanner/model/plan/Entrance.java new file mode 100644 index 00000000000..7b28ee992a4 --- /dev/null +++ b/src/main/java/org/opentripplanner/model/plan/Entrance.java @@ -0,0 +1,18 @@ +package org.opentripplanner.model.plan; + +public final class Entrance extends StepEntity { + + private final String code; + private final String gtfsId; + private final String name; + + public Entrance(String code, String gtfsId, String name) { + this.code = code; + this.gtfsId = gtfsId; + this.name = name; + } + + public static Entrance withCode(String code) { + return new Entrance(code, null, null); + } +} diff --git a/src/main/java/org/opentripplanner/model/plan/StepEntity.java b/src/main/java/org/opentripplanner/model/plan/StepEntity.java new file mode 100644 index 00000000000..e6bfd587bfc --- /dev/null +++ b/src/main/java/org/opentripplanner/model/plan/StepEntity.java @@ -0,0 +1,3 @@ +package org.opentripplanner.model.plan; + +public abstract class StepEntity {} diff --git a/src/main/java/org/opentripplanner/model/plan/WalkStep.java b/src/main/java/org/opentripplanner/model/plan/WalkStep.java index 3e9e9fe14dc..8114246d7e8 100644 --- a/src/main/java/org/opentripplanner/model/plan/WalkStep.java +++ b/src/main/java/org/opentripplanner/model/plan/WalkStep.java @@ -4,11 +4,11 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import org.opentripplanner.apis.gtfs.model.StepEntity; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.lang.DoubleUtils; import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.model.plan.StepEntity; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.note.StreetNote; diff --git a/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java b/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java index d020f8d0113..02b73c0ce15 100644 --- a/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java +++ b/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java @@ -5,12 +5,12 @@ import java.util.List; import java.util.Set; import javax.annotation.Nullable; -import org.opentripplanner.apis.gtfs.model.StepEntity; -import org.opentripplanner.apis.gtfs.model.StepEntity.Entrance; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.lang.DoubleUtils; import org.opentripplanner.framework.lang.IntUtils; +import org.opentripplanner.model.plan.Entrance; +import org.opentripplanner.model.plan.StepEntity; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.note.StreetNote; diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java index eeb091125aa..158979f5d91 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java @@ -10,11 +10,11 @@ import javax.annotation.Nonnull; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; -import org.opentripplanner.apis.gtfs.model.StepEntity; import org.opentripplanner.framework.geometry.DirectionUtils; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.plan.ElevationProfile; +import org.opentripplanner.model.plan.Entrance; import org.opentripplanner.model.plan.RelativeDirection; import org.opentripplanner.model.plan.WalkStep; import org.opentripplanner.model.plan.WalkStepBuilder; @@ -396,9 +396,7 @@ private void setStationEntrance(State backState) { } if (entranceState.getVertex() instanceof StationEntranceVertex) { current.withEntrance( - StepEntity.Entrance.withCode( - ((StationEntranceVertex) entranceState.getVertex()).getEntranceName() - ) + Entrance.withCode(((StationEntranceVertex) entranceState.getVertex()).getEntranceName()) ); } } From 0a62bf81363030d990ed9e0e5e596a479c3a5644 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Wed, 16 Oct 2024 18:19:55 +0300 Subject: [PATCH 005/195] Remove default name for subway station entrances --- .../graph_builder/module/osm/VertexGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index df9c4376871..633d4343b83 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java @@ -100,7 +100,7 @@ IntersectionVertex getVertexForOsmNode(OSMNode node, OSMWithTags way) { if (ref != null) { iv = vertexFactory.stationEntrance(nid, coordinate, ref); } else { - iv = vertexFactory.stationEntrance(nid, coordinate, "MAIN_ENTRANCE"); + iv = vertexFactory.stationEntrance(nid, coordinate, null); } } From 528ab55927be6269d8dee5bfec52e1b0dcbf999d Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Fri, 18 Oct 2024 17:39:43 +0300 Subject: [PATCH 006/195] Add option to turn on osm subway entrances in osmDefaults --- doc/user/BuildConfiguration.md | 2 ++ .../module/configure/GraphBuilderModules.java | 2 ++ .../graph_builder/module/osm/OsmModule.java | 8 ++++++- .../module/osm/OsmModuleBuilder.java | 9 +++++++- .../module/osm/VertexGenerator.java | 11 ++++++++-- .../osm/parameters/OsmExtractParameters.java | 21 +++++++++++++++++-- .../OsmExtractParametersBuilder.java | 15 +++++++++++++ .../parameters/OsmProcessingParameters.java | 4 +++- .../openstreetmap/OsmProvider.java | 9 ++++++++ .../config/buildconfig/OsmConfig.java | 8 +++++++ .../module/osm/WalkableAreaBuilderTest.java | 2 +- 11 files changed, 83 insertions(+), 8 deletions(-) diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index b311991120e..7af5bbad1c5 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 in the OSM data. Overrides the value specified in `osmDefaults`. | *Optional* | `false` | 2.2 | |       [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 in the OSM data. | *Optional* | `false` | 2.2 | |    [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/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java index 10d3a997579..169ac351c87 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/configure/GraphBuilderModules.java @@ -68,6 +68,7 @@ static OsmModule provideOpenStreetMapModule( osmConfiguredDataSource.dataSource(), osmConfiguredDataSource.config().osmTagMapper(), osmConfiguredDataSource.config().timeZone(), + osmConfiguredDataSource.config().includeOsmSubwayEntrances(), config.osmCacheDataInMem, issueStore ) @@ -83,6 +84,7 @@ static OsmModule provideOpenStreetMapModule( .withStaticBikeParkAndRide(config.staticBikeParkAndRide) .withMaxAreaNodes(config.maxAreaNodes) .withBoardingAreaRefTags(config.boardingLocationTags) + .withIncludeOsmSubwayEntrances(config.osmDefaults.includeOsmSubwayEntrances()) .withIssueStore(issueStore) .withStreetLimitationParameters(streetLimitationParameters) .build(); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java index 10c215ee448..cbdd3d7a7a2 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModule.java @@ -71,7 +71,13 @@ public class OsmModule implements GraphBuilderModule { 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); } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java index f0a40fa678f..29e8f8a1ae5 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmModuleBuilder.java @@ -24,6 +24,7 @@ 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(); @@ -72,6 +73,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; @@ -90,7 +96,8 @@ public OsmModule build() { areaVisibility, platformEntriesLinking, staticParkAndRide, - staticBikeParkAndRide + staticBikeParkAndRide, + includeOsmSubwayEntrances ) ); } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index 633d4343b83..87cd88a5227 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java +++ b/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,7 +102,7 @@ IntersectionVertex getVertexForOsmNode(OSMNode node, OSMWithTags way) { iv = bv; } - if (node.isSubwayEntrance()) { + if (includeOsmSubwayEntrances && node.isSubwayEntrance()) { String ref = node.getTag("ref"); if (ref != null) { iv = vertexFactory.stationEntrance(nid, coordinate, ref); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java index 9d2eead5f7e..1cae389d9c4 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParameters.java +++ b/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.getIncludeOsmSubwayEntrances() + ); } @Override @@ -37,6 +49,11 @@ public ZoneId timeZone() { return timeZone; } + @Nullable + public boolean includeOsmSubwayEntrances() { + return includeOsmSubwayEntrances; + } + public OsmExtractParametersBuilder copyOf() { return new OsmExtractParametersBuilder(this); } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java index 03fd7eaec4e..0bbc184569d 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmExtractParametersBuilder.java +++ b/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 getIncludeOsmSubwayEntrances() { + return includeOsmSubwayEntrances; + } + public OsmExtractParameters build() { return new OsmExtractParameters(this); } diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java index 52bf8d65314..a3fd14020e8 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/osm/parameters/OsmProcessingParameters.java +++ b/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/src/main/java/org/opentripplanner/openstreetmap/OsmProvider.java b/src/main/java/org/opentripplanner/openstreetmap/OsmProvider.java index e35a846cbbd..d66f5db394b 100644 --- a/src/main/java/org/opentripplanner/openstreetmap/OsmProvider.java +++ b/src/main/java/org/opentripplanner/openstreetmap/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/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java b/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java index 1b2ec0ed74d..c1a3963c6a5 100644 --- a/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/buildconfig/OsmConfig.java @@ -84,6 +84,14 @@ public static OsmExtractParametersBuilder mapOsmGenericParameters( ) .docDefaultValue(docDefaults.timeZone()) .asZoneId(defaults.timeZone()) + ) + .withIncludeOsmSubwayEntrances( + node + .of("includeOsmSubwayEntrances") + .since(V2_2) + .summary("Whether to include subway entrances from the OSM data." + documentationAddition) + .docDefaultValue(docDefaults.includeOsmSubwayEntrances()) + .asBoolean(defaults.includeOsmSubwayEntrances()) ); } } diff --git a/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java b/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java index 4906bce930d..26500299062 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilderTest.java +++ b/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, From 401a405cd9000a5f1ebb92bb9ba130f8a6ff0fcd Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Mon, 21 Oct 2024 18:41:18 +0300 Subject: [PATCH 007/195] Fix walk step generation --- .../mapping/StatesToWalkStepsMapper.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) 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 9202c94c76a..6bcef1b0da9 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 @@ -177,6 +177,9 @@ private void processState(State backState, State forwardState) { if (edge instanceof ElevatorAlightEdge) { addStep(createElevatorWalkStep(backState, forwardState, edge)); return; + } else if (backState.getVertex() instanceof StationEntranceVertex) { + addStep(createStationEntranceWalkStep(backState, forwardState, edge)); + return; } else if (edge instanceof PathwayEdge pwe && pwe.signpostedAs().isPresent()) { createAndSaveStep(backState, forwardState, pwe.signpostedAs().get(), FOLLOW_SIGNS, edge); return; @@ -259,8 +262,6 @@ private void processState(State backState, State forwardState) { setMotorwayExit(backState); - setStationEntrance(backState); - if (createdNewStep && !modeTransition) { // check last three steps for zag int lastIndex = steps.size() - 1; @@ -382,23 +383,6 @@ private void setMotorwayExit(State backState) { } } - /** - * Update the walk step with the name of the station entrance if set from OSM - */ - private void setStationEntrance(State backState) { - State entranceState = backState; - Edge entranceEdge = entranceState.getBackEdge(); - while (entranceEdge instanceof FreeEdge) { - entranceState = entranceState.getBackState(); - entranceEdge = entranceState.getBackEdge(); - } - if (entranceState.getVertex() instanceof StationEntranceVertex) { - current.withEntrance( - Entrance.withCode(((StationEntranceVertex) entranceState.getVertex()).getEntranceName()) - ); - } - } - /** * Is it possible to turn to another street from this previous state */ @@ -536,6 +520,22 @@ private WalkStepBuilder createElevatorWalkStep(State backState, State forwardSta return step; } + private WalkStepBuilder createStationEntranceWalkStep( + State backState, + State forwardState, + Edge edge + ) { + // don't care what came before or comes after + var step = createWalkStep(forwardState, backState); + + step.withRelativeDirection(RelativeDirection.CONTINUE); + + step.withEntrance( + Entrance.withCode(((StationEntranceVertex) backState.getVertex()).getEntranceName()) + ); + return step; + } + private void createAndSaveStep( State backState, State forwardState, From 438bc318faa6ef96c1ef78f997005b631910c6fd Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Wed, 23 Oct 2024 20:14:01 +0300 Subject: [PATCH 008/195] Add step entity to graphql tests --- .../apis/gtfs/GraphQLIntegrationTest.java | 8 +++++++- .../apis/gtfs/expectations/walk-steps.json | 17 +++++++++++++++-- .../apis/gtfs/queries/walk-steps.graphql | 6 ++++++ 3 files changed, 28 insertions(+), 3 deletions(-) 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 663ce2d5f3f..020a1ec6113 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -49,6 +49,7 @@ import org.opentripplanner.model.fare.ItineraryFares; import org.opentripplanner.model.fare.RiderCategory; import org.opentripplanner.model.plan.Emissions; +import org.opentripplanner.model.plan.Entrance; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.RelativeDirection; @@ -232,8 +233,13 @@ public Set getRoutesForStop(StopLocation stop) { .build(); var step2 = walkStep("elevator").withRelativeDirection(RelativeDirection.ELEVATOR).build(); + var step3 = walkStep("entrance") + .withRelativeDirection(RelativeDirection.CONTINUE) + .withEntrance(Entrance.withCode("A")) + .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/resources/org/opentripplanner/apis/gtfs/expectations/walk-steps.json b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/walk-steps.json index be584a875be..b4172c0f70f 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,26 @@ "streetName" : "street", "area" : false, "relativeDirection" : "DEPART", - "absoluteDirection" : "NORTHEAST" + "absoluteDirection" : "NORTHEAST", + "entity" : null }, { "streetName" : "elevator", "area" : false, "relativeDirection" : "ELEVATOR", - "absoluteDirection" : null + "absoluteDirection" : null, + "entity" : null + + }, + { + "streetName" : "entrance", + "area" : false, + "relativeDirection" : "CONTINUE", + "absoluteDirection" : null, + "entity" : { + "__typename" : "Entrance", + "code": "A" + } } ] }, 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..74cbe0c599e 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,12 @@ area relativeDirection absoluteDirection + entity { + __typename + ... on Entrance { + code + } + } } } } From 97c2de6ac34d7315ffb07555f011b4aeacd52cfa Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Fri, 25 Oct 2024 15:15:02 +0300 Subject: [PATCH 009/195] Rename variables to match with graphql --- .../algorithm/mapping/StatesToWalkStepsMapper.java | 6 ++---- .../street/model/vertex/StationEntranceVertex.java | 12 ++++++------ 2 files changed, 8 insertions(+), 10 deletions(-) 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 6bcef1b0da9..8a289713532 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 @@ -529,10 +529,8 @@ private WalkStepBuilder createStationEntranceWalkStep( var step = createWalkStep(forwardState, backState); step.withRelativeDirection(RelativeDirection.CONTINUE); - - step.withEntrance( - Entrance.withCode(((StationEntranceVertex) backState.getVertex()).getEntranceName()) - ); + System.out.println(backState.getVertex().toString()); + step.withEntrance(Entrance.withCode(((StationEntranceVertex) backState.getVertex()).getCode())); return step; } 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 index af1e824a7bd..083c0d52a0f 100644 --- a/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java +++ b/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java @@ -2,18 +2,18 @@ public class StationEntranceVertex extends OsmVertex { - private final String entranceName; + private final String code; - public StationEntranceVertex(double x, double y, long nodeId, String entranceName) { + public StationEntranceVertex(double x, double y, long nodeId, String code) { super(x, y, nodeId); - this.entranceName = entranceName; + this.code = code; } - public String getEntranceName() { - return entranceName; + public String getCode() { + return code; } public String toString() { - return "StationEntranceVertex(" + super.toString() + ")"; + return "StationEntranceVertex(" + super.toString() + ", code=" + code + ")"; } } From d844561b9d4a5f98d11a4f8eaabe63fe1b461a42 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Fri, 25 Oct 2024 16:10:14 +0300 Subject: [PATCH 010/195] Rename variables --- .../street/model/vertex/VertexFactory.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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 007d3119a55..61f74841245 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 @@ -94,12 +94,8 @@ 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 entranceName - ) { - return addToGraph(new StationEntranceVertex(coordinate.x, coordinate.y, nid, entranceName)); + public StationEntranceVertex stationEntrance(long nid, Coordinate coordinate, String code) { + return addToGraph(new StationEntranceVertex(coordinate.x, coordinate.y, nid, code)); } public OsmVertex osm( From 02a07ea2ed91ade71e5d4e5384a5e93fcf31ea67 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Sun, 27 Oct 2024 12:39:45 +0200 Subject: [PATCH 011/195] Rename function --- .../module/osm/parameters/OsmExtractParameters.java | 2 +- .../module/osm/parameters/OsmExtractParametersBuilder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 6e8ff0f793c..37edaf687ab 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 @@ -31,7 +31,7 @@ public record OsmExtractParameters( builder.getSource(), builder.getOsmTagMapper(), builder.getTimeZone(), - builder.getIncludeOsmSubwayEntrances() + builder.includeOsmSubwayEntrances() ); } 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 9abc6e6bcc7..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 @@ -72,7 +72,7 @@ public ZoneId getTimeZone() { return timeZone; } - public boolean getIncludeOsmSubwayEntrances() { + public boolean includeOsmSubwayEntrances() { return includeOsmSubwayEntrances; } From 5dc74dd48555935dcd1deddcf39de0d4b73eaac0 Mon Sep 17 00:00:00 2001 From: Henrik Sundell <47221103+HenrikSundell@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:05:39 +0200 Subject: [PATCH 012/195] Fix comments Co-authored-by: Joel Lappalainen --- .../java/org/opentripplanner/osm/model/OsmNode.java | 4 ++-- .../org/opentripplanner/apis/gtfs/schema.graphqls | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) 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 2c343401adf..cb9fcd679f0 100644 --- a/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java +++ b/application/src/main/java/org/opentripplanner/osm/model/OsmNode.java @@ -64,9 +64,9 @@ public boolean isBarrier() { } /** - * Checks if this node is an subway station entrance + * Checks if this node is a subway station entrance. * - * @return true if it does + * @return true if it is */ public boolean isSubwayEntrance() { return hasTag("railway") && "subway_entrance".equals(getTag("railway")); 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 9c4d0e4dab1..b2d48f7e0b1 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -447,13 +447,13 @@ type Emissions { co2: Grams } -"Station entrance/exit" +"Station entrance or exit." type Entrance { - "Code of entrance/exit eg A or B" + "Short text or a number that identifies the entrance or exit for passengers. For example, `A` or `B`." code: String - "Gtfs id of entrance/exit" + "ID of the entrance in the format of `FeedId:EntranceId`." gtfsId: String - "Name of entrance/exit" + "Name of the entrance or exit." name: String } @@ -2658,7 +2658,7 @@ type step { distance: Float "The elevation profile as a list of { distance, elevation } values." elevationProfile: [elevationProfileComponent] - "Step entity e.g. an entrance" + "Step entity, e.g. an entrance." entity: StepEntity "When exiting a highway or traffic circle, the exit name/number." exit: String From 5d55646ab1861c7553d7a7b3b9f91a5d328bbd59 Mon Sep 17 00:00:00 2001 From: Henrik Sundell <47221103+HenrikSundell@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:06:35 +0200 Subject: [PATCH 013/195] Remove println Co-authored-by: Joel Lappalainen --- .../routing/algorithm/mapping/StatesToWalkStepsMapper.java | 1 - 1 file changed, 1 deletion(-) 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 8a289713532..f04a6a5a2d9 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 @@ -529,7 +529,6 @@ private WalkStepBuilder createStationEntranceWalkStep( var step = createWalkStep(forwardState, backState); step.withRelativeDirection(RelativeDirection.CONTINUE); - System.out.println(backState.getVertex().toString()); step.withEntrance(Entrance.withCode(((StationEntranceVertex) backState.getVertex()).getCode())); return step; } From d1067c6679a68ee252f8bda96300d28dee8cfe75 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Mon, 28 Oct 2024 16:06:52 +0200 Subject: [PATCH 014/195] Remove unnecessary imports --- .../apis/gtfs/datafetchers/StepEntityTypeResolver.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java index 5fc8a123226..5e7d6098344 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java @@ -4,16 +4,7 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import graphql.schema.TypeResolver; -import org.opentripplanner.apis.gtfs.model.RouteTypeModel; -import org.opentripplanner.apis.gtfs.model.StopOnRouteModel; -import org.opentripplanner.apis.gtfs.model.StopOnTripModel; -import org.opentripplanner.apis.gtfs.model.UnknownModel; import org.opentripplanner.model.plan.Entrance; -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.RegularStop; -import org.opentripplanner.transit.model.timetable.Trip; public class StepEntityTypeResolver implements TypeResolver { From 5c97ad17f610d7458d6ff5e1a79c1b03ac643f18 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Mon, 28 Oct 2024 18:40:41 +0200 Subject: [PATCH 015/195] Add accessibilty information to entrances --- .../apis/gtfs/generated/GraphQLDataFetchers.java | 13 +++---------- .../graph_builder/module/osm/VertexGenerator.java | 8 +++----- .../org/opentripplanner/model/plan/Entrance.java | 8 +++++--- .../algorithm/mapping/StatesToWalkStepsMapper.java | 4 +++- .../street/model/vertex/StationEntranceVertex.java | 8 +++++++- .../street/model/vertex/VertexFactory.java | 9 +++++++-- .../org/opentripplanner/apis/gtfs/schema.graphqls | 2 ++ .../apis/gtfs/GraphQLIntegrationTest.java | 2 +- .../apis/gtfs/expectations/walk-steps.json | 3 ++- .../apis/gtfs/queries/walk-steps.graphql | 1 + 10 files changed, 34 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 179758bd78d..ec0e573d705 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -1,11 +1,9 @@ //THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. package org.opentripplanner.apis.gtfs.generated; -import graphql.relay.Connection; import graphql.relay.Connection; import graphql.relay.DefaultEdge; import graphql.relay.Edge; -import graphql.relay.Edge; import graphql.schema.DataFetcher; import graphql.schema.TypeResolver; import java.util.Currency; @@ -27,12 +25,8 @@ import org.opentripplanner.apis.gtfs.model.FeedPublisher; import org.opentripplanner.apis.gtfs.model.PlanPageInfo; import org.opentripplanner.apis.gtfs.model.RideHailingProvider; -import org.opentripplanner.apis.gtfs.model.RouteTypeModel; -import org.opentripplanner.apis.gtfs.model.StopOnRouteModel; -import org.opentripplanner.apis.gtfs.model.StopOnTripModel; import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.apis.gtfs.model.TripOccupancy; -import org.opentripplanner.apis.gtfs.model.UnknownModel; import org.opentripplanner.ext.fares.model.FareRuleSet; import org.opentripplanner.ext.ridehailing.model.RideEstimate; import org.opentripplanner.model.StopTimesInPattern; @@ -55,8 +49,6 @@ import org.opentripplanner.routing.graphfinder.PatternAtStop; import org.opentripplanner.routing.graphfinder.PlaceAtDistance; import org.opentripplanner.routing.vehicle_parking.VehicleParking; -import org.opentripplanner.routing.vehicle_parking.VehicleParking; -import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; import org.opentripplanner.routing.vehicle_parking.VehicleParkingState; import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; @@ -67,7 +59,6 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; -import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; import org.opentripplanner.transit.model.basic.Money; @@ -366,8 +357,10 @@ public interface GraphQLEmissions { public DataFetcher co2(); } - /** Station entrance/exit */ + /** Station entrance or exit. */ public interface GraphQLEntrance { + public DataFetcher accessible(); + public DataFetcher code(); public DataFetcher gtfsId(); 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 42bbaf659c1..b4c3c08aa8b 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 @@ -104,11 +104,9 @@ IntersectionVertex getVertexForOsmNode(OsmNode node, OsmWithTags way) { if (includeOsmSubwayEntrances && node.isSubwayEntrance()) { String ref = node.getTag("ref"); - if (ref != null) { - iv = vertexFactory.stationEntrance(nid, coordinate, ref); - } else { - iv = vertexFactory.stationEntrance(nid, coordinate, null); - } + + boolean accessible = node.isTag("wheelchair", "yes"); + iv = vertexFactory.stationEntrance(nid, coordinate, ref, accessible); } if (iv == null) { diff --git a/application/src/main/java/org/opentripplanner/model/plan/Entrance.java b/application/src/main/java/org/opentripplanner/model/plan/Entrance.java index 7b28ee992a4..fa1e0fe5e57 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/Entrance.java +++ b/application/src/main/java/org/opentripplanner/model/plan/Entrance.java @@ -5,14 +5,16 @@ public final class Entrance extends StepEntity { private final String code; private final String gtfsId; private final String name; + private final boolean accessible; - public Entrance(String code, String gtfsId, String name) { + public Entrance(String code, String gtfsId, String name, boolean accessible) { this.code = code; this.gtfsId = gtfsId; this.name = name; + this.accessible = accessible; } - public static Entrance withCode(String code) { - return new Entrance(code, null, null); + public static Entrance withCodeAndAccessible(String code, boolean accessible) { + return new Entrance(code, null, null, accessible); } } 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 f04a6a5a2d9..14ed2a553e4 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 @@ -529,7 +529,9 @@ private WalkStepBuilder createStationEntranceWalkStep( var step = createWalkStep(forwardState, backState); step.withRelativeDirection(RelativeDirection.CONTINUE); - step.withEntrance(Entrance.withCode(((StationEntranceVertex) backState.getVertex()).getCode())); + + StationEntranceVertex vertex = (StationEntranceVertex) backState.getVertex(); + step.withEntrance(Entrance.withCodeAndAccessible(vertex.getCode(), vertex.isAccessible())); return step; } 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 index 083c0d52a0f..3f83a356dea 100644 --- a/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java +++ b/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java @@ -3,16 +3,22 @@ public class StationEntranceVertex extends OsmVertex { private final String code; + private final boolean accessible; - public StationEntranceVertex(double x, double y, long nodeId, String code) { + public StationEntranceVertex(double x, double y, long nodeId, String code, boolean accessible) { super(x, y, nodeId); this.code = code; + this.accessible = accessible; } public String getCode() { return code; } + public boolean isAccessible() { + return accessible; + } + public String toString() { return "StationEntranceVertex(" + super.toString() + ", code=" + code + ")"; } 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 61f74841245..1c37d39adb6 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 @@ -94,8 +94,13 @@ 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) { - return addToGraph(new StationEntranceVertex(coordinate.x, coordinate.y, nid, code)); + public StationEntranceVertex stationEntrance( + long nid, + Coordinate coordinate, + String code, + boolean accessible + ) { + return addToGraph(new StationEntranceVertex(coordinate.x, coordinate.y, nid, code, accessible)); } public OsmVertex osm( 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 b2d48f7e0b1..83c32e2c1ca 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -449,6 +449,8 @@ type Emissions { "Station entrance or exit." type Entrance { + "True if the entrance is wheelchair accessible." + accessible: Boolean "Short text or a number that identifies the entrance or exit for passengers. For example, `A` or `B`." code: String "ID of the entrance in the format of `FeedId:EntranceId`." 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 020a1ec6113..d2448a92c59 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -235,7 +235,7 @@ public Set getRoutesForStop(StopLocation stop) { var step3 = walkStep("entrance") .withRelativeDirection(RelativeDirection.CONTINUE) - .withEntrance(Entrance.withCode("A")) + .withEntrance(Entrance.withCodeAndAccessible("A", true)) .build(); Itinerary i1 = newItinerary(A, T11_00) 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 b4172c0f70f..3a8e952880f 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 @@ -29,7 +29,8 @@ "absoluteDirection" : null, "entity" : { "__typename" : "Entrance", - "code": "A" + "code": "A", + "accessible": true } } ] 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 74cbe0c599e..5e7c0493c35 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 @@ -24,6 +24,7 @@ __typename ... on Entrance { code + accessible } } } From 36336bf11dcf98e3c242eae2328043aa8d6cf80f Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 1 Nov 2024 11:58:27 +0200 Subject: [PATCH 016/195] Add ability to create special transfer requests for cars and bikes with the 'carsAllowedStopMaxTransferDurationsForMode' build config parameter. --- .../api/parameter/QualifiedModeSet.java | 6 +- .../module/DirectTransferGenerator.java | 135 +++++++++-- .../module/configure/GraphBuilderModules.java | 3 +- .../nearbystops/StreetNearbyStopFinder.java | 30 ++- .../request/framework/DurationForEnum.java | 4 + .../standalone/config/BuildConfig.java | 10 + ...llowedStopMaxTransferDurationsForMode.java | 55 +++++ doc/user/BuildConfiguration.md | 222 ++++++++++-------- 8 files changed, 337 insertions(+), 128 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java diff --git a/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java b/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java index c6f1a3d74ec..c13fb576ad5 100644 --- a/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java +++ b/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java @@ -126,10 +126,8 @@ public RequestModes getRequestModes() { mBuilder.withEgressMode(StreetMode.CAR_HAILING); mBuilder.withDirectMode(StreetMode.WALK); } else { - mBuilder.withAccessMode(StreetMode.WALK); - mBuilder.withTransferMode(StreetMode.WALK); - mBuilder.withEgressMode(StreetMode.WALK); - mBuilder.withDirectMode(StreetMode.CAR); + // This is used in transfer cache request calculations. + mBuilder.withAllStreetModes(StreetMode.CAR); } } } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index a1a0796c66a..fcf5ea80cfc 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -3,10 +3,14 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimaps; import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.framework.logging.ProgressTracker; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; @@ -18,10 +22,13 @@ import org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder; import org.opentripplanner.model.PathTransfer; import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.framework.DurationForEnum; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.TransitStopVertex; +import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.service.DefaultTransitService; @@ -44,6 +51,7 @@ public class DirectTransferGenerator implements GraphBuilderModule { private final Duration radiusByDuration; private final List transferRequests; + private final DurationForEnum carsAllowedStopMaxTransferDurationsForMode; private final Graph graph; private final TimetableRepository timetableRepository; private final DataImportIssueStore issueStore; @@ -60,6 +68,23 @@ public DirectTransferGenerator( this.issueStore = issueStore; this.radiusByDuration = radiusByDuration; this.transferRequests = transferRequests; + this.carsAllowedStopMaxTransferDurationsForMode = DurationForEnum.of(StreetMode.class).build(); + } + + public DirectTransferGenerator( + Graph graph, + TimetableRepository timetableRepository, + DataImportIssueStore issueStore, + Duration radiusByDuration, + List transferRequests, + DurationForEnum carsAllowedStopMaxTransferDurationsForMode + ) { + this.graph = graph; + this.timetableRepository = timetableRepository; + this.issueStore = issueStore; + this.radiusByDuration = radiusByDuration; + this.transferRequests = transferRequests; + this.carsAllowedStopMaxTransferDurationsForMode = carsAllowedStopMaxTransferDurationsForMode; } @Override @@ -68,9 +93,16 @@ public void buildGraph() { timetableRepository.index(); /* The linker will use streets if they are available, or straight-line distance otherwise. */ - NearbyStopFinder nearbyStopFinder = createNearbyStopFinder(); + NearbyStopFinder nearbyStopFinder = createNearbyStopFinder(radiusByDuration, Set.of()); List stops = graph.getVerticesOfType(TransitStopVertex.class); + Set carsAllowedStops = timetableRepository + .getStopLocationsUsedForCarsAllowedTrips() + .stream() + .map(StopLocation::getId) + .map(graph::getStopVertexForStopId) + .filter(TransitStopVertex.class::isInstance) // filter out null values if no TransitStopVertex is found for ID + .collect(Collectors.toSet()); ProgressTracker progress = ProgressTracker.track( "Create transfer edges for stops", @@ -86,6 +118,37 @@ public void buildGraph() { HashMultimap.create() ); + List filteredTransferRequests = new ArrayList(); + List carsAllowedStopTransferRequests = new ArrayList(); + HashMap carsAllowedStopNearbyStopFinders = new HashMap<>(); + + // Split transfer requests into normal and carsAllowedStop requests. + for (RouteRequest transferProfile : transferRequests) { + StreetMode mode = transferProfile.journey().transfer().mode(); + if (carsAllowedStopMaxTransferDurationsForMode.containsKey(mode)) { + carsAllowedStopNearbyStopFinders.put( + mode, + createNearbyStopFinder( + carsAllowedStopMaxTransferDurationsForMode.valueOf(mode), + Collections.unmodifiableSet(carsAllowedStops) + ) + ); + + carsAllowedStopTransferRequests.add(transferProfile); + // For bikes, also normal transfer requests are wanted. + if (mode == StreetMode.BIKE) { + filteredTransferRequests.add(transferProfile); + } + } else if (mode == StreetMode.CAR) { + // Special transfers are always created for cars. + // If a duration is not specified for cars, the default is used. + carsAllowedStopNearbyStopFinders.put(mode, nearbyStopFinder); + carsAllowedStopTransferRequests.add(transferProfile); + } else { + filteredTransferRequests.add(transferProfile); + } + } + stops .stream() .parallel() @@ -101,25 +164,8 @@ public void buildGraph() { LOG.debug("Linking stop '{}' {}", stop, ts0); - for (RouteRequest transferProfile : transferRequests) { - for (NearbyStop sd : nearbyStopFinder.findNearbyStops( - ts0, - transferProfile, - transferProfile.journey().transfer(), - false - )) { - // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { - continue; - } - if (sd.stop.transfersNotAllowed()) { - continue; - } - distinctTransfers.put( - new TransferKey(stop, sd.stop, sd.edges), - new PathTransfer(stop, sd.stop, sd.distance, sd.edges) - ); - } + for (RouteRequest transferProfile : filteredTransferRequests) { + findNearbyStops(nearbyStopFinder, ts0, transferProfile, stop, distinctTransfers); if (OTPFeature.FlexRouting.isOn()) { // This code is for finding transfers from AreaStops to Stops, transfers // from Stops to AreaStops and between Stops are already covered above. @@ -143,6 +189,21 @@ public void buildGraph() { } } } + // This calculates transfers between stops that can use trips with cars. + for (RouteRequest transferProfile : carsAllowedStopTransferRequests) { + StreetMode mode = transferProfile.journey().transfer().mode(); + if ( + carsAllowedStops.contains(ts0) && carsAllowedStopNearbyStopFinders.containsKey(mode) + ) { + findNearbyStops( + carsAllowedStopNearbyStopFinders.get(mode), + ts0, + transferProfile, + stop, + distinctTransfers + ); + } + } LOG.debug( "Linked stop {} with {} transfers to stops with different patterns.", @@ -179,7 +240,10 @@ public void buildGraph() { * whether the graph has a street network and if ConsiderPatternsForDirectTransfers feature is * enabled. */ - private NearbyStopFinder createNearbyStopFinder() { + private NearbyStopFinder createNearbyStopFinder( + Duration radiusByDuration, + Set findOnlyVertices + ) { var transitService = new DefaultTransitService(timetableRepository); NearbyStopFinder finder; if (!graph.hasStreets) { @@ -189,7 +253,7 @@ private NearbyStopFinder createNearbyStopFinder() { finder = new StraightLineNearbyStopFinder(transitService, radiusByDuration); } else { LOG.info("Creating direct transfer edges between stops using the street network from OSM..."); - finder = new StreetNearbyStopFinder(radiusByDuration, 0, null); + finder = new StreetNearbyStopFinder(radiusByDuration, 0, null, Set.of(), findOnlyVertices); } if (OTPFeature.ConsiderPatternsForDirectTransfers.isOn()) { @@ -199,5 +263,32 @@ private NearbyStopFinder createNearbyStopFinder() { } } + private void findNearbyStops( + NearbyStopFinder nearbyStopFinder, + TransitStopVertex ts0, + RouteRequest transferProfile, + RegularStop stop, + Map distinctTransfers + ) { + for (NearbyStop sd : nearbyStopFinder.findNearbyStops( + ts0, + transferProfile, + transferProfile.journey().transfer(), + false + )) { + // Skip the origin stop, loop transfers are not needed. + if (sd.stop == stop) { + continue; + } + if (sd.stop.transfersNotAllowed()) { + continue; + } + distinctTransfers.put( + new TransferKey(stop, sd.stop, sd.edges), + new PathTransfer(stop, sd.stop, sd.distance, sd.edges) + ); + } + } + private record TransferKey(StopLocation source, StopLocation target, List edges) {} } 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 080d69c571e..03ea13e9f11 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 @@ -237,7 +237,8 @@ static DirectTransferGenerator provideDirectTransferGenerator( timetableRepository, issueStore, config.maxTransferDuration, - config.transferRequests + config.transferRequests, + config.carsAllowedStopMaxTransferDurationsForMode ); } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java index e54c27249e1..34d61e5c99d 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java @@ -30,8 +30,6 @@ import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.StreetSearchBuilder; 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.State; import org.opentripplanner.street.search.strategy.DominanceFunctions; import org.opentripplanner.transit.model.site.AreaStop; @@ -42,6 +40,7 @@ public class StreetNearbyStopFinder implements NearbyStopFinder { private final int maxStopCount; private final DataOverlayContext dataOverlayContext; private final Set ignoreVertices; + private final Set findOnlyVertices; /** * Construct a NearbyStopFinder for the given graph and search radius. @@ -54,7 +53,7 @@ public StreetNearbyStopFinder( int maxStopCount, DataOverlayContext dataOverlayContext ) { - this(durationLimit, maxStopCount, dataOverlayContext, Set.of()); + this(durationLimit, maxStopCount, dataOverlayContext, Set.of(), Set.of()); } /** @@ -69,11 +68,31 @@ public StreetNearbyStopFinder( int maxStopCount, DataOverlayContext dataOverlayContext, Set ignoreVertices + ) { + this(durationLimit, maxStopCount, dataOverlayContext, ignoreVertices, Set.of()); + } + + /** + * Construct a NearbyStopFinder for the given graph and search radius. + * + * @param maxStopCount The maximum stops to return. 0 means no limit. Regardless of the maxStopCount + * we will always return all the directly connected stops. + * @param ignoreVertices A set of stop vertices to ignore and not return NearbyStops for. + * + * @param findOnlyVertices Only return NearbyStops that are in this set. If this is empty, no filtering is performed. + */ + public StreetNearbyStopFinder( + Duration durationLimit, + int maxStopCount, + DataOverlayContext dataOverlayContext, + Set ignoreVertices, + Set findOnlyVertices ) { this.dataOverlayContext = dataOverlayContext; this.durationLimit = durationLimit; this.maxStopCount = maxStopCount; this.ignoreVertices = ignoreVertices; + this.findOnlyVertices = findOnlyVertices; } /** @@ -146,7 +165,10 @@ public Collection findNearbyStops( continue; } if (targetVertex instanceof TransitStopVertex tsv && state.isFinal()) { - stopsFound.add(NearbyStop.nearbyStopForState(state, tsv.getStop())); + // If a set of findOnlyVertices is provided, only add stops that are in the set. + if (findOnlyVertices.isEmpty() || findOnlyVertices.contains(targetVertex)) { + stopsFound.add(NearbyStop.nearbyStopForState(state, tsv.getStop())); + } } if ( OTPFeature.FlexRouting.isOn() && diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java b/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java index e6dacaac19d..69b15ef4624 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java @@ -49,6 +49,10 @@ public Duration defaultValue() { return defaultValue; } + public boolean containsKey(E key) { + return valueForEnum.containsKey(key); + } + /** * Utility method to get {@link #defaultValue} as an number in unit seconds. Equivalent to * {@code (int) defaultValue.toSeconds()}. The downcast is safe since we only allow days, hours, diff --git a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index ef2931b987e..6482868d578 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -33,7 +33,10 @@ import org.opentripplanner.model.calendar.ServiceDateInterval; import org.opentripplanner.netex.config.NetexFeedParameters; import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.framework.DurationForEnum; import org.opentripplanner.routing.fares.FareServiceFactory; +import org.opentripplanner.standalone.config.buildconfig.CarsAllowedStopMaxTransferDurationsForMode; import org.opentripplanner.standalone.config.buildconfig.DemConfig; import org.opentripplanner.standalone.config.buildconfig.GtfsConfig; import org.opentripplanner.standalone.config.buildconfig.IslandPruningConfig; @@ -151,6 +154,7 @@ public class BuildConfig implements OtpDataStoreConfig { public final IslandPruningConfig islandPruning; public final Duration maxTransferDuration; + public final DurationForEnum carsAllowedStopMaxTransferDurationsForMode; public final NetexFeedParameters netexDefaults; public final GtfsFeedParameters gtfsDefaults; @@ -287,6 +291,12 @@ When set to true (it is false by default), the elevation module will include the "Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph." ) .asDuration(Duration.ofMinutes(30)); + carsAllowedStopMaxTransferDurationsForMode = + CarsAllowedStopMaxTransferDurationsForMode.map( + root, + "carsAllowedStopMaxTransferDurationsForMode", + maxTransferDuration + ); maxStopToShapeSnapDistance = root .of("maxStopToShapeSnapDistance") diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java new file mode 100644 index 00000000000..322ed32d682 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java @@ -0,0 +1,55 @@ +package org.opentripplanner.standalone.config.buildconfig; + +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; + +import java.time.Duration; +import java.util.Map; +import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.framework.DurationForEnum; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; + +public class CarsAllowedStopMaxTransferDurationsForMode { + + public static DurationForEnum map( + NodeAdapter root, + String parameterName, + Duration maxTransferDuration + ) { + Map values = root + .of(parameterName) + .since(V2_7) + .summary( + "This is used for specifying a `maxTransferDuration` value for bikes and cars to use with transfers between stops that have trips with cars." + ) + .description( + """ +This is a special parameter that only works on transfers between stops that have trips that allow cars. +The duration can be set for either 'BIKE' or 'CAR'. +For cars, transfers are only calculated between stops that have trips that allow cars. +For cars, this overrides the default `maxTransferDuration`. +For bicycles, this indicates that additional transfers should be calculated with the specified duration between stops that have trips that allow cars. + +**Example** + +```JSON +// build-config.json +{ + "carsAllowedStopMaxTransferDurationsForMode": { + "CAR": "2h", + "BIKE": "3h" + } +} +``` +""" + ) + .asEnumMap(StreetMode.class, Duration.class); + for (StreetMode mode : values.keySet()) { + if (mode != StreetMode.BIKE && mode != StreetMode.CAR) { + throw new IllegalArgumentException( + "Only the CAR and BIKE modes are allowed in the carsAllowedStopMaxTransferDurationsForMode parameter." + ); + } + } + return DurationForEnum.of(StreetMode.class).withValues(values).build(); + } +} diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index 99e98066e73..e2d46e98d73 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -17,103 +17,104 @@ Sections follow that describe particular settings in more depth. -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|--------------------------------------------------------------------------|:------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|-----------------------------------|:-----:| -| [areaVisibility](#areaVisibility) | `boolean` | Perform visibility calculations. | *Optional* | `false` | 1.5 | -| [buildReportDir](#buildReportDir) | `uri` | URI to the directory where the graph build report should be written to. | *Optional* | | 2.0 | -| [configVersion](#configVersion) | `string` | Deployment version of the *build-config.json*. | *Optional* | | 2.1 | -| [dataImportReport](#dataImportReport) | `boolean` | Generate nice HTML report of Graph errors/warnings | *Optional* | `false` | 2.0 | -| [distanceBetweenElevationSamples](#distanceBetweenElevationSamples) | `double` | The distance between elevation samples in meters. | *Optional* | `10.0` | 2.0 | -| embedRouterConfig | `boolean` | Embed the Router config in the graph, which allows it to be sent to a server fully configured over the wire. | *Optional* | `true` | 2.0 | -| [graph](#graph) | `uri` | URI to the graph object file for reading and writing. | *Optional* | | 2.0 | -| [gsCredentials](#gsCredentials) | `string` | Local file system path to Google Cloud Platform service accounts credentials file. | *Optional* | | 2.0 | -| [includeEllipsoidToGeoidDifference](#includeEllipsoidToGeoidDifference) | `boolean` | Include the Ellipsoid to Geoid difference in the calculations of every point along every StreetWithElevationEdge. | *Optional* | `false` | 2.0 | -| maxAreaNodes | `integer` | Visibility calculations for an area will not be done if there are more nodes than this limit. | *Optional* | `150` | 2.1 | -| [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | -| maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | -| [maxStopToShapeSnapDistance](#maxStopToShapeSnapDistance) | `double` | Maximum distance between route shapes and their stops. | *Optional* | `150.0` | 2.1 | -| maxTransferDuration | `duration` | Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph. | *Optional* | `"PT30M"` | 2.1 | -| [multiThreadElevationCalculations](#multiThreadElevationCalculations) | `boolean` | Configuring multi-threading during elevation calculations. | *Optional* | `false` | 2.0 | -| [osmCacheDataInMem](#osmCacheDataInMem) | `boolean` | If OSM data should be cached in memory during processing. | *Optional* | `false` | 2.0 | -| [osmNaming](#osmNaming) | `enum` | A custom OSM namer to use. | *Optional* | `"default"` | 1.5 | -| platformEntriesLinking | `boolean` | Link unconnected entries to public transport platforms. | *Optional* | `false` | 2.0 | -| [readCachedElevations](#readCachedElevations) | `boolean` | Whether to read cached elevation data. | *Optional* | `true` | 2.0 | -| staticBikeParkAndRide | `boolean` | Whether we should create bike P+R stations from OSM data. | *Optional* | `false` | 1.5 | -| staticParkAndRide | `boolean` | Whether we should create car P+R stations from OSM data. | *Optional* | `true` | 1.5 | -| stopConsolidationFile | `uri` | Name of the CSV-formatted file in the build directory which contains the configuration for stop consolidation. | *Optional* | | 2.5 | -| [streetGraph](#streetGraph) | `uri` | URI to the street graph object file for reading and writing. | *Optional* | | 2.0 | -| [subwayAccessTime](#subwayAccessTime) | `double` | Minutes necessary to reach stops served by trips on routes of route_type=1 (subway) from the street. | *Optional* | `2.0` | 1.5 | -| [transitModelTimeZone](#transitModelTimeZone) | `time-zone` | Time zone for the graph. | *Optional* | | 2.2 | -| [transitServiceEnd](#transitServiceEnd) | `duration` | Limit the import of transit services to the given end date. | *Optional* | `"P3Y"` | 2.0 | -| [transitServiceStart](#transitServiceStart) | `duration` | Limit the import of transit services to the given START date. | *Optional* | `"-P1Y"` | 2.0 | -| [writeCachedElevations](#writeCachedElevations) | `boolean` | Reusing elevation data from previous builds | *Optional* | `false` | 2.0 | -| [boardingLocationTags](#boardingLocationTags) | `string[]` | What OSM tags should be looked on for the source of matching stops to platforms and stops. | *Optional* | | 2.2 | -| [dataOverlay](sandbox/DataOverlay.md) | `object` | Config for the DataOverlay Sandbox module | *Optional* | | 2.2 | -| [dem](#dem) | `object[]` | Specify parameters for DEM extracts. | *Optional* | | 2.2 | -|       [elevationUnitMultiplier](#dem_0_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. Overrides the value specified in `demDefaults`. | *Optional* | `1.0` | 2.3 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -| demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | -|    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | -| [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | -| [emissions](sandbox/Emissions.md) | `object` | Emissions configuration. | *Optional* | | 2.5 | -| [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | -| gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | -|    blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. | *Optional* | `true` | 2.3 | -|    [discardMinTransferTimes](#gd_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. | *Optional* | `false` | 2.3 | -|    maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. | *Optional* | `200` | 2.3 | -|    removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. | *Optional* | `true` | 2.3 | -|    [stationTransferPreference](#gd_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. | *Optional* | `"allowed"` | 2.3 | -| islandPruning | `object` | Settings for fixing street graph connectivity errors | *Optional* | | 2.3 | -|    [adaptivePruningDistance](#islandPruning_adaptivePruningDistance) | `integer` | Search distance for analyzing islands in pruning. | *Optional* | `250` | 2.3 | -|    [adaptivePruningFactor](#islandPruning_adaptivePruningFactor) | `double` | Defines how much pruning thresholds grow maximally by distance. | *Optional* | `50.0` | 2.3 | -|    [islandWithStopsMaxSize](#islandPruning_islandWithStopsMaxSize) | `integer` | When a graph island with stops in it should be pruned. | *Optional* | `2` | 2.3 | -|    [islandWithoutStopsMaxSize](#islandPruning_islandWithoutStopsMaxSize) | `integer` | When a graph island without stops should be pruned. | *Optional* | `10` | 2.3 | -| [localFileNamePatterns](#localFileNamePatterns) | `object` | Patterns for matching OTP file types in the base directory | *Optional* | | 2.0 | -|    [dem](#lfp_dem) | `regexp` | Pattern for matching elevation DEM files. | *Optional* | `"(?i)\.tiff?$"` | 2.0 | -|    [gtfs](#lfp_gtfs) | `regexp` | Patterns for matching GTFS zip-files or directories. | *Optional* | `"(?i)gtfs"` | 2.0 | -|    [netex](#lfp_netex) | `regexp` | Patterns for matching NeTEx zip files or directories. | *Optional* | `"(?i)netex"` | 2.0 | -|    [osm](#lfp_osm) | `regexp` | Pattern for matching Open Street Map input files. | *Optional* | `"(?i)(\.pbf¦\.osm¦\.osm\.xml)$"` | 2.0 | -| netexDefaults | `object` | The netexDefaults section allows you to specify default properties for NeTEx files. | *Optional* | | 2.2 | -|    feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Optional* | `"NETEX"` | 2.2 | -|    [groupFilePattern](#nd_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | -|    ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | -|    [ignoreFilePattern](#nd_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | -|    ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | -|    noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | -|    [sharedFilePattern](#nd_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | -|    [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 | -|       [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 | -|    [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 | -| [transitFeeds](#transitFeeds) | `object[]` | Scan for transit data files | *Optional* | | 2.2 | -|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | -|       type = "gtfs" | `enum` | The feed input format. | *Required* | | 2.2 | -|       blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | -|       [discardMinTransferTimes](#tf_0_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. Overrides the value specified in `gtfsDefaults`. | *Optional* | `false` | 2.3 | -|       feedId | `string` | The unique ID for this feed. This overrides any feed ID defined within the feed itself. | *Optional* | | 2.2 | -|       maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. Overrides the value specified in `gtfsDefaults`. | *Optional* | `200` | 2.3 | -|       removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -|       [stationTransferPreference](#tf_0_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. Overrides the value specified in `gtfsDefaults`. | *Optional* | `"allowed"` | 2.3 | -|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | -|       type = "netex" | `enum` | The feed input format. | *Required* | | 2.2 | -|       feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Required* | | 2.2 | -|       [groupFilePattern](#tf_1_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | -|       ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | -|       [ignoreFilePattern](#tf_1_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | -|       ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | -|       noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | -|       [sharedFilePattern](#tf_1_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | -|       [sharedGroupFilePattern](#tf_1_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -|       [ferryIdsNotAllowedForBicycle](#tf_1_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | -| [transitRouteToStationCentroid](#transitRouteToStationCentroid) | `feed-scoped-id[]` | List stations that should route to centroid. | *Optional* | | 2.7 | +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|-------------------------------------------------------------------------------------------|:----------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|-----------------------------------|:-----:| +| [areaVisibility](#areaVisibility) | `boolean` | Perform visibility calculations. | *Optional* | `false` | 1.5 | +| [buildReportDir](#buildReportDir) | `uri` | URI to the directory where the graph build report should be written to. | *Optional* | | 2.0 | +| [configVersion](#configVersion) | `string` | Deployment version of the *build-config.json*. | *Optional* | | 2.1 | +| [dataImportReport](#dataImportReport) | `boolean` | Generate nice HTML report of Graph errors/warnings | *Optional* | `false` | 2.0 | +| [distanceBetweenElevationSamples](#distanceBetweenElevationSamples) | `double` | The distance between elevation samples in meters. | *Optional* | `10.0` | 2.0 | +| embedRouterConfig | `boolean` | Embed the Router config in the graph, which allows it to be sent to a server fully configured over the wire. | *Optional* | `true` | 2.0 | +| [graph](#graph) | `uri` | URI to the graph object file for reading and writing. | *Optional* | | 2.0 | +| [gsCredentials](#gsCredentials) | `string` | Local file system path to Google Cloud Platform service accounts credentials file. | *Optional* | | 2.0 | +| [includeEllipsoidToGeoidDifference](#includeEllipsoidToGeoidDifference) | `boolean` | Include the Ellipsoid to Geoid difference in the calculations of every point along every StreetWithElevationEdge. | *Optional* | `false` | 2.0 | +| maxAreaNodes | `integer` | Visibility calculations for an area will not be done if there are more nodes than this limit. | *Optional* | `150` | 2.1 | +| [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | +| maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | +| [maxStopToShapeSnapDistance](#maxStopToShapeSnapDistance) | `double` | Maximum distance between route shapes and their stops. | *Optional* | `150.0` | 2.1 | +| maxTransferDuration | `duration` | Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph. | *Optional* | `"PT30M"` | 2.1 | +| [multiThreadElevationCalculations](#multiThreadElevationCalculations) | `boolean` | Configuring multi-threading during elevation calculations. | *Optional* | `false` | 2.0 | +| [osmCacheDataInMem](#osmCacheDataInMem) | `boolean` | If OSM data should be cached in memory during processing. | *Optional* | `false` | 2.0 | +| [osmNaming](#osmNaming) | `enum` | A custom OSM namer to use. | *Optional* | `"default"` | 1.5 | +| platformEntriesLinking | `boolean` | Link unconnected entries to public transport platforms. | *Optional* | `false` | 2.0 | +| [readCachedElevations](#readCachedElevations) | `boolean` | Whether to read cached elevation data. | *Optional* | `true` | 2.0 | +| staticBikeParkAndRide | `boolean` | Whether we should create bike P+R stations from OSM data. | *Optional* | `false` | 1.5 | +| staticParkAndRide | `boolean` | Whether we should create car P+R stations from OSM data. | *Optional* | `true` | 1.5 | +| stopConsolidationFile | `uri` | Name of the CSV-formatted file in the build directory which contains the configuration for stop consolidation. | *Optional* | | 2.5 | +| [streetGraph](#streetGraph) | `uri` | URI to the street graph object file for reading and writing. | *Optional* | | 2.0 | +| [subwayAccessTime](#subwayAccessTime) | `double` | Minutes necessary to reach stops served by trips on routes of route_type=1 (subway) from the street. | *Optional* | `2.0` | 1.5 | +| [transitModelTimeZone](#transitModelTimeZone) | `time-zone` | Time zone for the graph. | *Optional* | | 2.2 | +| [transitServiceEnd](#transitServiceEnd) | `duration` | Limit the import of transit services to the given end date. | *Optional* | `"P3Y"` | 2.0 | +| [transitServiceStart](#transitServiceStart) | `duration` | Limit the import of transit services to the given START date. | *Optional* | `"-P1Y"` | 2.0 | +| [writeCachedElevations](#writeCachedElevations) | `boolean` | Reusing elevation data from previous builds | *Optional* | `false` | 2.0 | +| [boardingLocationTags](#boardingLocationTags) | `string[]` | What OSM tags should be looked on for the source of matching stops to platforms and stops. | *Optional* | | 2.2 | +| [carsAllowedStopMaxTransferDurationsForMode](#carsAllowedStopMaxTransferDurationsForMode) | `enum map of duration` | This is used for specifying a `maxTransferDuration` value for bikes and cars to use with transfers between stops that have trips with cars. | *Optional* | | 2.7 | +| [dataOverlay](sandbox/DataOverlay.md) | `object` | Config for the DataOverlay Sandbox module | *Optional* | | 2.2 | +| [dem](#dem) | `object[]` | Specify parameters for DEM extracts. | *Optional* | | 2.2 | +|       [elevationUnitMultiplier](#dem_0_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. Overrides the value specified in `demDefaults`. | *Optional* | `1.0` | 2.3 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +| demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | +|    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | +| [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | +| [emissions](sandbox/Emissions.md) | `object` | Emissions configuration. | *Optional* | | 2.5 | +| [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | +| gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | +|    blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. | *Optional* | `true` | 2.3 | +|    [discardMinTransferTimes](#gd_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. | *Optional* | `false` | 2.3 | +|    maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. | *Optional* | `200` | 2.3 | +|    removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. | *Optional* | `true` | 2.3 | +|    [stationTransferPreference](#gd_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. | *Optional* | `"allowed"` | 2.3 | +| islandPruning | `object` | Settings for fixing street graph connectivity errors | *Optional* | | 2.3 | +|    [adaptivePruningDistance](#islandPruning_adaptivePruningDistance) | `integer` | Search distance for analyzing islands in pruning. | *Optional* | `250` | 2.3 | +|    [adaptivePruningFactor](#islandPruning_adaptivePruningFactor) | `double` | Defines how much pruning thresholds grow maximally by distance. | *Optional* | `50.0` | 2.3 | +|    [islandWithStopsMaxSize](#islandPruning_islandWithStopsMaxSize) | `integer` | When a graph island with stops in it should be pruned. | *Optional* | `2` | 2.3 | +|    [islandWithoutStopsMaxSize](#islandPruning_islandWithoutStopsMaxSize) | `integer` | When a graph island without stops should be pruned. | *Optional* | `10` | 2.3 | +| [localFileNamePatterns](#localFileNamePatterns) | `object` | Patterns for matching OTP file types in the base directory | *Optional* | | 2.0 | +|    [dem](#lfp_dem) | `regexp` | Pattern for matching elevation DEM files. | *Optional* | `"(?i)\.tiff?$"` | 2.0 | +|    [gtfs](#lfp_gtfs) | `regexp` | Patterns for matching GTFS zip-files or directories. | *Optional* | `"(?i)gtfs"` | 2.0 | +|    [netex](#lfp_netex) | `regexp` | Patterns for matching NeTEx zip files or directories. | *Optional* | `"(?i)netex"` | 2.0 | +|    [osm](#lfp_osm) | `regexp` | Pattern for matching Open Street Map input files. | *Optional* | `"(?i)(\.pbf¦\.osm¦\.osm\.xml)$"` | 2.0 | +| netexDefaults | `object` | The netexDefaults section allows you to specify default properties for NeTEx files. | *Optional* | | 2.2 | +|    feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Optional* | `"NETEX"` | 2.2 | +|    [groupFilePattern](#nd_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | +|    ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | +|    [ignoreFilePattern](#nd_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | +|    ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | +|    noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | +|    [sharedFilePattern](#nd_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | +|    [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 | +|       [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 | +|    [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 | +| [transitFeeds](#transitFeeds) | `object[]` | Scan for transit data files | *Optional* | | 2.2 | +|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | +|       type = "gtfs" | `enum` | The feed input format. | *Required* | | 2.2 | +|       blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | +|       [discardMinTransferTimes](#tf_0_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. Overrides the value specified in `gtfsDefaults`. | *Optional* | `false` | 2.3 | +|       feedId | `string` | The unique ID for this feed. This overrides any feed ID defined within the feed itself. | *Optional* | | 2.2 | +|       maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. Overrides the value specified in `gtfsDefaults`. | *Optional* | `200` | 2.3 | +|       removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +|       [stationTransferPreference](#tf_0_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. Overrides the value specified in `gtfsDefaults`. | *Optional* | `"allowed"` | 2.3 | +|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | +|       type = "netex" | `enum` | The feed input format. | *Required* | | 2.2 | +|       feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Required* | | 2.2 | +|       [groupFilePattern](#tf_1_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | +|       ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | +|       [ignoreFilePattern](#tf_1_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | +|       ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | +|       noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | +|       [sharedFilePattern](#tf_1_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | +|       [sharedGroupFilePattern](#tf_1_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +|       [ferryIdsNotAllowedForBicycle](#tf_1_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | +| [transitRouteToStationCentroid](#transitRouteToStationCentroid) | `feed-scoped-id[]` | List stations that should route to centroid. | *Optional* | | 2.7 | @@ -653,6 +654,33 @@ What OSM tags should be looked on for the source of matching stops to platforms [Detailed documentation](BoardingLocations.md) +

carsAllowedStopMaxTransferDurationsForMode

+ +**Since version:** `2.7` ∙ **Type:** `enum map of duration` ∙ **Cardinality:** `Optional` +**Path:** / +**Enum keys:** `not-set` | `walk` | `bike` | `bike-to-park` | `bike-rental` | `scooter-rental` | `car` | `car-to-park` | `car-pickup` | `car-rental` | `car-hailing` | `flexible` + +This is used for specifying a `maxTransferDuration` value for bikes and cars to use with transfers between stops that have trips with cars. + +This is a special parameter that only works on transfers between stops that have trips that allow cars. +The duration can be set for either 'BIKE' or 'CAR'. +For cars, transfers are only calculated between stops that have trips that allow cars. +For cars, this overrides the default `maxTransferDuration`. +For bicycles, this indicates that additional transfers should be calculated with the specified duration between stops that have trips that allow cars. + +**Example** + +```JSON +// build-config.json +{ + "carsAllowedStopMaxTransferDurationsForMode": { + "CAR": "2h", + "BIKE": "3h" + } +} +``` + +

dem

**Since version:** `2.2` ∙ **Type:** `object[]` ∙ **Cardinality:** `Optional` From f1380e31bfcbe3eaaadd290f3517015f285455e1 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 1 Nov 2024 11:59:05 +0200 Subject: [PATCH 017/195] Add tests for 'carsAllowedStopMaxTransferDurationsForMode' build config parameter. --- .../module/DirectTransferGeneratorTest.java | 239 +++++++++++++++++- .../StreetNearbyStopFinderTest.java | 72 ++++++ 2 files changed, 306 insertions(+), 5 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java index bb3144aa904..30bc5f78f0f 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java @@ -22,15 +22,18 @@ import org.opentripplanner.routing.algorithm.GraphRoutingTest; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.framework.DurationForEnum; import org.opentripplanner.street.model.StreetTraversalPermission; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.StreetVertex; import org.opentripplanner.street.model.vertex.TransitStopVertex; import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.network.CarAccess; import org.opentripplanner.transit.model.network.StopPattern; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.ScheduledTripTimes; /** * This creates a graph with trip patterns @@ -196,7 +199,7 @@ public void testMultipleRequestsWithoutPatterns() { reqWalk.journey().transfer().setMode(StreetMode.WALK); var reqBike = new RouteRequest(); - reqWalk.journey().transfer().setMode(StreetMode.BIKE); + reqBike.journey().transfer().setMode(StreetMode.BIKE); var transferRequests = List.of(reqWalk, reqBike); @@ -223,7 +226,7 @@ public void testMultipleRequestsWithPatterns() { reqWalk.journey().transfer().setMode(StreetMode.WALK); var reqBike = new RouteRequest(); - reqWalk.journey().transfer().setMode(StreetMode.BIKE); + reqBike.journey().transfer().setMode(StreetMode.BIKE); var transferRequests = List.of(reqWalk, reqBike); @@ -252,7 +255,7 @@ public void testMultipleRequestsWithPatterns() { @Test public void testTransferOnIsolatedStations() { - var otpModel = model(true, false, true); + var otpModel = model(true, false, true, false); var graph = otpModel.graph(); graph.hasStreets = false; @@ -273,18 +276,174 @@ public void testTransferOnIsolatedStations() { assertTrue(timetableRepository.getAllPathTransfers().isEmpty()); } + @Test + public void testRequestWithCarsAllowedPatterns() { + var reqCar = new RouteRequest(); + reqCar.journey().transfer().setMode(StreetMode.CAR); + + var transferRequests = List.of(reqCar); + + var otpModel = model(false, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + MAX_TRANSFER_DURATION, + transferRequests, + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofMinutes(60)).build() + ) + .buildGraph(); + + assertTransfers( + timetableRepository.getAllPathTransfers(), + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 200, List.of(V0, V12), S12) + ); + } + + @Test + public void testRequestWithCarsAllowedPatternsWithDurationLimit() { + var reqCar = new RouteRequest(); + reqCar.journey().transfer().setMode(StreetMode.CAR); + + var transferRequests = List.of(reqCar); + + var otpModel = model(false, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + MAX_TRANSFER_DURATION, + transferRequests, + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofSeconds(10)).build() + ) + .buildGraph(); + + assertTransfers(timetableRepository.getAllPathTransfers(), tr(S0, 100, List.of(V0, V11), S11)); + } + + @Test + public void testMultipleRequestsWithPatternsAndWithCarsAllowedPatterns() { + var reqWalk = new RouteRequest(); + reqWalk.journey().transfer().setMode(StreetMode.WALK); + + var reqBike = new RouteRequest(); + reqBike.journey().transfer().setMode(StreetMode.BIKE); + + var reqCar = new RouteRequest(); + reqCar.journey().transfer().setMode(StreetMode.CAR); + + var transferRequests = List.of(reqWalk, reqBike, reqCar); + + var otpModel = model(true, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + MAX_TRANSFER_DURATION, + transferRequests, + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofMinutes(60)).build() + ) + .buildGraph(); + + assertTransfers( + timetableRepository.getAllPathTransfers(), + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 100, List.of(V0, V21), S21), + tr(S0, 200, List.of(V0, V12), S12), + tr(S11, 100, List.of(V11, V21), S21), + tr(S11, 110, List.of(V11, V22), S22), + tr(S11, 100, List.of(V11, V12), S12) + ); + } + + @Test + public void testBikeRequestWithPatternsAndWithCarsAllowedPatterns() { + var reqBike = new RouteRequest(); + reqBike.journey().transfer().setMode(StreetMode.BIKE); + + var transferRequests = List.of(reqBike); + + var otpModel = model(true, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + Duration.ofSeconds(30), + transferRequests, + DurationForEnum.of(StreetMode.class).with(StreetMode.BIKE, Duration.ofSeconds(120)).build() + ) + .buildGraph(); + + assertTransfers( + timetableRepository.getAllPathTransfers(), + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 100, List.of(V0, V21), S21), + tr(S0, 200, List.of(V0, V12), S12), + tr(S11, 110, List.of(V11, V22), S22), + tr(S11, 100, List.of(V11, V12), S12) + ); + } + + @Test + public void testBikeRequestWithPatternsAndWithCarsAllowedPatternsWithoutCarInTransferRequests() { + var reqBike = new RouteRequest(); + reqBike.journey().transfer().setMode(StreetMode.BIKE); + + var transferRequests = List.of(reqBike); + + var otpModel = model(true, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + Duration.ofSeconds(30), + transferRequests, + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofSeconds(120)).build() + ) + .buildGraph(); + + assertTransfers( + timetableRepository.getAllPathTransfers(), + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 100, List.of(V0, V21), S21), + tr(S11, 110, List.of(V11, V22), S22) + ); + } + private TestOtpModel model(boolean addPatterns) { return model(addPatterns, false); } private TestOtpModel model(boolean addPatterns, boolean withBoardingConstraint) { - return model(addPatterns, withBoardingConstraint, false); + return model(addPatterns, withBoardingConstraint, false, false); } private TestOtpModel model( boolean addPatterns, boolean withBoardingConstraint, - boolean withNoTransfersOnStations + boolean withNoTransfersOnStations, + boolean addCarsAllowedPatterns ) { return modelOf( new Builder() { @@ -352,6 +511,76 @@ public void build() { .build() ); } + + if (addCarsAllowedPatterns) { + var agency = TimetableRepositoryForTest.agency("FerryAgency"); + + tripPattern( + TripPattern + .of(TimetableRepositoryForTest.id("TP3")) + .withRoute(route("R3", TransitMode.FERRY, agency)) + .withStopPattern(new StopPattern(List.of(st(S11), st(S21)))) + .withScheduledTimeTableBuilder(builder -> + builder.addTripTimes( + ScheduledTripTimes + .of() + .withTrip( + TimetableRepositoryForTest + .trip("carsAllowedTrip") + .withCarsAllowed(CarAccess.ALLOWED) + .build() + ) + .withDepartureTimes("00:00 01:00") + .build() + ) + ) + .build() + ); + + tripPattern( + TripPattern + .of(TimetableRepositoryForTest.id("TP4")) + .withRoute(route("R4", TransitMode.FERRY, agency)) + .withStopPattern(new StopPattern(List.of(st(S0), st(S13)))) + .withScheduledTimeTableBuilder(builder -> + builder.addTripTimes( + ScheduledTripTimes + .of() + .withTrip( + TimetableRepositoryForTest + .trip("carsAllowedTrip") + .withCarsAllowed(CarAccess.ALLOWED) + .build() + ) + .withDepartureTimes("00:00 01:00") + .build() + ) + ) + .build() + ); + + tripPattern( + TripPattern + .of(TimetableRepositoryForTest.id("TP5")) + .withRoute(route("R5", TransitMode.FERRY, agency)) + .withStopPattern(new StopPattern(List.of(st(S12), st(S22)))) + .withScheduledTimeTableBuilder(builder -> + builder.addTripTimes( + ScheduledTripTimes + .of() + .withTrip( + TimetableRepositoryForTest + .trip("carsAllowedTrip") + .withCarsAllowed(CarAccess.ALLOWED) + .build() + ) + .withDepartureTimes("00:00 01:00") + .build() + ) + ) + .build() + ); + } } } ); diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java index 261a40454f0..f6197e9b56c 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java @@ -165,6 +165,78 @@ void testIgnoreStopsWithMaxStops() { assertStopAtDistance(stopC, 200, sortedNearbyStops.get(0)); } + @Test + void testFindOnlyVerticesStops() { + var durationLimit = Duration.ofMinutes(10); + var maxStopCount = 0; + Set findOnlyStops = Set.of(stopB, stopC); + var finder = new StreetNearbyStopFinder( + durationLimit, + maxStopCount, + null, + Set.of(), + findOnlyStops + ); + + var sortedNearbyStops = sort( + finder.findNearbyStops(stopA, new RouteRequest(), new StreetRequest(), false) + ); + + assertThat(sortedNearbyStops).hasSize(3); + assertZeroDistanceStop(stopA, sortedNearbyStops.get(0)); + assertStopAtDistance(stopB, 100, sortedNearbyStops.get(1)); + assertStopAtDistance(stopC, 200, sortedNearbyStops.get(2)); + } + + @Test + void testFindOnlyVerticesStopsWithIgnore() { + var durationLimit = Duration.ofMinutes(10); + var maxStopCount = 0; + Set findOnlyStops = Set.of(stopB, stopC); + Set ignore = Set.of(stopB); + var finder = new StreetNearbyStopFinder( + durationLimit, + maxStopCount, + null, + ignore, + findOnlyStops + ); + + var sortedNearbyStops = sort( + finder.findNearbyStops(stopA, new RouteRequest(), new StreetRequest(), false) + ); + + assertThat(sortedNearbyStops).hasSize(2); + assertZeroDistanceStop(stopA, sortedNearbyStops.get(0)); + assertStopAtDistance(stopC, 200, sortedNearbyStops.get(1)); + } + + @Test + void testFindOnlyVerticesStopsWithDurationLimit() { + // If we only allow walk for 101 seconds and speed is 1 m/s we should only be able to reach + // one extra stop. + var durationLimit = Duration.ofSeconds(101); + var maxStopCount = 0; + Set findOnlyStops = Set.of(stopB, stopC); + var routeRequest = new RouteRequest() + .withPreferences(b -> b.withWalk(walkPreferences -> walkPreferences.withSpeed(1.0))); + + var finder = new StreetNearbyStopFinder( + durationLimit, + maxStopCount, + null, + Set.of(), + findOnlyStops + ); + var sortedNearbyStops = sort( + finder.findNearbyStops(stopA, routeRequest, new StreetRequest(), false) + ); + + assertThat(sortedNearbyStops).hasSize(2); + assertZeroDistanceStop(stopA, sortedNearbyStops.get(0)); + assertStopAtDistance(stopB, 100, sortedNearbyStops.get(1)); + } + private List sort(Collection stops) { return stops.stream().sorted(Comparator.comparing(x -> x.distance)).toList(); } From 2175e64b58f0cb36b398a45ee27e0306f3f80668 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 1 Nov 2024 13:13:50 +0200 Subject: [PATCH 018/195] Move if statement outside of for loop. --- .../graph_builder/module/DirectTransferGenerator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index fcf5ea80cfc..a7b9e269c63 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -166,7 +166,9 @@ public void buildGraph() { for (RouteRequest transferProfile : filteredTransferRequests) { findNearbyStops(nearbyStopFinder, ts0, transferProfile, stop, distinctTransfers); - if (OTPFeature.FlexRouting.isOn()) { + } + if (OTPFeature.FlexRouting.isOn()) { + for (RouteRequest transferProfile : filteredTransferRequests) { // This code is for finding transfers from AreaStops to Stops, transfers // from Stops to AreaStops and between Stops are already covered above. for (NearbyStop sd : nearbyStopFinder.findNearbyStops( @@ -189,6 +191,7 @@ public void buildGraph() { } } } + // This calculates transfers between stops that can use trips with cars. for (RouteRequest transferProfile : carsAllowedStopTransferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); From e10e0a2fae17e16cfebb1e888652a531e7fef493 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Thu, 7 Nov 2024 13:22:19 +0200 Subject: [PATCH 019/195] Use existing entrance class for walk steps --- .../mapping/RelativeDirectionMapper.java | 1 + .../restapi/model/ApiRelativeDirection.java | 1 + .../apis/gtfs/GtfsGraphQLIndex.java | 4 +- .../apis/gtfs/datafetchers/EntranceImpl.java | 53 +++++++++++++++++++ .../datafetchers/StepEntityTypeResolver.java | 2 +- .../apis/gtfs/datafetchers/stepImpl.java | 4 +- .../gtfs/generated/GraphQLDataFetchers.java | 9 ++-- .../apis/gtfs/generated/GraphQLTypes.java | 1 + .../apis/gtfs/mapping/DirectionMapper.java | 1 + .../opentripplanner/model/plan/Entrance.java | 20 ------- .../model/plan/RelativeDirection.java | 1 + .../model/plan/StepEntity.java | 3 -- .../opentripplanner/model/plan/WalkStep.java | 14 ++--- .../model/plan/WalkStepBuilder.java | 9 ++-- .../mapping/StatesToWalkStepsMapper.java | 17 ++++-- .../framework/AbstractTransitEntity.java | 2 +- .../opentripplanner/apis/gtfs/schema.graphqls | 12 ++--- .../apis/gtfs/GraphQLIntegrationTest.java | 14 +++-- .../apis/gtfs/expectations/walk-steps.json | 11 ++-- .../apis/gtfs/queries/walk-steps.graphql | 9 ++-- 20 files changed, 115 insertions(+), 73 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java delete mode 100644 application/src/main/java/org/opentripplanner/model/plan/Entrance.java delete mode 100644 application/src/main/java/org/opentripplanner/model/plan/StepEntity.java diff --git a/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/RelativeDirectionMapper.java b/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/RelativeDirectionMapper.java index a1bdd145a55..708da1fd6c3 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/RelativeDirectionMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/RelativeDirectionMapper.java @@ -25,6 +25,7 @@ public static ApiRelativeDirection mapRelativeDirection(RelativeDirection domain case UTURN_RIGHT -> ApiRelativeDirection.UTURN_RIGHT; case ENTER_STATION -> ApiRelativeDirection.ENTER_STATION; case EXIT_STATION -> ApiRelativeDirection.EXIT_STATION; + case ENTER_OR_EXIT_STATION -> ApiRelativeDirection.ENTER_OR_EXIT_STATION; case FOLLOW_SIGNS -> ApiRelativeDirection.FOLLOW_SIGNS; }; } diff --git a/application/src/ext/java/org/opentripplanner/ext/restapi/model/ApiRelativeDirection.java b/application/src/ext/java/org/opentripplanner/ext/restapi/model/ApiRelativeDirection.java index 02a530f06de..eb624df5ea6 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/model/ApiRelativeDirection.java +++ b/application/src/ext/java/org/opentripplanner/ext/restapi/model/ApiRelativeDirection.java @@ -21,5 +21,6 @@ public enum ApiRelativeDirection { UTURN_RIGHT, ENTER_STATION, EXIT_STATION, + ENTER_OR_EXIT_STATION, FOLLOW_SIGNS, } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 5b288762262..a5eedb4c71c 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -37,6 +37,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.CurrencyImpl; import org.opentripplanner.apis.gtfs.datafetchers.DefaultFareProductImpl; import org.opentripplanner.apis.gtfs.datafetchers.DepartureRowImpl; +import org.opentripplanner.apis.gtfs.datafetchers.EntranceImpl; import org.opentripplanner.apis.gtfs.datafetchers.FareProductTypeResolver; import org.opentripplanner.apis.gtfs.datafetchers.FareProductUseImpl; import org.opentripplanner.apis.gtfs.datafetchers.FeedImpl; @@ -58,7 +59,6 @@ import org.opentripplanner.apis.gtfs.datafetchers.RouteImpl; import org.opentripplanner.apis.gtfs.datafetchers.RouteTypeImpl; import org.opentripplanner.apis.gtfs.datafetchers.RoutingErrorImpl; -import org.opentripplanner.apis.gtfs.datafetchers.StepEntityTypeResolver; import org.opentripplanner.apis.gtfs.datafetchers.StopGeometriesImpl; import org.opentripplanner.apis.gtfs.datafetchers.StopImpl; import org.opentripplanner.apis.gtfs.datafetchers.StopOnRouteImpl; @@ -125,7 +125,6 @@ protected static GraphQLSchema buildSchema() { .type("Node", type -> type.typeResolver(new NodeTypeResolver())) .type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver())) .type("StopPosition", type -> type.typeResolver(new StopPosition() {})) - .type("StepEntity", type -> type.typeResolver(new StepEntityTypeResolver())) .type("FareProduct", type -> type.typeResolver(new FareProductTypeResolver())) .type("AlertEntity", type -> type.typeResolver(new AlertEntityTypeResolver())) .type(typeWiring.build(AgencyImpl.class)) @@ -181,6 +180,7 @@ protected static GraphQLSchema buildSchema() { .type(typeWiring.build(FareProductUseImpl.class)) .type(typeWiring.build(DefaultFareProductImpl.class)) .type(typeWiring.build(TripOccupancyImpl.class)) + .type(typeWiring.build(EntranceImpl.class)) .build(); SchemaGenerator schemaGenerator = new SchemaGenerator(); return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java new file mode 100644 index 00000000000..89f80286eed --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java @@ -0,0 +1,53 @@ +package org.opentripplanner.apis.gtfs.datafetchers; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import org.opentripplanner.apis.gtfs.GraphQLUtils; +import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.transit.model.site.Entrance; + +public class EntranceImpl implements GraphQLDataFetchers.GraphQLEntrance { + + @Override + public DataFetcher code() { + return environment -> { + Entrance entrance = getEntrance(environment); + return entrance != null && entrance.getCode() != null ? entrance.getCode() : null; + }; + } + + @Override + public DataFetcher gtfsId() { + return environment -> { + Entrance entrance = getEntrance(environment); + return entrance != null && entrance.getId() != null ? entrance.getId().toString() : null; + }; + } + + @Override + public DataFetcher name() { + return environment -> { + Entrance entrance = getEntrance(environment); + return entrance != null && entrance.getName() != null ? entrance.getName().toString() : null; + }; + } + + @Override + public DataFetcher wheelchairAccessible() { + return environment -> { + Entrance entrance = getEntrance(environment); + return entrance != null + ? GraphQLUtils.toGraphQL(entrance.getWheelchairAccessibility()) + : null; + }; + } + + /** + * Helper method to retrieve the Entrance object from the DataFetchingEnvironment. + */ + private Entrance getEntrance(DataFetchingEnvironment environment) { + Object source = environment.getSource(); + return source instanceof Entrance ? (Entrance) source : null; + } +} diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java index 5e7d6098344..43762c97a7d 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java @@ -4,7 +4,7 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import graphql.schema.TypeResolver; -import org.opentripplanner.model.plan.Entrance; +import org.opentripplanner.transit.model.site.Entrance; public class StepEntityTypeResolver implements TypeResolver { diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java index 658f2d321ad..994434b4e4c 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java @@ -54,8 +54,8 @@ public DataFetcher exit() { } @Override - public DataFetcher entity() { - return environment -> getSource(environment).getEntity(); + public DataFetcher entrance() { + return environment -> getSource(environment).getEntrance(); } @Override diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index ec0e573d705..f99fceb410c 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -359,13 +359,13 @@ public interface GraphQLEmissions { /** Station entrance or exit. */ public interface GraphQLEntrance { - public DataFetcher accessible(); - public DataFetcher code(); public DataFetcher gtfsId(); public DataFetcher name(); + + public DataFetcher wheelchairAccessible(); } /** A 'medium' that a fare product applies to, for example cash, 'Oyster Card' or 'DB Navigator App'. */ @@ -984,9 +984,6 @@ public interface GraphQLRoutingError { public DataFetcher inputField(); } - /** Entity to a step */ - public interface GraphQLStepEntity extends TypeResolver {} - /** * Stop can represent either a single public transport stop, where passengers can * board and/or disembark vehicles, or a station, which contains multiple stops. @@ -1438,7 +1435,7 @@ public interface GraphQLStep { public DataFetcher> elevationProfile(); - public DataFetcher entity(); + public DataFetcher entrance(); public DataFetcher exit(); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index bf413f5fca4..9e4119100c0 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -3942,6 +3942,7 @@ public enum GraphQLRelativeDirection { CONTINUE, DEPART, ELEVATOR, + ENTER_OR_EXIT_STATION, ENTER_STATION, EXIT_STATION, FOLLOW_SIGNS, diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapper.java index 69a78b05f55..1439cdd34c3 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapper.java @@ -38,6 +38,7 @@ public static GraphQLRelativeDirection map(RelativeDirection relativeDirection) case UTURN_RIGHT -> GraphQLRelativeDirection.UTURN_RIGHT; case ENTER_STATION -> GraphQLRelativeDirection.ENTER_STATION; case EXIT_STATION -> GraphQLRelativeDirection.EXIT_STATION; + case ENTER_OR_EXIT_STATION -> GraphQLRelativeDirection.ENTER_OR_EXIT_STATION; case FOLLOW_SIGNS -> GraphQLRelativeDirection.FOLLOW_SIGNS; }; } diff --git a/application/src/main/java/org/opentripplanner/model/plan/Entrance.java b/application/src/main/java/org/opentripplanner/model/plan/Entrance.java deleted file mode 100644 index fa1e0fe5e57..00000000000 --- a/application/src/main/java/org/opentripplanner/model/plan/Entrance.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.opentripplanner.model.plan; - -public final class Entrance extends StepEntity { - - private final String code; - private final String gtfsId; - private final String name; - private final boolean accessible; - - public Entrance(String code, String gtfsId, String name, boolean accessible) { - this.code = code; - this.gtfsId = gtfsId; - this.name = name; - this.accessible = accessible; - } - - public static Entrance withCodeAndAccessible(String code, boolean accessible) { - return new Entrance(code, null, null, accessible); - } -} 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..fbdb836ab6a 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,7 @@ public enum RelativeDirection { UTURN_RIGHT, ENTER_STATION, EXIT_STATION, + ENTER_OR_EXIT_STATION, FOLLOW_SIGNS; public static RelativeDirection calculate( diff --git a/application/src/main/java/org/opentripplanner/model/plan/StepEntity.java b/application/src/main/java/org/opentripplanner/model/plan/StepEntity.java deleted file mode 100644 index e6bfd587bfc..00000000000 --- a/application/src/main/java/org/opentripplanner/model/plan/StepEntity.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.opentripplanner.model.plan; - -public abstract class StepEntity {} 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 8114246d7e8..1f72a2960df 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java +++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java @@ -8,9 +8,9 @@ import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.lang.DoubleUtils; import org.opentripplanner.framework.tostring.ToStringBuilder; -import org.opentripplanner.model.plan.StepEntity; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.note.StreetNote; +import org.opentripplanner.transit.model.site.Entrance; /** * Represents one instruction in walking directions. Three examples from New York City: @@ -45,7 +45,7 @@ public final class WalkStep { private final boolean walkingBike; private final String exit; - private final StepEntity entity; + private final Entrance entrance; private final ElevationProfile elevationProfile; private final boolean stayOn; @@ -58,7 +58,7 @@ public final class WalkStep { I18NString directionText, Set streetNotes, String exit, - StepEntity entity, + Entrance entrance, ElevationProfile elevationProfile, boolean bogusName, boolean walkingBike, @@ -79,7 +79,7 @@ public final class WalkStep { this.walkingBike = walkingBike; this.area = area; this.exit = exit; - this.entity = entity; + this.entrance = entrance; this.elevationProfile = elevationProfile; this.stayOn = stayOn; this.edges = List.copyOf(Objects.requireNonNull(edges)); @@ -135,10 +135,10 @@ public String getExit() { } /** - * Entity related to a step e.g. building entrance/exit. + * Get information about building entrance or exit. */ - public Object getEntity() { - return entity; + public Entrance getEntrance() { + return 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 02b73c0ce15..b8055125051 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java +++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java @@ -9,10 +9,9 @@ import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.lang.DoubleUtils; import org.opentripplanner.framework.lang.IntUtils; -import org.opentripplanner.model.plan.Entrance; -import org.opentripplanner.model.plan.StepEntity; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.note.StreetNote; +import org.opentripplanner.transit.model.site.Entrance; public class WalkStepBuilder { @@ -27,7 +26,7 @@ public class WalkStepBuilder { private RelativeDirection relativeDirection; private ElevationProfile elevationProfile; private String exit; - private StepEntity entity; + private Entrance entrance; private boolean stayOn = false; /** * Distance used for appending elevation profiles @@ -78,7 +77,7 @@ public WalkStepBuilder withExit(String exit) { } public WalkStepBuilder withEntrance(Entrance entrance) { - this.entity = entrance; + this.entrance = entrance; return this; } @@ -164,7 +163,7 @@ public WalkStep build() { directionText, streetNotes, exit, - entity, + entrance, elevationProfile, bogusName, walkingBike, 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 14ed2a553e4..67b76578d9b 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 @@ -13,7 +13,6 @@ import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.model.plan.ElevationProfile; -import org.opentripplanner.model.plan.Entrance; import org.opentripplanner.model.plan.RelativeDirection; import org.opentripplanner.model.plan.WalkStep; import org.opentripplanner.model.plan.WalkStepBuilder; @@ -30,6 +29,8 @@ 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.basic.Accessibility; +import org.opentripplanner.transit.model.site.Entrance; /** * Process a list of states into a list of walking/driving instructions for a street leg. @@ -528,10 +529,20 @@ private WalkStepBuilder createStationEntranceWalkStep( // don't care what came before or comes after var step = createWalkStep(forwardState, backState); - step.withRelativeDirection(RelativeDirection.CONTINUE); + step.withRelativeDirection(RelativeDirection.ENTER_OR_EXIT_STATION); StationEntranceVertex vertex = (StationEntranceVertex) backState.getVertex(); - step.withEntrance(Entrance.withCodeAndAccessible(vertex.getCode(), vertex.isAccessible())); + + Entrance entrance = Entrance + .of(null) + .withCode(vertex.getCode()) + .withCoordinate(new WgsCoordinate(vertex.getCoordinate())) + .withWheelchairAccessibility( + vertex.isAccessible() ? Accessibility.POSSIBLE : Accessibility.NOT_POSSIBLE + ) + .build(); + + step.withEntrance(entrance); return step; } diff --git a/application/src/main/java/org/opentripplanner/transit/model/framework/AbstractTransitEntity.java b/application/src/main/java/org/opentripplanner/transit/model/framework/AbstractTransitEntity.java index 258d505b53c..6f233aa9b31 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/framework/AbstractTransitEntity.java +++ b/application/src/main/java/org/opentripplanner/transit/model/framework/AbstractTransitEntity.java @@ -30,7 +30,7 @@ public abstract class AbstractTransitEntity< private final FeedScopedId id; public AbstractTransitEntity(FeedScopedId id) { - this.id = Objects.requireNonNull(id); + this.id = id; // TODO IS THIS WORNG } public final FeedScopedId getId() { 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 83c32e2c1ca..de1a748c1e9 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -73,9 +73,6 @@ interface PlaceInterface { "Entity related to an alert" union AlertEntity = Agency | Pattern | Route | RouteType | Stop | StopOnRoute | StopOnTrip | Trip | Unknown -"Entity to a step" -union StepEntity = Entrance - union StopPosition = PositionAtStop | PositionBetweenStops "A public transport agency" @@ -449,14 +446,14 @@ type Emissions { "Station entrance or exit." type Entrance { - "True if the entrance is wheelchair accessible." - accessible: Boolean "Short text or a number that identifies the entrance or exit for passengers. For example, `A` or `B`." code: String "ID of the entrance in the format of `FeedId:EntranceId`." gtfsId: String "Name of the entrance or exit." name: String + "Whether the entrance or exit is accessible by wheelchair" + wheelchairAccessible: WheelchairBoarding } "A 'medium' that a fare product applies to, for example cash, 'Oyster Card' or 'DB Navigator App'." @@ -2660,8 +2657,8 @@ type step { distance: Float "The elevation profile as a list of { distance, elevation } values." elevationProfile: [elevationProfileComponent] - "Step entity, e.g. an entrance." - entity: StepEntity + "Information about an station entrance or exit" + entrance: Entrance "When exiting a highway or traffic circle, the exit name/number." exit: String "The latitude of the start of the step." @@ -3329,6 +3326,7 @@ enum RelativeDirection { CONTINUE DEPART ELEVATOR + ENTER_OR_EXIT_STATION ENTER_STATION EXIT_STATION FOLLOW_SIGNS 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 d2448a92c59..a672ff3db3f 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -49,7 +49,6 @@ import org.opentripplanner.model.fare.ItineraryFares; import org.opentripplanner.model.fare.RiderCategory; import org.opentripplanner.model.plan.Emissions; -import org.opentripplanner.model.plan.Entrance; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.RelativeDirection; @@ -81,6 +80,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; @@ -90,6 +90,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; @@ -232,10 +233,15 @@ public Set getRoutesForStop(StopLocation stop) { .withAbsoluteDirection(20) .build(); var step2 = walkStep("elevator").withRelativeDirection(RelativeDirection.ELEVATOR).build(); - + Entrance entrance = Entrance + .of(null) + .withCoordinate(new WgsCoordinate(60, 80)) + .withCode("A") + .withWheelchairAccessibility(Accessibility.POSSIBLE) + .build(); var step3 = walkStep("entrance") - .withRelativeDirection(RelativeDirection.CONTINUE) - .withEntrance(Entrance.withCodeAndAccessible("A", true)) + .withRelativeDirection(RelativeDirection.ENTER_OR_EXIT_STATION) + .withEntrance(entrance) .build(); Itinerary i1 = newItinerary(A, T11_00) 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 3a8e952880f..7cb304d6bb1 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 @@ -12,25 +12,24 @@ "area" : false, "relativeDirection" : "DEPART", "absoluteDirection" : "NORTHEAST", - "entity" : null + "entrance" : null }, { "streetName" : "elevator", "area" : false, "relativeDirection" : "ELEVATOR", "absoluteDirection" : null, - "entity" : null + "entrance" : null }, { "streetName" : "entrance", "area" : false, - "relativeDirection" : "CONTINUE", + "relativeDirection" : "ENTER_OR_EXIT_STATION", "absoluteDirection" : null, - "entity" : { - "__typename" : "Entrance", + "entrance": { "code": "A", - "accessible": true + "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 5e7c0493c35..c7de3f22ee1 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,12 +20,9 @@ area relativeDirection absoluteDirection - entity { - __typename - ... on Entrance { - code - accessible - } + entrance { + code + wheelchairAccessible } } } From 34761fe1b48d7ec93f735785a58f13e3b993e100 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Thu, 7 Nov 2024 14:41:35 +0200 Subject: [PATCH 020/195] Fix EntranceImpl --- .../apis/gtfs/datafetchers/EntranceImpl.java | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java index 89f80286eed..16cb3f5f6e7 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java @@ -1,7 +1,6 @@ package org.opentripplanner.apis.gtfs.datafetchers; import graphql.schema.DataFetcher; -import graphql.schema.DataFetchingEnvironment; import org.opentripplanner.apis.gtfs.GraphQLUtils; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; @@ -12,42 +11,37 @@ public class EntranceImpl implements GraphQLDataFetchers.GraphQLEntrance { @Override public DataFetcher code() { return environment -> { - Entrance entrance = getEntrance(environment); - return entrance != null && entrance.getCode() != null ? entrance.getCode() : null; + Entrance entrance = environment.getSource(); + return entrance.getCode(); }; } @Override public DataFetcher gtfsId() { return environment -> { - Entrance entrance = getEntrance(environment); - return entrance != null && entrance.getId() != null ? entrance.getId().toString() : null; + Entrance entrance = environment.getSource(); + return entrance.getId() != null ? entrance.getId().toString() : null; }; } @Override public DataFetcher name() { return environment -> { - Entrance entrance = getEntrance(environment); - return entrance != null && entrance.getName() != null ? entrance.getName().toString() : null; + Entrance entrance = environment.getSource(); + return entrance.getName() != null + ? org.opentripplanner.framework.graphql.GraphQLUtils.getTranslation( + entrance.getName(), + environment + ) + : null; }; } @Override public DataFetcher wheelchairAccessible() { return environment -> { - Entrance entrance = getEntrance(environment); - return entrance != null - ? GraphQLUtils.toGraphQL(entrance.getWheelchairAccessibility()) - : null; + Entrance entrance = environment.getSource(); + return GraphQLUtils.toGraphQL(entrance.getWheelchairAccessibility()); }; } - - /** - * Helper method to retrieve the Entrance object from the DataFetchingEnvironment. - */ - private Entrance getEntrance(DataFetchingEnvironment environment) { - Object source = environment.getSource(); - return source instanceof Entrance ? (Entrance) source : null; - } } From c84b7cf878974075ccd0049d813b33f94c9bf2e3 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Fri, 8 Nov 2024 10:09:53 +0200 Subject: [PATCH 021/195] Add id to walk step entrances --- .../opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java | 2 +- .../apis/gtfs/generated/GraphQLDataFetchers.java | 2 +- .../routing/algorithm/mapping/StatesToWalkStepsMapper.java | 5 ++++- .../street/model/vertex/StationEntranceVertex.java | 4 ++++ .../transit/model/framework/AbstractTransitEntity.java | 2 +- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- .../opentripplanner/apis/gtfs/GraphQLIntegrationTest.java | 3 ++- .../opentripplanner/apis/gtfs/expectations/walk-steps.json | 3 ++- .../org/opentripplanner/apis/gtfs/queries/walk-steps.graphql | 1 + 9 files changed, 17 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java index 16cb3f5f6e7..c062f62d07a 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java @@ -17,7 +17,7 @@ public DataFetcher code() { } @Override - public DataFetcher gtfsId() { + public DataFetcher entranceId() { return environment -> { Entrance entrance = environment.getSource(); return entrance.getId() != null ? entrance.getId().toString() : null; diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index f99fceb410c..0d547b4cf10 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -361,7 +361,7 @@ public interface GraphQLEmissions { public interface GraphQLEntrance { public DataFetcher code(); - public DataFetcher gtfsId(); + public DataFetcher entranceId(); public DataFetcher name(); 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 67b76578d9b..81f6ffcb461 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 @@ -30,6 +30,7 @@ import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.street.search.state.State; import org.opentripplanner.transit.model.basic.Accessibility; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.Entrance; /** @@ -533,8 +534,10 @@ private WalkStepBuilder createStationEntranceWalkStep( StationEntranceVertex vertex = (StationEntranceVertex) backState.getVertex(); + FeedScopedId entranceId = new FeedScopedId("osm", vertex.getId()); + Entrance entrance = Entrance - .of(null) + .of(entranceId) .withCode(vertex.getCode()) .withCoordinate(new WgsCoordinate(vertex.getCoordinate())) .withWheelchairAccessibility( 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 index 3f83a356dea..e55ac7078db 100644 --- a/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java +++ b/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java @@ -19,6 +19,10 @@ public boolean isAccessible() { return accessible; } + public String getId() { + return Long.toString(nodeId); + } + public String toString() { return "StationEntranceVertex(" + super.toString() + ", code=" + code + ")"; } diff --git a/application/src/main/java/org/opentripplanner/transit/model/framework/AbstractTransitEntity.java b/application/src/main/java/org/opentripplanner/transit/model/framework/AbstractTransitEntity.java index 6f233aa9b31..258d505b53c 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/framework/AbstractTransitEntity.java +++ b/application/src/main/java/org/opentripplanner/transit/model/framework/AbstractTransitEntity.java @@ -30,7 +30,7 @@ public abstract class AbstractTransitEntity< private final FeedScopedId id; public AbstractTransitEntity(FeedScopedId id) { - this.id = id; // TODO IS THIS WORNG + this.id = Objects.requireNonNull(id); } public final FeedScopedId getId() { 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 de1a748c1e9..b4a5ce6da15 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -449,7 +449,7 @@ type Entrance { "Short text or a number that identifies the entrance or exit for passengers. For example, `A` or `B`." code: String "ID of the entrance in the format of `FeedId:EntranceId`." - gtfsId: String + entranceId: String "Name of the entrance or exit." name: String "Whether the entrance or exit is accessible by wheelchair" 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 a672ff3db3f..d67038ec3f6 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -233,8 +233,9 @@ public Set getRoutesForStop(StopLocation stop) { .withAbsoluteDirection(20) .build(); var step2 = walkStep("elevator").withRelativeDirection(RelativeDirection.ELEVATOR).build(); + FeedScopedId entranceId = new FeedScopedId("osm", "123"); Entrance entrance = Entrance - .of(null) + .of(entranceId) .withCoordinate(new WgsCoordinate(60, 80)) .withCode("A") .withWheelchairAccessibility(Accessibility.POSSIBLE) 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 7cb304d6bb1..a0a781153f1 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 @@ -29,7 +29,8 @@ "absoluteDirection" : null, "entrance": { "code": "A", - "wheelchairAccessible": "POSSIBLE" + "wheelchairAccessible": "POSSIBLE", + "entranceId": "osm:123" } } ] 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 c7de3f22ee1..45e2eed904a 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 @@ -23,6 +23,7 @@ entrance { code wheelchairAccessible + entranceId } } } From 2ea8a522e6616f2832b7410ed896c6434758c238 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Fri, 8 Nov 2024 10:37:19 +0200 Subject: [PATCH 022/195] Remove old file --- .../datafetchers/StepEntityTypeResolver.java | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java deleted file mode 100644 index 43762c97a7d..00000000000 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepEntityTypeResolver.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.opentripplanner.apis.gtfs.datafetchers; - -import graphql.TypeResolutionEnvironment; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLSchema; -import graphql.schema.TypeResolver; -import org.opentripplanner.transit.model.site.Entrance; - -public class StepEntityTypeResolver implements TypeResolver { - - @Override - public GraphQLObjectType getType(TypeResolutionEnvironment environment) { - Object o = environment.getObject(); - GraphQLSchema schema = environment.getSchema(); - - if (o instanceof Entrance) { - return schema.getObjectType("Entrance"); - } - return null; - } -} From 39b0db37f86b28fe5f03f7da2716f47bdbfed3cc Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Fri, 8 Nov 2024 10:38:59 +0200 Subject: [PATCH 023/195] Fix otp version --- .../standalone/config/buildconfig/OsmConfig.java | 3 ++- doc/user/BuildConfiguration.md | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) 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 c1a3963c6a5..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; @@ -88,7 +89,7 @@ public static OsmExtractParametersBuilder mapOsmGenericParameters( .withIncludeOsmSubwayEntrances( node .of("includeOsmSubwayEntrances") - .since(V2_2) + .since(V2_7) .summary("Whether to include subway entrances from the OSM data." + documentationAddition) .docDefaultValue(docDefaults.includeOsmSubwayEntrances()) .asBoolean(defaults.includeOsmSubwayEntrances()) diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index 8b38da6fdbb..c5fdfa8095b 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -84,12 +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.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.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 | From 3b6bf3fbab24a97c67957effb7838b8383b652ff Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Fri, 8 Nov 2024 10:39:31 +0200 Subject: [PATCH 024/195] Remove unused import --- .../org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 9e4119100c0..32e1fc69870 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -1,7 +1,6 @@ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. package org.opentripplanner.apis.gtfs.generated; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; From c9df52d9e8e3a2359ef39c64e78295230090ff2e Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Sun, 10 Nov 2024 17:02:44 +0200 Subject: [PATCH 025/195] Require entranceId --- .../opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java | 2 +- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java index c062f62d07a..d7e2e6aa3ac 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java @@ -20,7 +20,7 @@ public DataFetcher code() { public DataFetcher entranceId() { return environment -> { Entrance entrance = environment.getSource(); - return entrance.getId() != null ? entrance.getId().toString() : null; + return entrance.getId().toString(); }; } 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 80094ecfd3f..c7cb1a6e346 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -449,7 +449,7 @@ type Entrance { "Short text or a number that identifies the entrance or exit for passengers. For example, `A` or `B`." code: String "ID of the entrance in the format of `FeedId:EntranceId`." - entranceId: String + entranceId: String! "Name of the entrance or exit." name: String "Whether the entrance or exit is accessible by wheelchair" From 7a9a8f694ad29a782827605bdc70e7cd8adea370 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Sun, 10 Nov 2024 17:59:58 +0200 Subject: [PATCH 026/195] Rename methods --- .../opentripplanner/ext/restapi/mapping/WalkStepMapper.java | 2 +- .../opentripplanner/apis/gtfs/datafetchers/stepImpl.java | 4 ++-- .../apis/transmodel/model/plan/PathGuidanceType.java | 2 +- .../main/java/org/opentripplanner/model/plan/WalkStep.java | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java b/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java index c5c59e4dc37..1df3d7e6fa7 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java @@ -39,7 +39,7 @@ public ApiWalkStep mapWalkStep(WalkStep domain) { api.streetName = domain.getDirectionText().toString(locale); api.absoluteDirection = domain.getAbsoluteDirection().map(AbsoluteDirectionMapper::mapAbsoluteDirection).orElse(null); - api.exit = domain.getExit(); + api.exit = domain.getHighwayExit(); api.stayOn = domain.isStayOn(); api.area = domain.getArea(); api.bogusName = domain.getBogusName(); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java index 994434b4e4c..4414fc7b4cd 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java @@ -50,12 +50,12 @@ public DataFetcher> elevationProfile() { @Override public DataFetcher exit() { - return environment -> getSource(environment).getExit(); + return environment -> getSource(environment).getHighwayExit(); } @Override public DataFetcher entrance() { - return environment -> getSource(environment).getEntrance(); + return environment -> getSource(environment).getStationEntrance(); } @Override diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java index c52baa0be4c..12eaa089a50 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java @@ -65,7 +65,7 @@ public static GraphQLObjectType create(GraphQLObjectType elevationStepType) { .name("exit") .description("When exiting a highway or traffic circle, the exit name/number.") .type(Scalars.GraphQLString) - .dataFetcher(environment -> ((WalkStep) environment.getSource()).getExit()) + .dataFetcher(environment -> ((WalkStep) environment.getSource()).getHighwayExit()) .build() ) .field( 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 9e055e33559..96bde6d1ef3 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java +++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java @@ -130,14 +130,14 @@ public Optional getAbsoluteDirection() { /** * When exiting a highway or traffic circle, the exit name/number. */ - public String getExit() { + public String getHighwayExit() { return exit; } /** - * Get information about building entrance or exit. + * Get information about a subway station entrance or exit. */ - public Entrance getEntrance() { + public Entrance getStationEntrance() { return entrance; } From c9139e31f60662e7cae7cf35ace311ec9adc1d37 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Mon, 11 Nov 2024 15:04:33 +0200 Subject: [PATCH 027/195] Update dosumentation --- .../routing/algorithm/mapping/StatesToWalkStepsMapper.java | 3 ++- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) 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 81f6ffcb461..b0df9658301 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 @@ -529,7 +529,8 @@ private WalkStepBuilder createStationEntranceWalkStep( ) { // don't care what came before or comes after var step = 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. step.withRelativeDirection(RelativeDirection.ENTER_OR_EXIT_STATION); StationEntranceVertex vertex = (StationEntranceVertex) backState.getVertex(); 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 c7cb1a6e346..13b576a46c7 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -444,11 +444,11 @@ type Emissions { co2: Grams } -"Station entrance or exit." +"Station entrance or exit, originating from OSM or GTFS data." type Entrance { "Short text or a number that identifies the entrance or exit for passengers. For example, `A` or `B`." code: String - "ID of the entrance in the format of `FeedId:EntranceId`." + "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 From 7b210246ee0c5be3b54b4eff66722ffdd7e39e90 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Mon, 11 Nov 2024 15:18:55 +0200 Subject: [PATCH 028/195] Update documentation --- .../routing/algorithm/mapping/StatesToWalkStepsMapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b0df9658301..39941b72ecd 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 @@ -529,7 +529,7 @@ private WalkStepBuilder createStationEntranceWalkStep( ) { // don't care what came before or comes after var step = createWalkStep(forwardState, backState); - // There is not a way to definitively determine if a user is entering or exiting the station, + // 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. step.withRelativeDirection(RelativeDirection.ENTER_OR_EXIT_STATION); From ce7719cf50c103b24a4a25530ec9be29aaa67d5a Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Mon, 11 Nov 2024 15:20:23 +0200 Subject: [PATCH 029/195] Remove redundant null check --- .../apis/gtfs/datafetchers/EntranceImpl.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java index d7e2e6aa3ac..9891d107479 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java @@ -28,12 +28,10 @@ public DataFetcher entranceId() { public DataFetcher name() { return environment -> { Entrance entrance = environment.getSource(); - return entrance.getName() != null - ? org.opentripplanner.framework.graphql.GraphQLUtils.getTranslation( - entrance.getName(), - environment - ) - : null; + return org.opentripplanner.framework.graphql.GraphQLUtils.getTranslation( + entrance.getName(), + environment + ); }; } From b73741100d9cb5a6a3691ded6976691880ed7682 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Thu, 14 Nov 2024 09:49:28 +0200 Subject: [PATCH 030/195] Add feature union to steps --- .../apis/gtfs/GtfsGraphQLIndex.java | 2 ++ .../apis/gtfs/datafetchers/EntranceImpl.java | 13 +++++++--- .../datafetchers/StepFeatureTypeResolver.java | 25 +++++++++++++++++++ .../apis/gtfs/datafetchers/stepImpl.java | 4 +-- .../gtfs/generated/GraphQLDataFetchers.java | 8 +++--- .../apis/gtfs/model/StepFeature.java | 20 +++++++++++++++ .../opentripplanner/model/plan/WalkStep.java | 14 +++++------ .../model/plan/WalkStepBuilder.java | 7 +++--- .../opentripplanner/apis/gtfs/schema.graphqls | 7 ++++-- .../apis/gtfs/expectations/walk-steps.json | 11 ++++---- .../apis/gtfs/queries/walk-steps.graphql | 11 +++++--- 11 files changed, 92 insertions(+), 30 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepFeatureTypeResolver.java create mode 100644 application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index a5eedb4c71c..22e8f2e6fa7 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -59,6 +59,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.RouteImpl; import org.opentripplanner.apis.gtfs.datafetchers.RouteTypeImpl; import org.opentripplanner.apis.gtfs.datafetchers.RoutingErrorImpl; +import org.opentripplanner.apis.gtfs.datafetchers.StepFeatureTypeResolver; import org.opentripplanner.apis.gtfs.datafetchers.StopGeometriesImpl; import org.opentripplanner.apis.gtfs.datafetchers.StopImpl; import org.opentripplanner.apis.gtfs.datafetchers.StopOnRouteImpl; @@ -127,6 +128,7 @@ protected static GraphQLSchema buildSchema() { .type("StopPosition", type -> type.typeResolver(new StopPosition() {})) .type("FareProduct", type -> type.typeResolver(new FareProductTypeResolver())) .type("AlertEntity", type -> type.typeResolver(new AlertEntityTypeResolver())) + .type("StepFeature", type -> type.typeResolver(new StepFeatureTypeResolver())) .type(typeWiring.build(AgencyImpl.class)) .type(typeWiring.build(AlertImpl.class)) .type(typeWiring.build(BikeParkImpl.class)) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java index 9891d107479..bcf37d42cc7 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java @@ -4,6 +4,7 @@ import org.opentripplanner.apis.gtfs.GraphQLUtils; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.model.StepFeature; import org.opentripplanner.transit.model.site.Entrance; public class EntranceImpl implements GraphQLDataFetchers.GraphQLEntrance { @@ -11,7 +12,8 @@ public class EntranceImpl implements GraphQLDataFetchers.GraphQLEntrance { @Override public DataFetcher code() { return environment -> { - Entrance entrance = environment.getSource(); + StepFeature feature = environment.getSource(); + Entrance entrance = (Entrance) feature.getFeature(); return entrance.getCode(); }; } @@ -19,7 +21,8 @@ public DataFetcher code() { @Override public DataFetcher entranceId() { return environment -> { - Entrance entrance = environment.getSource(); + StepFeature feature = environment.getSource(); + Entrance entrance = (Entrance) feature.getFeature(); return entrance.getId().toString(); }; } @@ -27,7 +30,8 @@ public DataFetcher entranceId() { @Override public DataFetcher name() { return environment -> { - Entrance entrance = environment.getSource(); + StepFeature feature = environment.getSource(); + Entrance entrance = (Entrance) feature.getFeature(); return org.opentripplanner.framework.graphql.GraphQLUtils.getTranslation( entrance.getName(), environment @@ -38,7 +42,8 @@ public DataFetcher name() { @Override public DataFetcher wheelchairAccessible() { return environment -> { - Entrance entrance = environment.getSource(); + StepFeature feature = environment.getSource(); + Entrance entrance = (Entrance) feature.getFeature(); return GraphQLUtils.toGraphQL(entrance.getWheelchairAccessibility()); }; } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepFeatureTypeResolver.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepFeatureTypeResolver.java new file mode 100644 index 00000000000..8748d87700d --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepFeatureTypeResolver.java @@ -0,0 +1,25 @@ +package org.opentripplanner.apis.gtfs.datafetchers; + +import graphql.TypeResolutionEnvironment; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLSchema; +import graphql.schema.TypeResolver; +import org.opentripplanner.apis.gtfs.model.StepFeature; +import org.opentripplanner.transit.model.site.Entrance; + +public class StepFeatureTypeResolver implements TypeResolver { + + @Override + public GraphQLObjectType getType(TypeResolutionEnvironment environment) { + Object o = environment.getObject(); + GraphQLSchema schema = environment.getSchema(); + + if (o instanceof StepFeature) { + Object feature = ((StepFeature) o).getFeature(); + if (feature instanceof Entrance) { + return schema.getObjectType("Entrance"); + } + } + return null; + } +} diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java index 4414fc7b4cd..74453d6e7c4 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java @@ -54,8 +54,8 @@ public DataFetcher exit() { } @Override - public DataFetcher entrance() { - return environment -> getSource(environment).getStationEntrance(); + public DataFetcher feature() { + return environment -> getSource(environment).getStepFeature(); } @Override diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 0d547b4cf10..b7889c8be3a 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -357,7 +357,7 @@ public interface GraphQLEmissions { public DataFetcher co2(); } - /** Station entrance or exit. */ + /** Station entrance or exit, originating from OSM or GTFS data. */ public interface GraphQLEntrance { public DataFetcher code(); @@ -984,6 +984,8 @@ public interface GraphQLRoutingError { public DataFetcher inputField(); } + public interface GraphQLStepFeature extends TypeResolver {} + /** * Stop can represent either a single public transport stop, where passengers can * board and/or disembark vehicles, or a station, which contains multiple stops. @@ -1435,10 +1437,10 @@ public interface GraphQLStep { public DataFetcher> elevationProfile(); - public DataFetcher entrance(); - public DataFetcher exit(); + public DataFetcher feature(); + public DataFetcher lat(); public DataFetcher lon(); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java b/application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java new file mode 100644 index 00000000000..c4842e25476 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java @@ -0,0 +1,20 @@ +package org.opentripplanner.apis.gtfs.model; + +import org.opentripplanner.transit.model.site.Entrance; + +/** + * A generic wrapper class for features in Walk steps. + * At the moment only subway station entrances. + **/ +public class StepFeature { + + private final Object feature; + + public StepFeature(Entrance entrance) { + this.feature = entrance; + } + + public Object getFeature() { + return feature; + } +} 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 96bde6d1ef3..660b973a8a8 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java +++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java @@ -4,11 +4,11 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import org.opentripplanner.apis.gtfs.model.StepFeature; import org.opentripplanner.framework.geometry.WgsCoordinate; 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; @@ -45,7 +45,7 @@ public final class WalkStep { private final boolean walkingBike; private final String exit; - private final Entrance entrance; + private final StepFeature feature; private final ElevationProfile elevationProfile; private final boolean stayOn; @@ -58,7 +58,7 @@ public final class WalkStep { I18NString directionText, Set streetNotes, String exit, - Entrance entrance, + StepFeature feature, ElevationProfile elevationProfile, boolean bogusName, boolean walkingBike, @@ -79,7 +79,7 @@ public final class WalkStep { this.walkingBike = walkingBike; this.area = area; this.exit = exit; - this.entrance = entrance; + this.feature = feature; this.elevationProfile = elevationProfile; this.stayOn = stayOn; this.edges = List.copyOf(Objects.requireNonNull(edges)); @@ -135,10 +135,10 @@ public String getHighwayExit() { } /** - * Get information about a subway station entrance or exit. + * Get information about feature e.g. a subway station entrance or exit. */ - public Entrance getStationEntrance() { - return entrance; + public StepFeature getStepFeature() { + return feature; } /** 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 7ef5012e145..6752eba95e9 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java +++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Set; import javax.annotation.Nullable; +import org.opentripplanner.apis.gtfs.model.StepFeature; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.street.model.edge.Edge; @@ -26,7 +27,7 @@ public class WalkStepBuilder { private RelativeDirection relativeDirection; private ElevationProfile elevationProfile; private String exit; - private Entrance entrance; + private StepFeature feature; private boolean stayOn = false; /** * Distance used for appending elevation profiles @@ -77,7 +78,7 @@ public WalkStepBuilder withExit(String exit) { } public WalkStepBuilder withEntrance(Entrance entrance) { - this.entrance = entrance; + this.feature = new StepFeature(entrance); return this; } @@ -163,7 +164,7 @@ public WalkStep build() { directionText, streetNotes, exit, - entrance, + feature, elevationProfile, bogusName, walkingBike, 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 13b576a46c7..83fe952ce7e 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -73,6 +73,9 @@ interface PlaceInterface { "Entity related to an alert" union AlertEntity = Agency | Pattern | Route | RouteType | Stop | StopOnRoute | StopOnTrip | Trip | Unknown +"A feature for a step" +union StepFeature = Entrance + union StopPosition = PositionAtStop | PositionBetweenStops "A public transport agency" @@ -2657,10 +2660,10 @@ type step { distance: Float "The elevation profile as a list of { distance, elevation } values." elevationProfile: [elevationProfileComponent] - "Information about an station entrance or exit" - entrance: Entrance "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." 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 a0a781153f1..8d79102fc59 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 @@ -12,14 +12,14 @@ "area" : false, "relativeDirection" : "DEPART", "absoluteDirection" : "NORTHEAST", - "entrance" : null + "feature" : null }, { "streetName" : "elevator", "area" : false, "relativeDirection" : "ELEVATOR", "absoluteDirection" : null, - "entrance" : null + "feature" : null }, { @@ -27,10 +27,11 @@ "area" : false, "relativeDirection" : "ENTER_OR_EXIT_STATION", "absoluteDirection" : null, - "entrance": { + "feature": { + "__typename": "Entrance", "code": "A", - "wheelchairAccessible": "POSSIBLE", - "entranceId": "osm:123" + "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 45e2eed904a..565e620fed3 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,10 +20,13 @@ area relativeDirection absoluteDirection - entrance { - code - wheelchairAccessible - entranceId + feature { + __typename + ... on Entrance { + code + entranceId + wheelchairAccessible + } } } } From f547e074f2879c9e4f6bec37759a7d64fc927258 Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Thu, 14 Nov 2024 12:42:47 +0200 Subject: [PATCH 031/195] Return feature based on relativeDirection --- .../apis/gtfs/datafetchers/EntranceImpl.java | 13 ++++--------- .../gtfs/datafetchers/StepFeatureTypeResolver.java | 8 ++------ .../apis/gtfs/datafetchers/stepImpl.java | 9 ++++++++- .../apis/gtfs/model/StepFeature.java | 8 ++++---- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java index bcf37d42cc7..9891d107479 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java @@ -4,7 +4,6 @@ import org.opentripplanner.apis.gtfs.GraphQLUtils; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; -import org.opentripplanner.apis.gtfs.model.StepFeature; import org.opentripplanner.transit.model.site.Entrance; public class EntranceImpl implements GraphQLDataFetchers.GraphQLEntrance { @@ -12,8 +11,7 @@ public class EntranceImpl implements GraphQLDataFetchers.GraphQLEntrance { @Override public DataFetcher code() { return environment -> { - StepFeature feature = environment.getSource(); - Entrance entrance = (Entrance) feature.getFeature(); + Entrance entrance = environment.getSource(); return entrance.getCode(); }; } @@ -21,8 +19,7 @@ public DataFetcher code() { @Override public DataFetcher entranceId() { return environment -> { - StepFeature feature = environment.getSource(); - Entrance entrance = (Entrance) feature.getFeature(); + Entrance entrance = environment.getSource(); return entrance.getId().toString(); }; } @@ -30,8 +27,7 @@ public DataFetcher entranceId() { @Override public DataFetcher name() { return environment -> { - StepFeature feature = environment.getSource(); - Entrance entrance = (Entrance) feature.getFeature(); + Entrance entrance = environment.getSource(); return org.opentripplanner.framework.graphql.GraphQLUtils.getTranslation( entrance.getName(), environment @@ -42,8 +38,7 @@ public DataFetcher name() { @Override public DataFetcher wheelchairAccessible() { return environment -> { - StepFeature feature = environment.getSource(); - Entrance entrance = (Entrance) feature.getFeature(); + Entrance entrance = environment.getSource(); return GraphQLUtils.toGraphQL(entrance.getWheelchairAccessibility()); }; } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepFeatureTypeResolver.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepFeatureTypeResolver.java index 8748d87700d..714518cb9ea 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepFeatureTypeResolver.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/StepFeatureTypeResolver.java @@ -4,7 +4,6 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLSchema; import graphql.schema.TypeResolver; -import org.opentripplanner.apis.gtfs.model.StepFeature; import org.opentripplanner.transit.model.site.Entrance; public class StepFeatureTypeResolver implements TypeResolver { @@ -14,11 +13,8 @@ public GraphQLObjectType getType(TypeResolutionEnvironment environment) { Object o = environment.getObject(); GraphQLSchema schema = environment.getSchema(); - if (o instanceof StepFeature) { - Object feature = ((StepFeature) o).getFeature(); - if (feature instanceof Entrance) { - return schema.getObjectType("Entrance"); - } + if (o instanceof Entrance) { + return schema.getObjectType("Entrance"); } return null; } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java index 74453d6e7c4..6a1c180fad6 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java @@ -7,6 +7,7 @@ import org.opentripplanner.apis.gtfs.mapping.DirectionMapper; import org.opentripplanner.apis.gtfs.mapping.StreetNoteMapper; import org.opentripplanner.model.plan.ElevationProfile.Step; +import org.opentripplanner.model.plan.RelativeDirection; import org.opentripplanner.model.plan.WalkStep; import org.opentripplanner.routing.alertpatch.TransitAlert; @@ -55,7 +56,13 @@ public DataFetcher exit() { @Override public DataFetcher feature() { - return environment -> getSource(environment).getStepFeature(); + return environment -> { + WalkStep source = getSource(environment); + if (source.getRelativeDirection() == RelativeDirection.ENTER_OR_EXIT_STATION) { + return source.getStepFeature().getEntrance(); + } + return null; + }; } @Override diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java b/application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java index c4842e25476..bf5fd8cc104 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java @@ -8,13 +8,13 @@ **/ public class StepFeature { - private final Object feature; + private final Entrance entranceFeature; public StepFeature(Entrance entrance) { - this.feature = entrance; + this.entranceFeature = entrance; } - public Object getFeature() { - return feature; + public Entrance getEntrance() { + return entranceFeature; } } From 18b84f00a2f83841ef5a993cd5d958899a3805fd Mon Sep 17 00:00:00 2001 From: Henrik Sundell Date: Thu, 14 Nov 2024 14:20:00 +0200 Subject: [PATCH 032/195] Remove StepFeature class --- .../apis/gtfs/datafetchers/stepImpl.java | 2 +- .../apis/gtfs/model/StepFeature.java | 20 ------------------- .../opentripplanner/model/plan/WalkStep.java | 14 ++++++------- .../model/plan/WalkStepBuilder.java | 7 +++---- 4 files changed, 11 insertions(+), 32 deletions(-) delete mode 100644 application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java index 6a1c180fad6..c98237f78c5 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java @@ -59,7 +59,7 @@ public DataFetcher feature() { return environment -> { WalkStep source = getSource(environment); if (source.getRelativeDirection() == RelativeDirection.ENTER_OR_EXIT_STATION) { - return source.getStepFeature().getEntrance(); + return source.getEntrance(); } return null; }; diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java b/application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java deleted file mode 100644 index bf5fd8cc104..00000000000 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/model/StepFeature.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.opentripplanner.apis.gtfs.model; - -import org.opentripplanner.transit.model.site.Entrance; - -/** - * A generic wrapper class for features in Walk steps. - * At the moment only subway station entrances. - **/ -public class StepFeature { - - private final Entrance entranceFeature; - - public StepFeature(Entrance entrance) { - this.entranceFeature = entrance; - } - - public Entrance getEntrance() { - return entranceFeature; - } -} 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 660b973a8a8..2efe6e36aff 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java +++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java @@ -4,11 +4,11 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import org.opentripplanner.apis.gtfs.model.StepFeature; import org.opentripplanner.framework.geometry.WgsCoordinate; 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; @@ -45,7 +45,7 @@ public final class WalkStep { private final boolean walkingBike; private final String exit; - private final StepFeature feature; + private final Entrance entrance; private final ElevationProfile elevationProfile; private final boolean stayOn; @@ -58,7 +58,7 @@ public final class WalkStep { I18NString directionText, Set streetNotes, String exit, - StepFeature feature, + Entrance entrance, ElevationProfile elevationProfile, boolean bogusName, boolean walkingBike, @@ -79,7 +79,7 @@ public final class WalkStep { this.walkingBike = walkingBike; this.area = area; this.exit = exit; - this.feature = feature; + this.entrance = entrance; this.elevationProfile = elevationProfile; this.stayOn = stayOn; this.edges = List.copyOf(Objects.requireNonNull(edges)); @@ -135,10 +135,10 @@ public String getHighwayExit() { } /** - * Get information about feature e.g. a subway station entrance or exit. + * Get information about a subway station entrance or exit. */ - public StepFeature getStepFeature() { - return feature; + public Entrance getEntrance() { + return 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 6752eba95e9..7ef5012e145 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java +++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Set; import javax.annotation.Nullable; -import org.opentripplanner.apis.gtfs.model.StepFeature; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.street.model.edge.Edge; @@ -27,7 +26,7 @@ public class WalkStepBuilder { private RelativeDirection relativeDirection; private ElevationProfile elevationProfile; private String exit; - private StepFeature feature; + private Entrance entrance; private boolean stayOn = false; /** * Distance used for appending elevation profiles @@ -78,7 +77,7 @@ public WalkStepBuilder withExit(String exit) { } public WalkStepBuilder withEntrance(Entrance entrance) { - this.feature = new StepFeature(entrance); + this.entrance = entrance; return this; } @@ -164,7 +163,7 @@ public WalkStep build() { directionText, streetNotes, exit, - feature, + entrance, elevationProfile, bogusName, walkingBike, From e1e9bbbafe36b2548ef1a4f718913e3393834612 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 3 Dec 2024 08:43:11 +0200 Subject: [PATCH 033/195] Fixes based on review comments. --- .../opentripplanner/api/parameter/QualifiedModeSet.java | 7 +++++-- .../graph_builder/module/DirectTransferGenerator.java | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java b/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java index c13fb576ad5..98ce58c6d21 100644 --- a/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java +++ b/application/src/main/java/org/opentripplanner/api/parameter/QualifiedModeSet.java @@ -126,8 +126,11 @@ public RequestModes getRequestModes() { mBuilder.withEgressMode(StreetMode.CAR_HAILING); mBuilder.withDirectMode(StreetMode.WALK); } else { - // This is used in transfer cache request calculations. - mBuilder.withAllStreetModes(StreetMode.CAR); + // This is necessary for transfer calculations. + mBuilder.withAccessMode(StreetMode.CAR); + mBuilder.withTransferMode(StreetMode.CAR); + mBuilder.withEgressMode(StreetMode.CAR); + mBuilder.withDirectMode(StreetMode.CAR); } } } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index fc00a135f27..da71d2afb1b 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -101,7 +101,8 @@ public void buildGraph() { .stream() .map(StopLocation::getId) .map(graph::getStopVertexForStopId) - .filter(TransitStopVertex.class::isInstance) // filter out null values if no TransitStopVertex is found for ID + // filter out null values if no TransitStopVertex is found for ID + .filter(TransitStopVertex.class::isInstance) .collect(Collectors.toSet()); ProgressTracker progress = ProgressTracker.track( From 89e617f26a6e7e8d82d7888bd435cd0b146cd462 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 3 Dec 2024 13:26:10 +0200 Subject: [PATCH 034/195] Add TransferParameters to build config. --- .../module/DirectTransferGenerator.java | 73 +++++++++++-------- .../module/TransferParameters.java | 55 ++++++++++++++ .../module/configure/GraphBuilderModules.java | 3 +- .../standalone/config/BuildConfig.java | 8 +- ...llowedStopMaxTransferDurationsForMode.java | 6 +- .../config/buildconfig/TransferConfig.java | 20 +++++ .../buildconfig/TransferParametersMapper.java | 35 +++++++++ .../module/DirectTransferGeneratorTest.java | 16 ++-- 8 files changed, 172 insertions(+), 44 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java create mode 100644 application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java create mode 100644 application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index da71d2afb1b..51e6294f3b3 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -51,6 +51,7 @@ public class DirectTransferGenerator implements GraphBuilderModule { private final Duration radiusByDuration; private final List transferRequests; + private final Map transferParametersForMode; private final DurationForEnum carsAllowedStopMaxTransferDurationsForMode; private final Graph graph; private final TimetableRepository timetableRepository; @@ -69,6 +70,7 @@ public DirectTransferGenerator( this.radiusByDuration = radiusByDuration; this.transferRequests = transferRequests; this.carsAllowedStopMaxTransferDurationsForMode = DurationForEnum.of(StreetMode.class).build(); + this.transferParametersForMode = Collections.emptyMap(); } public DirectTransferGenerator( @@ -77,7 +79,8 @@ public DirectTransferGenerator( DataImportIssueStore issueStore, Duration radiusByDuration, List transferRequests, - DurationForEnum carsAllowedStopMaxTransferDurationsForMode + DurationForEnum carsAllowedStopMaxTransferDurationsForMode, + Map transferParametersForMode ) { this.graph = graph; this.timetableRepository = timetableRepository; @@ -85,6 +88,7 @@ public DirectTransferGenerator( this.radiusByDuration = radiusByDuration; this.transferRequests = transferRequests; this.carsAllowedStopMaxTransferDurationsForMode = carsAllowedStopMaxTransferDurationsForMode; + this.transferParametersForMode = transferParametersForMode; } @Override @@ -92,9 +96,6 @@ public void buildGraph() { /* Initialize transit model index which is needed by the nearby stop finder. */ timetableRepository.index(); - /* The linker will use streets if they are available, or straight-line distance otherwise. */ - NearbyStopFinder nearbyStopFinder = createNearbyStopFinder(radiusByDuration, Set.of()); - List stops = graph.getVerticesOfType(TransitStopVertex.class); Set carsAllowedStops = timetableRepository .getStopLocationsUsedForCarsAllowedTrips() @@ -121,32 +122,37 @@ public void buildGraph() { List filteredTransferRequests = new ArrayList(); List carsAllowedStopTransferRequests = new ArrayList(); + /* The linker will use streets if they are available, or straight-line distance otherwise. */ + HashMap nearbyStopFinders = new HashMap<>(); + /* These are used for calculating transfers only between carsAllowedStops. */ HashMap carsAllowedStopNearbyStopFinders = new HashMap<>(); - // Split transfer requests into normal and carsAllowedStop requests. + // Parse transfer parameters. for (RouteRequest transferProfile : transferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); - if (carsAllowedStopMaxTransferDurationsForMode.containsKey(mode)) { - carsAllowedStopNearbyStopFinders.put( - mode, - createNearbyStopFinder( - carsAllowedStopMaxTransferDurationsForMode.valueOf(mode), - Collections.unmodifiableSet(carsAllowedStops) - ) - ); - - carsAllowedStopTransferRequests.add(transferProfile); - // For bikes, also normal transfer requests are wanted. - if (mode == StreetMode.BIKE) { + TransferParameters transferParameters = transferParametersForMode.get(mode); + if (transferParameters != null) { + // Disable normal transfer calculations if disableDefaultTransfers is set in the build config. + if (!transferParameters.disableDefaultTransfers()) { filteredTransferRequests.add(transferProfile); + // Set mode-specific maxTransferDuration, if it is set in the build config. + Duration maxTransferDuration = radiusByDuration; + if (transferParameters.maxTransferDuration() != Duration.ZERO) { + maxTransferDuration = transferParameters.maxTransferDuration(); + } + nearbyStopFinders.put(mode, createNearbyStopFinder(maxTransferDuration, Set.of())); + } + // Create transfers between carsAllowedStops for the specific mode if carsAllowedStopMaxTransferDuration is set in the build config. + if (transferParameters.carsAllowedStopMaxTransferDuration() != Duration.ZERO) { + carsAllowedStopTransferRequests.add(transferProfile); + carsAllowedStopNearbyStopFinders.put( + mode, + createNearbyStopFinder( + transferParameters.carsAllowedStopMaxTransferDuration(), + Collections.unmodifiableSet(carsAllowedStops) + ) + ); } - } else if (mode == StreetMode.CAR) { - // Special transfers are always created for cars. - // If a duration is not specified for cars, the default is used. - carsAllowedStopNearbyStopFinders.put(mode, nearbyStopFinder); - carsAllowedStopTransferRequests.add(transferProfile); - } else { - filteredTransferRequests.add(transferProfile); } } @@ -166,18 +172,23 @@ public void buildGraph() { LOG.debug("Linking stop '{}' {}", stop, ts0); for (RouteRequest transferProfile : filteredTransferRequests) { - findNearbyStops(nearbyStopFinder, ts0, transferProfile, stop, distinctTransfers); + StreetMode mode = transferProfile.journey().transfer().mode(); + findNearbyStops( + nearbyStopFinders.get(mode), + ts0, + transferProfile, + stop, + distinctTransfers + ); } if (OTPFeature.FlexRouting.isOn()) { for (RouteRequest transferProfile : filteredTransferRequests) { + StreetMode mode = transferProfile.journey().transfer().mode(); // This code is for finding transfers from AreaStops to Stops, transfers // from Stops to AreaStops and between Stops are already covered above. - for (NearbyStop sd : nearbyStopFinder.findNearbyStops( - ts0, - transferProfile, - transferProfile.journey().transfer(), - true - )) { + for (NearbyStop sd : nearbyStopFinders + .get(mode) + .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), true)) { // Skip the origin stop, loop transfers are not needed. if (sd.stop == stop) { continue; diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java b/application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java new file mode 100644 index 00000000000..962d8579ddd --- /dev/null +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java @@ -0,0 +1,55 @@ +package org.opentripplanner.graph_builder.module; + +import java.time.Duration; + +public record TransferParameters( + Duration maxTransferDuration, + Duration carsAllowedStopMaxTransferDuration, + boolean disableDefaultTransfers +) { + public static final Duration DEFAULT_MAX_TRANSFER_DURATION = Duration.ZERO; + public static final Duration DEFAULT_CARS_ALLOWED_STOP_MAX_TRANSFER_DURATION = Duration.ZERO; + public static final boolean DEFAULT_DISABLE_DEFAULT_TRANSFERS = false; + + TransferParameters(Builder builder) { + this( + builder.maxTransferDuration, + builder.carsAllowedStopMaxTransferDuration, + builder.disableDefaultTransfers + ); + } + + public static class Builder { + + private Duration maxTransferDuration; + private Duration carsAllowedStopMaxTransferDuration; + private boolean disableDefaultTransfers; + + public Builder() { + this.maxTransferDuration = DEFAULT_MAX_TRANSFER_DURATION; + this.carsAllowedStopMaxTransferDuration = DEFAULT_CARS_ALLOWED_STOP_MAX_TRANSFER_DURATION; + this.disableDefaultTransfers = DEFAULT_DISABLE_DEFAULT_TRANSFERS; + } + + public Builder withMaxTransferDuration(Duration maxTransferDuration) { + this.maxTransferDuration = maxTransferDuration; + return this; + } + + public Builder withCarsAllowedStopMaxTransferDuration( + Duration carsAllowedStopMaxTransferDuration + ) { + this.carsAllowedStopMaxTransferDuration = carsAllowedStopMaxTransferDuration; + return this; + } + + public Builder withDisableDefaultTransfers(boolean disableDefaultTransfers) { + this.disableDefaultTransfers = disableDefaultTransfers; + return this; + } + + public TransferParameters build() { + return new TransferParameters(this); + } + } +} 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 03ea13e9f11..b6c8eb484ee 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 @@ -238,7 +238,8 @@ static DirectTransferGenerator provideDirectTransferGenerator( issueStore, config.maxTransferDuration, config.transferRequests, - config.carsAllowedStopMaxTransferDurationsForMode + config.carsAllowedStopMaxTransferDurationsForMode, + config.transferParametersForMode ); } diff --git a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index 6f726c806fe..323b7635708 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -15,6 +15,7 @@ import java.time.LocalDate; import java.time.ZoneId; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import javax.annotation.Nullable; @@ -23,6 +24,7 @@ import org.opentripplanner.ext.emissions.EmissionsConfig; import org.opentripplanner.ext.fares.FaresConfiguration; import org.opentripplanner.framework.geometry.CompactElevationProfile; +import org.opentripplanner.graph_builder.module.TransferParameters; import org.opentripplanner.graph_builder.module.ned.parameter.DemExtractParameters; import org.opentripplanner.graph_builder.module.ned.parameter.DemExtractParametersList; import org.opentripplanner.graph_builder.module.osm.parameters.OsmExtractParameters; @@ -42,6 +44,7 @@ import org.opentripplanner.standalone.config.buildconfig.NetexConfig; import org.opentripplanner.standalone.config.buildconfig.OsmConfig; import org.opentripplanner.standalone.config.buildconfig.S3BucketConfig; +import org.opentripplanner.standalone.config.buildconfig.TransferConfig; import org.opentripplanner.standalone.config.buildconfig.TransferRequestConfig; import org.opentripplanner.standalone.config.buildconfig.TransitFeedConfig; import org.opentripplanner.standalone.config.buildconfig.TransitFeeds; @@ -154,6 +157,7 @@ public class BuildConfig implements OtpDataStoreConfig { public final IslandPruningConfig islandPruning; public final Duration maxTransferDuration; + public final Map transferParametersForMode; public final DurationForEnum carsAllowedStopMaxTransferDurationsForMode; public final NetexFeedParameters netexDefaults; public final GtfsFeedParameters gtfsDefaults; @@ -291,11 +295,11 @@ When set to true (it is false by default), the elevation module will include the "Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph." ) .asDuration(Duration.ofMinutes(30)); + transferParametersForMode = TransferConfig.map(root, "transfers"); carsAllowedStopMaxTransferDurationsForMode = CarsAllowedStopMaxTransferDurationsForMode.map( root, - "carsAllowedStopMaxTransferDurationsForMode", - maxTransferDuration + "carsAllowedStopMaxTransferDurationsForMode" ); maxStopToShapeSnapDistance = root diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java index 322ed32d682..531d5d9af7f 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java @@ -10,11 +10,7 @@ public class CarsAllowedStopMaxTransferDurationsForMode { - public static DurationForEnum map( - NodeAdapter root, - String parameterName, - Duration maxTransferDuration - ) { + public static DurationForEnum map(NodeAdapter root, String parameterName) { Map values = root .of(parameterName) .since(V2_7) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java new file mode 100644 index 00000000000..3f171238c05 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java @@ -0,0 +1,20 @@ +package org.opentripplanner.standalone.config.buildconfig; + +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; + +import java.util.EnumMap; +import java.util.Map; +import org.opentripplanner.graph_builder.module.TransferParameters; +import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; + +public class TransferConfig { + + public static Map map(NodeAdapter root, String parameterName) { + return root + .of(parameterName) + .since(V2_7) + .summary("Configures properties for transfer calculations.") + .asEnumMap(StreetMode.class, TransferParametersMapper::map, new EnumMap<>(StreetMode.class)); + } +} diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java new file mode 100644 index 00000000000..caf35f0155c --- /dev/null +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java @@ -0,0 +1,35 @@ +package org.opentripplanner.standalone.config.buildconfig; + +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; + +import org.opentripplanner.graph_builder.module.TransferParameters; +import org.opentripplanner.standalone.config.framework.json.NodeAdapter; + +public class TransferParametersMapper { + + public static TransferParameters map(NodeAdapter c) { + TransferParameters.Builder builder = new TransferParameters.Builder(); + builder.withMaxTransferDuration( + c + .of("maxTransferDuration") + .summary("") + .since(V2_7) + .asDuration(TransferParameters.DEFAULT_MAX_TRANSFER_DURATION) + ); + builder.withCarsAllowedStopMaxTransferDuration( + c + .of("carsAllowedStopMaxTransferDuration") + .summary("") + .since(V2_7) + .asDuration(TransferParameters.DEFAULT_CARS_ALLOWED_STOP_MAX_TRANSFER_DURATION) + ); + builder.withDisableDefaultTransfers( + c + .of("disableDefaultTransfers") + .summary("") + .since(V2_7) + .asBoolean(TransferParameters.DEFAULT_DISABLE_DEFAULT_TRANSFERS) + ); + return builder.build(); + } +} diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java index fc4aed8833f..d66d0b29930 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java @@ -8,6 +8,7 @@ import java.time.Duration; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -294,7 +295,8 @@ public void testRequestWithCarsAllowedPatterns() { DataImportIssueStore.NOOP, MAX_TRANSFER_DURATION, transferRequests, - DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofMinutes(60)).build() + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofMinutes(60)).build(), + Collections.emptyMap() ) .buildGraph(); @@ -323,7 +325,8 @@ public void testRequestWithCarsAllowedPatternsWithDurationLimit() { DataImportIssueStore.NOOP, MAX_TRANSFER_DURATION, transferRequests, - DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofSeconds(10)).build() + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofSeconds(10)).build(), + Collections.emptyMap() ) .buildGraph(); @@ -354,7 +357,8 @@ public void testMultipleRequestsWithPatternsAndWithCarsAllowedPatterns() { DataImportIssueStore.NOOP, MAX_TRANSFER_DURATION, transferRequests, - DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofMinutes(60)).build() + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofMinutes(60)).build(), + Collections.emptyMap() ) .buildGraph(); @@ -387,7 +391,8 @@ public void testBikeRequestWithPatternsAndWithCarsAllowedPatterns() { DataImportIssueStore.NOOP, Duration.ofSeconds(30), transferRequests, - DurationForEnum.of(StreetMode.class).with(StreetMode.BIKE, Duration.ofSeconds(120)).build() + DurationForEnum.of(StreetMode.class).with(StreetMode.BIKE, Duration.ofSeconds(120)).build(), + Collections.emptyMap() ) .buildGraph(); @@ -419,7 +424,8 @@ public void testBikeRequestWithPatternsAndWithCarsAllowedPatternsWithoutCarInTra DataImportIssueStore.NOOP, Duration.ofSeconds(30), transferRequests, - DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofSeconds(120)).build() + DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofSeconds(120)).build(), + Collections.emptyMap() ) .buildGraph(); From a1e080791c8bdc304a40c33e7f625afc39fb21b0 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 3 Dec 2024 14:17:01 +0200 Subject: [PATCH 035/195] Remove mentions of carsAllowedStopMaxTransferDurationsForMode and fix tests. --- .../module/DirectTransferGenerator.java | 7 +- .../module/configure/GraphBuilderModules.java | 1 - .../request/framework/DurationForEnum.java | 4 - .../standalone/config/BuildConfig.java | 7 - ...llowedStopMaxTransferDurationsForMode.java | 51 ---- .../module/DirectTransferGeneratorTest.java | 47 +++- doc/user/BuildConfiguration.md | 231 ++++++++---------- 7 files changed, 146 insertions(+), 202 deletions(-) delete mode 100644 application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index 51e6294f3b3..62221750f68 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -52,7 +52,6 @@ public class DirectTransferGenerator implements GraphBuilderModule { private final List transferRequests; private final Map transferParametersForMode; - private final DurationForEnum carsAllowedStopMaxTransferDurationsForMode; private final Graph graph; private final TimetableRepository timetableRepository; private final DataImportIssueStore issueStore; @@ -69,7 +68,6 @@ public DirectTransferGenerator( this.issueStore = issueStore; this.radiusByDuration = radiusByDuration; this.transferRequests = transferRequests; - this.carsAllowedStopMaxTransferDurationsForMode = DurationForEnum.of(StreetMode.class).build(); this.transferParametersForMode = Collections.emptyMap(); } @@ -79,7 +77,6 @@ public DirectTransferGenerator( DataImportIssueStore issueStore, Duration radiusByDuration, List transferRequests, - DurationForEnum carsAllowedStopMaxTransferDurationsForMode, Map transferParametersForMode ) { this.graph = graph; @@ -87,7 +84,6 @@ public DirectTransferGenerator( this.issueStore = issueStore; this.radiusByDuration = radiusByDuration; this.transferRequests = transferRequests; - this.carsAllowedStopMaxTransferDurationsForMode = carsAllowedStopMaxTransferDurationsForMode; this.transferParametersForMode = transferParametersForMode; } @@ -153,6 +149,9 @@ public void buildGraph() { ) ); } + } else { + filteredTransferRequests.add(transferProfile); + nearbyStopFinders.put(mode, createNearbyStopFinder(radiusByDuration, Set.of())); } } 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 b6c8eb484ee..4cf0d3816bf 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 @@ -238,7 +238,6 @@ static DirectTransferGenerator provideDirectTransferGenerator( issueStore, config.maxTransferDuration, config.transferRequests, - config.carsAllowedStopMaxTransferDurationsForMode, config.transferParametersForMode ); } diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java b/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java index 7547a58ecc7..c6586aa8b0f 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/framework/DurationForEnum.java @@ -49,10 +49,6 @@ public Duration defaultValue() { return defaultValue; } - public boolean containsKey(E key) { - return valueForEnum.containsKey(key); - } - /** * Utility method to get {@link #defaultValue} as an number in unit seconds. Equivalent to * {@code (int) defaultValue.toSeconds()}. The downcast is safe since we only allow days, hours, diff --git a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index 323b7635708..d7ed602aef5 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -37,7 +37,6 @@ import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.framework.DurationForEnum; import org.opentripplanner.routing.fares.FareServiceFactory; -import org.opentripplanner.standalone.config.buildconfig.CarsAllowedStopMaxTransferDurationsForMode; import org.opentripplanner.standalone.config.buildconfig.DemConfig; import org.opentripplanner.standalone.config.buildconfig.GtfsConfig; import org.opentripplanner.standalone.config.buildconfig.IslandPruningConfig; @@ -158,7 +157,6 @@ public class BuildConfig implements OtpDataStoreConfig { public final Duration maxTransferDuration; public final Map transferParametersForMode; - public final DurationForEnum carsAllowedStopMaxTransferDurationsForMode; public final NetexFeedParameters netexDefaults; public final GtfsFeedParameters gtfsDefaults; @@ -296,11 +294,6 @@ When set to true (it is false by default), the elevation module will include the ) .asDuration(Duration.ofMinutes(30)); transferParametersForMode = TransferConfig.map(root, "transfers"); - carsAllowedStopMaxTransferDurationsForMode = - CarsAllowedStopMaxTransferDurationsForMode.map( - root, - "carsAllowedStopMaxTransferDurationsForMode" - ); maxStopToShapeSnapDistance = root .of("maxStopToShapeSnapDistance") diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java deleted file mode 100644 index 531d5d9af7f..00000000000 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/CarsAllowedStopMaxTransferDurationsForMode.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.opentripplanner.standalone.config.buildconfig; - -import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; - -import java.time.Duration; -import java.util.Map; -import org.opentripplanner.routing.api.request.StreetMode; -import org.opentripplanner.routing.api.request.framework.DurationForEnum; -import org.opentripplanner.standalone.config.framework.json.NodeAdapter; - -public class CarsAllowedStopMaxTransferDurationsForMode { - - public static DurationForEnum map(NodeAdapter root, String parameterName) { - Map values = root - .of(parameterName) - .since(V2_7) - .summary( - "This is used for specifying a `maxTransferDuration` value for bikes and cars to use with transfers between stops that have trips with cars." - ) - .description( - """ -This is a special parameter that only works on transfers between stops that have trips that allow cars. -The duration can be set for either 'BIKE' or 'CAR'. -For cars, transfers are only calculated between stops that have trips that allow cars. -For cars, this overrides the default `maxTransferDuration`. -For bicycles, this indicates that additional transfers should be calculated with the specified duration between stops that have trips that allow cars. - -**Example** - -```JSON -// build-config.json -{ - "carsAllowedStopMaxTransferDurationsForMode": { - "CAR": "2h", - "BIKE": "3h" - } -} -``` -""" - ) - .asEnumMap(StreetMode.class, Duration.class); - for (StreetMode mode : values.keySet()) { - if (mode != StreetMode.BIKE && mode != StreetMode.CAR) { - throw new IllegalArgumentException( - "Only the CAR and BIKE modes are allowed in the carsAllowedStopMaxTransferDurationsForMode parameter." - ); - } - } - return DurationForEnum.of(StreetMode.class).withValues(values).build(); - } -} diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java index d66d0b29930..8cd8dae3b6f 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java @@ -9,8 +9,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Stream; @@ -289,14 +291,19 @@ public void testRequestWithCarsAllowedPatterns() { graph.hasStreets = true; var timetableRepository = otpModel.timetableRepository(); + TransferParameters.Builder transferParametersBuilder = new TransferParameters.Builder(); + transferParametersBuilder.withCarsAllowedStopMaxTransferDuration(Duration.ofMinutes(60)); + transferParametersBuilder.withDisableDefaultTransfers(true); + Map transferParametersForMode = new HashMap<>(); + transferParametersForMode.put(StreetMode.CAR, transferParametersBuilder.build()); + new DirectTransferGenerator( graph, timetableRepository, DataImportIssueStore.NOOP, MAX_TRANSFER_DURATION, transferRequests, - DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofMinutes(60)).build(), - Collections.emptyMap() + transferParametersForMode ) .buildGraph(); @@ -319,14 +326,19 @@ public void testRequestWithCarsAllowedPatternsWithDurationLimit() { graph.hasStreets = true; var timetableRepository = otpModel.timetableRepository(); + TransferParameters.Builder transferParametersBuilder = new TransferParameters.Builder(); + transferParametersBuilder.withCarsAllowedStopMaxTransferDuration(Duration.ofSeconds(10)); + transferParametersBuilder.withDisableDefaultTransfers(true); + Map transferParametersForMode = new HashMap<>(); + transferParametersForMode.put(StreetMode.CAR, transferParametersBuilder.build()); + new DirectTransferGenerator( graph, timetableRepository, DataImportIssueStore.NOOP, MAX_TRANSFER_DURATION, transferRequests, - DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofSeconds(10)).build(), - Collections.emptyMap() + transferParametersForMode ) .buildGraph(); @@ -351,14 +363,19 @@ public void testMultipleRequestsWithPatternsAndWithCarsAllowedPatterns() { graph.hasStreets = true; var timetableRepository = otpModel.timetableRepository(); + TransferParameters.Builder transferParametersBuilder = new TransferParameters.Builder(); + transferParametersBuilder.withCarsAllowedStopMaxTransferDuration(Duration.ofMinutes(60)); + transferParametersBuilder.withDisableDefaultTransfers(true); + Map transferParametersForMode = new HashMap<>(); + transferParametersForMode.put(StreetMode.CAR, transferParametersBuilder.build()); + new DirectTransferGenerator( graph, timetableRepository, DataImportIssueStore.NOOP, MAX_TRANSFER_DURATION, transferRequests, - DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofMinutes(60)).build(), - Collections.emptyMap() + transferParametersForMode ) .buildGraph(); @@ -385,14 +402,19 @@ public void testBikeRequestWithPatternsAndWithCarsAllowedPatterns() { graph.hasStreets = true; var timetableRepository = otpModel.timetableRepository(); + TransferParameters.Builder transferParametersBuilder = new TransferParameters.Builder(); + transferParametersBuilder.withCarsAllowedStopMaxTransferDuration(Duration.ofMinutes(120)); + transferParametersBuilder.withDisableDefaultTransfers(true); + Map transferParametersForMode = new HashMap<>(); + transferParametersForMode.put(StreetMode.BIKE, transferParametersBuilder.build()); + new DirectTransferGenerator( graph, timetableRepository, DataImportIssueStore.NOOP, Duration.ofSeconds(30), transferRequests, - DurationForEnum.of(StreetMode.class).with(StreetMode.BIKE, Duration.ofSeconds(120)).build(), - Collections.emptyMap() + transferParametersForMode ) .buildGraph(); @@ -418,14 +440,19 @@ public void testBikeRequestWithPatternsAndWithCarsAllowedPatternsWithoutCarInTra graph.hasStreets = true; var timetableRepository = otpModel.timetableRepository(); + TransferParameters.Builder transferParametersBuilder = new TransferParameters.Builder(); + transferParametersBuilder.withCarsAllowedStopMaxTransferDuration(Duration.ofSeconds(120)); + transferParametersBuilder.withDisableDefaultTransfers(true); + Map transferParametersForMode = new HashMap<>(); + transferParametersForMode.put(StreetMode.CAR, transferParametersBuilder.build()); + new DirectTransferGenerator( graph, timetableRepository, DataImportIssueStore.NOOP, Duration.ofSeconds(30), transferRequests, - DurationForEnum.of(StreetMode.class).with(StreetMode.CAR, Duration.ofSeconds(120)).build(), - Collections.emptyMap() + transferParametersForMode ) .buildGraph(); diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index e2d46e98d73..413d8a8c7a1 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -17,104 +17,104 @@ Sections follow that describe particular settings in more depth. -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|-------------------------------------------------------------------------------------------|:----------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|-----------------------------------|:-----:| -| [areaVisibility](#areaVisibility) | `boolean` | Perform visibility calculations. | *Optional* | `false` | 1.5 | -| [buildReportDir](#buildReportDir) | `uri` | URI to the directory where the graph build report should be written to. | *Optional* | | 2.0 | -| [configVersion](#configVersion) | `string` | Deployment version of the *build-config.json*. | *Optional* | | 2.1 | -| [dataImportReport](#dataImportReport) | `boolean` | Generate nice HTML report of Graph errors/warnings | *Optional* | `false` | 2.0 | -| [distanceBetweenElevationSamples](#distanceBetweenElevationSamples) | `double` | The distance between elevation samples in meters. | *Optional* | `10.0` | 2.0 | -| embedRouterConfig | `boolean` | Embed the Router config in the graph, which allows it to be sent to a server fully configured over the wire. | *Optional* | `true` | 2.0 | -| [graph](#graph) | `uri` | URI to the graph object file for reading and writing. | *Optional* | | 2.0 | -| [gsCredentials](#gsCredentials) | `string` | Local file system path to Google Cloud Platform service accounts credentials file. | *Optional* | | 2.0 | -| [includeEllipsoidToGeoidDifference](#includeEllipsoidToGeoidDifference) | `boolean` | Include the Ellipsoid to Geoid difference in the calculations of every point along every StreetWithElevationEdge. | *Optional* | `false` | 2.0 | -| maxAreaNodes | `integer` | Visibility calculations for an area will not be done if there are more nodes than this limit. | *Optional* | `150` | 2.1 | -| [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | -| maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | -| [maxStopToShapeSnapDistance](#maxStopToShapeSnapDistance) | `double` | Maximum distance between route shapes and their stops. | *Optional* | `150.0` | 2.1 | -| maxTransferDuration | `duration` | Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph. | *Optional* | `"PT30M"` | 2.1 | -| [multiThreadElevationCalculations](#multiThreadElevationCalculations) | `boolean` | Configuring multi-threading during elevation calculations. | *Optional* | `false` | 2.0 | -| [osmCacheDataInMem](#osmCacheDataInMem) | `boolean` | If OSM data should be cached in memory during processing. | *Optional* | `false` | 2.0 | -| [osmNaming](#osmNaming) | `enum` | A custom OSM namer to use. | *Optional* | `"default"` | 1.5 | -| platformEntriesLinking | `boolean` | Link unconnected entries to public transport platforms. | *Optional* | `false` | 2.0 | -| [readCachedElevations](#readCachedElevations) | `boolean` | Whether to read cached elevation data. | *Optional* | `true` | 2.0 | -| staticBikeParkAndRide | `boolean` | Whether we should create bike P+R stations from OSM data. | *Optional* | `false` | 1.5 | -| staticParkAndRide | `boolean` | Whether we should create car P+R stations from OSM data. | *Optional* | `true` | 1.5 | -| stopConsolidationFile | `uri` | Name of the CSV-formatted file in the build directory which contains the configuration for stop consolidation. | *Optional* | | 2.5 | -| [streetGraph](#streetGraph) | `uri` | URI to the street graph object file for reading and writing. | *Optional* | | 2.0 | -| [subwayAccessTime](#subwayAccessTime) | `double` | Minutes necessary to reach stops served by trips on routes of route_type=1 (subway) from the street. | *Optional* | `2.0` | 1.5 | -| [transitModelTimeZone](#transitModelTimeZone) | `time-zone` | Time zone for the graph. | *Optional* | | 2.2 | -| [transitServiceEnd](#transitServiceEnd) | `duration` | Limit the import of transit services to the given end date. | *Optional* | `"P3Y"` | 2.0 | -| [transitServiceStart](#transitServiceStart) | `duration` | Limit the import of transit services to the given START date. | *Optional* | `"-P1Y"` | 2.0 | -| [writeCachedElevations](#writeCachedElevations) | `boolean` | Reusing elevation data from previous builds | *Optional* | `false` | 2.0 | -| [boardingLocationTags](#boardingLocationTags) | `string[]` | What OSM tags should be looked on for the source of matching stops to platforms and stops. | *Optional* | | 2.2 | -| [carsAllowedStopMaxTransferDurationsForMode](#carsAllowedStopMaxTransferDurationsForMode) | `enum map of duration` | This is used for specifying a `maxTransferDuration` value for bikes and cars to use with transfers between stops that have trips with cars. | *Optional* | | 2.7 | -| [dataOverlay](sandbox/DataOverlay.md) | `object` | Config for the DataOverlay Sandbox module | *Optional* | | 2.2 | -| [dem](#dem) | `object[]` | Specify parameters for DEM extracts. | *Optional* | | 2.2 | -|       [elevationUnitMultiplier](#dem_0_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. Overrides the value specified in `demDefaults`. | *Optional* | `1.0` | 2.3 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -| demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | -|    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | -| [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | -| [emissions](sandbox/Emissions.md) | `object` | Emissions configuration. | *Optional* | | 2.5 | -| [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | -| gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | -|    blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. | *Optional* | `true` | 2.3 | -|    [discardMinTransferTimes](#gd_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. | *Optional* | `false` | 2.3 | -|    maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. | *Optional* | `200` | 2.3 | -|    removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. | *Optional* | `true` | 2.3 | -|    [stationTransferPreference](#gd_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. | *Optional* | `"allowed"` | 2.3 | -| islandPruning | `object` | Settings for fixing street graph connectivity errors | *Optional* | | 2.3 | -|    [adaptivePruningDistance](#islandPruning_adaptivePruningDistance) | `integer` | Search distance for analyzing islands in pruning. | *Optional* | `250` | 2.3 | -|    [adaptivePruningFactor](#islandPruning_adaptivePruningFactor) | `double` | Defines how much pruning thresholds grow maximally by distance. | *Optional* | `50.0` | 2.3 | -|    [islandWithStopsMaxSize](#islandPruning_islandWithStopsMaxSize) | `integer` | When a graph island with stops in it should be pruned. | *Optional* | `2` | 2.3 | -|    [islandWithoutStopsMaxSize](#islandPruning_islandWithoutStopsMaxSize) | `integer` | When a graph island without stops should be pruned. | *Optional* | `10` | 2.3 | -| [localFileNamePatterns](#localFileNamePatterns) | `object` | Patterns for matching OTP file types in the base directory | *Optional* | | 2.0 | -|    [dem](#lfp_dem) | `regexp` | Pattern for matching elevation DEM files. | *Optional* | `"(?i)\.tiff?$"` | 2.0 | -|    [gtfs](#lfp_gtfs) | `regexp` | Patterns for matching GTFS zip-files or directories. | *Optional* | `"(?i)gtfs"` | 2.0 | -|    [netex](#lfp_netex) | `regexp` | Patterns for matching NeTEx zip files or directories. | *Optional* | `"(?i)netex"` | 2.0 | -|    [osm](#lfp_osm) | `regexp` | Pattern for matching Open Street Map input files. | *Optional* | `"(?i)(\.pbf¦\.osm¦\.osm\.xml)$"` | 2.0 | -| netexDefaults | `object` | The netexDefaults section allows you to specify default properties for NeTEx files. | *Optional* | | 2.2 | -|    feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Optional* | `"NETEX"` | 2.2 | -|    [groupFilePattern](#nd_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | -|    ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | -|    [ignoreFilePattern](#nd_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | -|    ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | -|    noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | -|    [sharedFilePattern](#nd_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | -|    [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 | -|       [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 | -|    [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 | -| [transitFeeds](#transitFeeds) | `object[]` | Scan for transit data files | *Optional* | | 2.2 | -|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | -|       type = "gtfs" | `enum` | The feed input format. | *Required* | | 2.2 | -|       blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | -|       [discardMinTransferTimes](#tf_0_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. Overrides the value specified in `gtfsDefaults`. | *Optional* | `false` | 2.3 | -|       feedId | `string` | The unique ID for this feed. This overrides any feed ID defined within the feed itself. | *Optional* | | 2.2 | -|       maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. Overrides the value specified in `gtfsDefaults`. | *Optional* | `200` | 2.3 | -|       removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -|       [stationTransferPreference](#tf_0_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. Overrides the value specified in `gtfsDefaults`. | *Optional* | `"allowed"` | 2.3 | -|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | -|       type = "netex" | `enum` | The feed input format. | *Required* | | 2.2 | -|       feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Required* | | 2.2 | -|       [groupFilePattern](#tf_1_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | -|       ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | -|       [ignoreFilePattern](#tf_1_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | -|       ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | -|       noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | -|       [sharedFilePattern](#tf_1_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | -|       [sharedGroupFilePattern](#tf_1_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -|       [ferryIdsNotAllowedForBicycle](#tf_1_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | -| [transitRouteToStationCentroid](#transitRouteToStationCentroid) | `feed-scoped-id[]` | List stations that should route to centroid. | *Optional* | | 2.7 | +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|--------------------------------------------------------------------------|:--------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|-----------------------------------|:-----:| +| [areaVisibility](#areaVisibility) | `boolean` | Perform visibility calculations. | *Optional* | `false` | 1.5 | +| [buildReportDir](#buildReportDir) | `uri` | URI to the directory where the graph build report should be written to. | *Optional* | | 2.0 | +| [configVersion](#configVersion) | `string` | Deployment version of the *build-config.json*. | *Optional* | | 2.1 | +| [dataImportReport](#dataImportReport) | `boolean` | Generate nice HTML report of Graph errors/warnings | *Optional* | `false` | 2.0 | +| [distanceBetweenElevationSamples](#distanceBetweenElevationSamples) | `double` | The distance between elevation samples in meters. | *Optional* | `10.0` | 2.0 | +| embedRouterConfig | `boolean` | Embed the Router config in the graph, which allows it to be sent to a server fully configured over the wire. | *Optional* | `true` | 2.0 | +| [graph](#graph) | `uri` | URI to the graph object file for reading and writing. | *Optional* | | 2.0 | +| [gsCredentials](#gsCredentials) | `string` | Local file system path to Google Cloud Platform service accounts credentials file. | *Optional* | | 2.0 | +| [includeEllipsoidToGeoidDifference](#includeEllipsoidToGeoidDifference) | `boolean` | Include the Ellipsoid to Geoid difference in the calculations of every point along every StreetWithElevationEdge. | *Optional* | `false` | 2.0 | +| maxAreaNodes | `integer` | Visibility calculations for an area will not be done if there are more nodes than this limit. | *Optional* | `150` | 2.1 | +| [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | +| maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | +| [maxStopToShapeSnapDistance](#maxStopToShapeSnapDistance) | `double` | Maximum distance between route shapes and their stops. | *Optional* | `150.0` | 2.1 | +| maxTransferDuration | `duration` | Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph. | *Optional* | `"PT30M"` | 2.1 | +| [multiThreadElevationCalculations](#multiThreadElevationCalculations) | `boolean` | Configuring multi-threading during elevation calculations. | *Optional* | `false` | 2.0 | +| [osmCacheDataInMem](#osmCacheDataInMem) | `boolean` | If OSM data should be cached in memory during processing. | *Optional* | `false` | 2.0 | +| [osmNaming](#osmNaming) | `enum` | A custom OSM namer to use. | *Optional* | `"default"` | 1.5 | +| platformEntriesLinking | `boolean` | Link unconnected entries to public transport platforms. | *Optional* | `false` | 2.0 | +| [readCachedElevations](#readCachedElevations) | `boolean` | Whether to read cached elevation data. | *Optional* | `true` | 2.0 | +| staticBikeParkAndRide | `boolean` | Whether we should create bike P+R stations from OSM data. | *Optional* | `false` | 1.5 | +| staticParkAndRide | `boolean` | Whether we should create car P+R stations from OSM data. | *Optional* | `true` | 1.5 | +| stopConsolidationFile | `uri` | Name of the CSV-formatted file in the build directory which contains the configuration for stop consolidation. | *Optional* | | 2.5 | +| [streetGraph](#streetGraph) | `uri` | URI to the street graph object file for reading and writing. | *Optional* | | 2.0 | +| [subwayAccessTime](#subwayAccessTime) | `double` | Minutes necessary to reach stops served by trips on routes of route_type=1 (subway) from the street. | *Optional* | `2.0` | 1.5 | +| [transitModelTimeZone](#transitModelTimeZone) | `time-zone` | Time zone for the graph. | *Optional* | | 2.2 | +| [transitServiceEnd](#transitServiceEnd) | `duration` | Limit the import of transit services to the given end date. | *Optional* | `"P3Y"` | 2.0 | +| [transitServiceStart](#transitServiceStart) | `duration` | Limit the import of transit services to the given START date. | *Optional* | `"-P1Y"` | 2.0 | +| [writeCachedElevations](#writeCachedElevations) | `boolean` | Reusing elevation data from previous builds | *Optional* | `false` | 2.0 | +| [boardingLocationTags](#boardingLocationTags) | `string[]` | What OSM tags should be looked on for the source of matching stops to platforms and stops. | *Optional* | | 2.2 | +| [dataOverlay](sandbox/DataOverlay.md) | `object` | Config for the DataOverlay Sandbox module | *Optional* | | 2.2 | +| [dem](#dem) | `object[]` | Specify parameters for DEM extracts. | *Optional* | | 2.2 | +|       [elevationUnitMultiplier](#dem_0_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. Overrides the value specified in `demDefaults`. | *Optional* | `1.0` | 2.3 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +| demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | +|    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | +| [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | +| [emissions](sandbox/Emissions.md) | `object` | Emissions configuration. | *Optional* | | 2.5 | +| [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | +| gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | +|    blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. | *Optional* | `true` | 2.3 | +|    [discardMinTransferTimes](#gd_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. | *Optional* | `false` | 2.3 | +|    maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. | *Optional* | `200` | 2.3 | +|    removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. | *Optional* | `true` | 2.3 | +|    [stationTransferPreference](#gd_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. | *Optional* | `"allowed"` | 2.3 | +| islandPruning | `object` | Settings for fixing street graph connectivity errors | *Optional* | | 2.3 | +|    [adaptivePruningDistance](#islandPruning_adaptivePruningDistance) | `integer` | Search distance for analyzing islands in pruning. | *Optional* | `250` | 2.3 | +|    [adaptivePruningFactor](#islandPruning_adaptivePruningFactor) | `double` | Defines how much pruning thresholds grow maximally by distance. | *Optional* | `50.0` | 2.3 | +|    [islandWithStopsMaxSize](#islandPruning_islandWithStopsMaxSize) | `integer` | When a graph island with stops in it should be pruned. | *Optional* | `2` | 2.3 | +|    [islandWithoutStopsMaxSize](#islandPruning_islandWithoutStopsMaxSize) | `integer` | When a graph island without stops should be pruned. | *Optional* | `10` | 2.3 | +| [localFileNamePatterns](#localFileNamePatterns) | `object` | Patterns for matching OTP file types in the base directory | *Optional* | | 2.0 | +|    [dem](#lfp_dem) | `regexp` | Pattern for matching elevation DEM files. | *Optional* | `"(?i)\.tiff?$"` | 2.0 | +|    [gtfs](#lfp_gtfs) | `regexp` | Patterns for matching GTFS zip-files or directories. | *Optional* | `"(?i)gtfs"` | 2.0 | +|    [netex](#lfp_netex) | `regexp` | Patterns for matching NeTEx zip files or directories. | *Optional* | `"(?i)netex"` | 2.0 | +|    [osm](#lfp_osm) | `regexp` | Pattern for matching Open Street Map input files. | *Optional* | `"(?i)(\.pbf¦\.osm¦\.osm\.xml)$"` | 2.0 | +| netexDefaults | `object` | The netexDefaults section allows you to specify default properties for NeTEx files. | *Optional* | | 2.2 | +|    feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Optional* | `"NETEX"` | 2.2 | +|    [groupFilePattern](#nd_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | +|    ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | +|    [ignoreFilePattern](#nd_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | +|    ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | +|    noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | +|    [sharedFilePattern](#nd_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | +|    [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 | +|       [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 | +|    [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 | +| [transfers](#transfers) | `enum map of object` | Configures properties for transfer calculations. | *Optional* | | 2.7 | +| [transitFeeds](#transitFeeds) | `object[]` | Scan for transit data files | *Optional* | | 2.2 | +|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | +|       type = "gtfs" | `enum` | The feed input format. | *Required* | | 2.2 | +|       blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | +|       [discardMinTransferTimes](#tf_0_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. Overrides the value specified in `gtfsDefaults`. | *Optional* | `false` | 2.3 | +|       feedId | `string` | The unique ID for this feed. This overrides any feed ID defined within the feed itself. | *Optional* | | 2.2 | +|       maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. Overrides the value specified in `gtfsDefaults`. | *Optional* | `200` | 2.3 | +|       removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +|       [stationTransferPreference](#tf_0_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. Overrides the value specified in `gtfsDefaults`. | *Optional* | `"allowed"` | 2.3 | +|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | +|       type = "netex" | `enum` | The feed input format. | *Required* | | 2.2 | +|       feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Required* | | 2.2 | +|       [groupFilePattern](#tf_1_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | +|       ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | +|       [ignoreFilePattern](#tf_1_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | +|       ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | +|       noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | +|       [sharedFilePattern](#tf_1_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | +|       [sharedGroupFilePattern](#tf_1_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +|       [ferryIdsNotAllowedForBicycle](#tf_1_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | +| [transitRouteToStationCentroid](#transitRouteToStationCentroid) | `feed-scoped-id[]` | List stations that should route to centroid. | *Optional* | | 2.7 | @@ -654,33 +654,6 @@ What OSM tags should be looked on for the source of matching stops to platforms [Detailed documentation](BoardingLocations.md) -

carsAllowedStopMaxTransferDurationsForMode

- -**Since version:** `2.7` ∙ **Type:** `enum map of duration` ∙ **Cardinality:** `Optional` -**Path:** / -**Enum keys:** `not-set` | `walk` | `bike` | `bike-to-park` | `bike-rental` | `scooter-rental` | `car` | `car-to-park` | `car-pickup` | `car-rental` | `car-hailing` | `flexible` - -This is used for specifying a `maxTransferDuration` value for bikes and cars to use with transfers between stops that have trips with cars. - -This is a special parameter that only works on transfers between stops that have trips that allow cars. -The duration can be set for either 'BIKE' or 'CAR'. -For cars, transfers are only calculated between stops that have trips that allow cars. -For cars, this overrides the default `maxTransferDuration`. -For bicycles, this indicates that additional transfers should be calculated with the specified duration between stops that have trips that allow cars. - -**Example** - -```JSON -// build-config.json -{ - "carsAllowedStopMaxTransferDurationsForMode": { - "CAR": "2h", - "BIKE": "3h" - } -} -``` - -

dem

**Since version:** `2.2` ∙ **Type:** `object[]` ∙ **Cardinality:** `Optional` @@ -980,6 +953,14 @@ The named set of mapping rules applied when parsing OSM tags. Overrides the valu The named set of mapping rules applied when parsing OSM tags. +

transfers

+ +**Since version:** `2.7` ∙ **Type:** `enum map of object` ∙ **Cardinality:** `Optional` +**Path:** / +**Enum keys:** `not-set` | `walk` | `bike` | `bike-to-park` | `bike-rental` | `scooter-rental` | `car` | `car-to-park` | `car-pickup` | `car-rental` | `car-hailing` | `flexible` + +Configures properties for transfer calculations. +

transitFeeds

**Since version:** `2.2` ∙ **Type:** `object[]` ∙ **Cardinality:** `Optional` From 21bb3078592482af160b9fb2bc57bc72dcf83c13 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 5 Dec 2024 10:00:21 +0200 Subject: [PATCH 036/195] Rename variables and small improvements. --- .../module/DirectTransferGenerator.java | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index 62221750f68..57681d4893a 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -48,7 +48,7 @@ public class DirectTransferGenerator implements GraphBuilderModule { private static final Logger LOG = LoggerFactory.getLogger(DirectTransferGenerator.class); - private final Duration radiusByDuration; + private final Duration defaultMaxTransferDuration; private final List transferRequests; private final Map transferParametersForMode; @@ -60,13 +60,13 @@ public DirectTransferGenerator( Graph graph, TimetableRepository timetableRepository, DataImportIssueStore issueStore, - Duration radiusByDuration, + Duration defaultMaxTransferDuration, List transferRequests ) { this.graph = graph; this.timetableRepository = timetableRepository; this.issueStore = issueStore; - this.radiusByDuration = radiusByDuration; + this.defaultMaxTransferDuration = defaultMaxTransferDuration; this.transferRequests = transferRequests; this.transferParametersForMode = Collections.emptyMap(); } @@ -75,14 +75,14 @@ public DirectTransferGenerator( Graph graph, TimetableRepository timetableRepository, DataImportIssueStore issueStore, - Duration radiusByDuration, + Duration defaultMaxTransferDuration, List transferRequests, Map transferParametersForMode ) { this.graph = graph; this.timetableRepository = timetableRepository; this.issueStore = issueStore; - this.radiusByDuration = radiusByDuration; + this.defaultMaxTransferDuration = defaultMaxTransferDuration; this.transferRequests = transferRequests; this.transferParametersForMode = transferParametersForMode; } @@ -116,10 +116,10 @@ public void buildGraph() { HashMultimap.create() ); - List filteredTransferRequests = new ArrayList(); + List defaultTransferRequests = new ArrayList(); List carsAllowedStopTransferRequests = new ArrayList(); /* The linker will use streets if they are available, or straight-line distance otherwise. */ - HashMap nearbyStopFinders = new HashMap<>(); + HashMap defaultNearbyStopFinders = new HashMap<>(); /* These are used for calculating transfers only between carsAllowedStops. */ HashMap carsAllowedStopNearbyStopFinders = new HashMap<>(); @@ -130,28 +130,32 @@ public void buildGraph() { if (transferParameters != null) { // Disable normal transfer calculations if disableDefaultTransfers is set in the build config. if (!transferParameters.disableDefaultTransfers()) { - filteredTransferRequests.add(transferProfile); + defaultTransferRequests.add(transferProfile); // Set mode-specific maxTransferDuration, if it is set in the build config. - Duration maxTransferDuration = radiusByDuration; - if (transferParameters.maxTransferDuration() != Duration.ZERO) { - maxTransferDuration = transferParameters.maxTransferDuration(); + Duration maxTransferDuration = transferParameters.maxTransferDuration(); + if (maxTransferDuration == Duration.ZERO) { + maxTransferDuration = defaultMaxTransferDuration; } - nearbyStopFinders.put(mode, createNearbyStopFinder(maxTransferDuration, Set.of())); + defaultNearbyStopFinders.put(mode, createNearbyStopFinder(maxTransferDuration, Set.of())); } // Create transfers between carsAllowedStops for the specific mode if carsAllowedStopMaxTransferDuration is set in the build config. - if (transferParameters.carsAllowedStopMaxTransferDuration() != Duration.ZERO) { + Duration carsAllowedStopMaxTransferDuration = transferParameters.carsAllowedStopMaxTransferDuration(); + if (carsAllowedStopMaxTransferDuration != Duration.ZERO) { carsAllowedStopTransferRequests.add(transferProfile); carsAllowedStopNearbyStopFinders.put( mode, createNearbyStopFinder( - transferParameters.carsAllowedStopMaxTransferDuration(), + carsAllowedStopMaxTransferDuration, Collections.unmodifiableSet(carsAllowedStops) ) ); } } else { - filteredTransferRequests.add(transferProfile); - nearbyStopFinders.put(mode, createNearbyStopFinder(radiusByDuration, Set.of())); + defaultTransferRequests.add(transferProfile); + defaultNearbyStopFinders.put( + mode, + createNearbyStopFinder(defaultMaxTransferDuration, Set.of()) + ); } } @@ -170,10 +174,10 @@ public void buildGraph() { LOG.debug("Linking stop '{}' {}", stop, ts0); - for (RouteRequest transferProfile : filteredTransferRequests) { + for (RouteRequest transferProfile : defaultTransferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); findNearbyStops( - nearbyStopFinders.get(mode), + defaultNearbyStopFinders.get(mode), ts0, transferProfile, stop, @@ -181,11 +185,11 @@ public void buildGraph() { ); } if (OTPFeature.FlexRouting.isOn()) { - for (RouteRequest transferProfile : filteredTransferRequests) { + for (RouteRequest transferProfile : defaultTransferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); // This code is for finding transfers from AreaStops to Stops, transfers // from Stops to AreaStops and between Stops are already covered above. - for (NearbyStop sd : nearbyStopFinders + for (NearbyStop sd : defaultNearbyStopFinders .get(mode) .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), true)) { // Skip the origin stop, loop transfers are not needed. @@ -204,11 +208,9 @@ public void buildGraph() { } // This calculates transfers between stops that can use trips with cars. - for (RouteRequest transferProfile : carsAllowedStopTransferRequests) { - StreetMode mode = transferProfile.journey().transfer().mode(); - if ( - carsAllowedStops.contains(ts0) && carsAllowedStopNearbyStopFinders.containsKey(mode) - ) { + if (carsAllowedStops.contains(ts0)) { + for (RouteRequest transferProfile : carsAllowedStopTransferRequests) { + StreetMode mode = transferProfile.journey().transfer().mode(); findNearbyStops( carsAllowedStopNearbyStopFinders.get(mode), ts0, From 573078a462bf9dfd3aafbacc0f89336d43f79338 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 5 Dec 2024 10:24:05 +0200 Subject: [PATCH 037/195] Change test. --- .../graph_builder/module/DirectTransferGeneratorTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java index 8cd8dae3b6f..2d92f83ea6c 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java @@ -404,7 +404,6 @@ public void testBikeRequestWithPatternsAndWithCarsAllowedPatterns() { TransferParameters.Builder transferParametersBuilder = new TransferParameters.Builder(); transferParametersBuilder.withCarsAllowedStopMaxTransferDuration(Duration.ofMinutes(120)); - transferParametersBuilder.withDisableDefaultTransfers(true); Map transferParametersForMode = new HashMap<>(); transferParametersForMode.put(StreetMode.BIKE, transferParametersBuilder.build()); From 3fd5bf55577a88efc6d9fe56e176dd5ba0bb44cc Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 5 Dec 2024 10:58:51 +0200 Subject: [PATCH 038/195] Add documentation for build config fields. --- .../config/buildconfig/TransferConfig.java | 24 ++++++++++++++++++- .../buildconfig/TransferParametersMapper.java | 15 +++++++++--- doc/user/BuildConfiguration.md | 24 +++++++++++++++++-- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java index 3f171238c05..aa70a2788c4 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java @@ -14,7 +14,29 @@ public static Map map(NodeAdapter root, String p return root .of(parameterName) .since(V2_7) - .summary("Configures properties for transfer calculations.") + .summary("Configures mode-specific properties for transfer calculations.") + .description( + """ +This field enables configuring mode-specific parameters for transfer calculations. +To configure mode-specific parameters, the modes should also be used in the `transferRequests` field in the build config. + +**Example** + +// build-config.json +{ + "transfers": { + "CAR": { + "disableDefaultTransfers": true, + "carsAllowedStopMaxTransferDuration": "3h" + }, + "BIKE": { + "maxTransferDuration": "30m", + "carsAllowedStopMaxTransferDuration": "3h" + } + } +} +""" + ) .asEnumMap(StreetMode.class, TransferParametersMapper::map, new EnumMap<>(StreetMode.class)); } } diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java index caf35f0155c..d14858d79c4 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java @@ -12,21 +12,30 @@ public static TransferParameters map(NodeAdapter c) { builder.withMaxTransferDuration( c .of("maxTransferDuration") - .summary("") + .summary("This overwrites the `maxTransferDuration` for the given mode.") .since(V2_7) .asDuration(TransferParameters.DEFAULT_MAX_TRANSFER_DURATION) ); builder.withCarsAllowedStopMaxTransferDuration( c .of("carsAllowedStopMaxTransferDuration") - .summary("") + .summary( + "This is used for specifying a `maxTransferDuration` value to use with transfers between stops that have trips with cars." + ) + .description( + """ +This parameter configures additional transfers to be calculated for the specified mode only between stops that have trips with cars. +The transfers are calculated for the mode in a range based on the given duration. +By default, these transfers are not calculated for the specified mode. +""" + ) .since(V2_7) .asDuration(TransferParameters.DEFAULT_CARS_ALLOWED_STOP_MAX_TRANSFER_DURATION) ); builder.withDisableDefaultTransfers( c .of("disableDefaultTransfers") - .summary("") + .summary("This disables default transfer calculations.") .since(V2_7) .asBoolean(TransferParameters.DEFAULT_DISABLE_DEFAULT_TRANSFERS) ); diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index 413d8a8c7a1..dee3a266f8a 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -91,7 +91,7 @@ Sections follow that describe particular settings in more depth. |    [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 | -| [transfers](#transfers) | `enum map of object` | Configures properties for transfer calculations. | *Optional* | | 2.7 | +| [transfers](#transfers) | `enum map of object` | Configures mode-specific properties for transfer calculations. | *Optional* | | 2.7 | | [transitFeeds](#transitFeeds) | `object[]` | Scan for transit data files | *Optional* | | 2.2 | |    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | |       type = "gtfs" | `enum` | The feed input format. | *Required* | | 2.2 | @@ -959,7 +959,27 @@ The named set of mapping rules applied when parsing OSM tags. **Path:** / **Enum keys:** `not-set` | `walk` | `bike` | `bike-to-park` | `bike-rental` | `scooter-rental` | `car` | `car-to-park` | `car-pickup` | `car-rental` | `car-hailing` | `flexible` -Configures properties for transfer calculations. +Configures mode-specific properties for transfer calculations. + +This field enables configuring mode-specific parameters for transfer calculations. +To configure mode-specific parameters, the modes should also be used in the `transferRequests` field in the build config. + +**Example** + +// build-config.json +{ + "transfers": { + "CAR": { + "disableDefaultTransfers": true, + "carsAllowedStopMaxTransferDuration": "3h" + }, + "BIKE": { + "maxTransferDuration": "30m", + "carsAllowedStopMaxTransferDuration": "3h" + } + } +} +

transitFeeds

From 35c98bc04ee093488ba4303e2f8144165ad5572c Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 5 Dec 2024 13:11:41 +0200 Subject: [PATCH 039/195] Add logging for transfers. --- .../graph_builder/module/DirectTransferGenerator.java | 11 +++++++++++ .../graph_builder/module/TransferParameters.java | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index 57681d4893a..7dc69946d3a 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -102,6 +102,17 @@ public void buildGraph() { .filter(TransitStopVertex.class::isInstance) .collect(Collectors.toSet()); + LOG.info("Creating transfers based on requests:"); + transferRequests.forEach(transferProfile -> LOG.info(transferProfile.toString())); + if (transferParametersForMode.isEmpty()) { + LOG.info("No mode-specific transfer configurations provided."); + } else { + LOG.info("Using transfer configurations for modes:"); + transferParametersForMode.forEach((mode, transferParameters) -> + LOG.info(mode + ": " + transferParameters) + ); + } + ProgressTracker progress = ProgressTracker.track( "Create transfer edges for stops", 1000, diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java b/application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java index 962d8579ddd..89553eb1e39 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java @@ -1,6 +1,7 @@ package org.opentripplanner.graph_builder.module; import java.time.Duration; +import org.opentripplanner.utils.tostring.ToStringBuilder; public record TransferParameters( Duration maxTransferDuration, @@ -19,6 +20,15 @@ public record TransferParameters( ); } + public String toString() { + return ToStringBuilder + .of(getClass()) + .addDuration("maxTransferDuration", maxTransferDuration) + .addDuration("carsAllowedStopMaxTransferDuration", carsAllowedStopMaxTransferDuration) + .addBool("disableDefaultTransfers", disableDefaultTransfers) + .toString(); + } + public static class Builder { private Duration maxTransferDuration; From 97238a3446d2c44f979a4ec36bffd333166eb996 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 5 Dec 2024 15:35:35 +0200 Subject: [PATCH 040/195] Fix merge issues. --- .../module/DirectTransferGenerator.java | 91 +++++++------------ 1 file changed, 35 insertions(+), 56 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index 6349ab122fa..f5978513080 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -92,6 +92,15 @@ public void buildGraph() { /* Initialize transit model index which is needed by the nearby stop finder. */ timetableRepository.index(); + /* The linker will use streets if they are available, or straight-line distance otherwise. */ + NearbyStopFinder nearbyStopFinder = createNearbyStopFinder( + defaultMaxTransferDuration, + Set.of() + ); + HashMap defaultNearbyStopFinders = new HashMap<>(); + /* These are used for calculating transfers only between carsAllowedStops. */ + HashMap carsAllowedStopNearbyStopFinders = new HashMap<>(); + List stops = graph.getVerticesOfType(TransitStopVertex.class); Set carsAllowedStops = timetableRepository .getStopLocationsUsedForCarsAllowedTrips() @@ -130,20 +139,6 @@ public void buildGraph() { List defaultTransferRequests = new ArrayList<>(); List carsAllowedStopTransferRequests = new ArrayList<>(); List flexTransferRequests = new ArrayList<>(); - // Flex transfer requests only use the WALK mode. - if (OTPFeature.FlexRouting.isOn()) { - flexTransferRequests.addAll( - transferRequests - .stream() - .filter(transferProfile -> transferProfile.journey().transfer().mode() == StreetMode.WALK) - .toList() - ); - } - - /* The linker will use streets if they are available, or straight-line distance otherwise. */ - HashMap defaultNearbyStopFinders = new HashMap<>(); - /* These are used for calculating transfers only between carsAllowedStops. */ - HashMap carsAllowedStopNearbyStopFinders = new HashMap<>(); // Parse transfer parameters. for (RouteRequest transferProfile : transferRequests) { @@ -155,10 +150,14 @@ public void buildGraph() { defaultTransferRequests.add(transferProfile); // Set mode-specific maxTransferDuration, if it is set in the build config. Duration maxTransferDuration = transferParameters.maxTransferDuration(); - if (maxTransferDuration == Duration.ZERO) { - maxTransferDuration = defaultMaxTransferDuration; + if (maxTransferDuration != Duration.ZERO) { + defaultNearbyStopFinders.put( + mode, + createNearbyStopFinder(maxTransferDuration, Set.of()) + ); + } else { + defaultNearbyStopFinders.put(mode, nearbyStopFinder); } - defaultNearbyStopFinders.put(mode, createNearbyStopFinder(maxTransferDuration, Set.of())); } // Create transfers between carsAllowedStops for the specific mode if carsAllowedStopMaxTransferDuration is set in the build config. Duration carsAllowedStopMaxTransferDuration = transferParameters.carsAllowedStopMaxTransferDuration(); @@ -174,10 +173,20 @@ public void buildGraph() { } } else { defaultTransferRequests.add(transferProfile); - defaultNearbyStopFinders.put( - mode, - createNearbyStopFinder(defaultMaxTransferDuration, Set.of()) - ); + defaultNearbyStopFinders.put(mode, nearbyStopFinder); + } + } + + // Flex transfer requests only use the WALK mode. + if (OTPFeature.FlexRouting.isOn()) { + flexTransferRequests.addAll( + transferRequests + .stream() + .filter(transferProfile -> transferProfile.journey().transfer().mode() == StreetMode.WALK) + .toList() + ); + if (!defaultNearbyStopFinders.containsKey(StreetMode.WALK)) { + defaultNearbyStopFinders.put(StreetMode.WALK, nearbyStopFinder); } } @@ -196,34 +205,8 @@ public void buildGraph() { LOG.debug("Linking stop '{}' {}", stop, ts0); - /* for (RouteRequest transferProfile : defaultTransferRequests) { - StreetMode mode = transferProfile.journey().transfer().mode(); - findNearbyStops( - defaultNearbyStopFinders.get(mode), - ts0, - transferProfile, - stop, - distinctTransfers - ); - } - if (OTPFeature.FlexRouting.isOn()) { - for (RouteRequest transferProfile : defaultTransferRequests) { - StreetMode mode = transferProfile.journey().transfer().mode(); - // This code is for finding transfers from AreaStops to Stops, transfers - // from Stops to AreaStops and between Stops are already covered above. - for (NearbyStop sd : defaultNearbyStopFinders - .get(mode) - .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), true)) { - // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { - continue; - } - if (sd.stop instanceof RegularStop) { - continue; - } */ - // Calculate default transfers. - for (RouteRequest transferProfile : transferRequests) { + for (RouteRequest transferProfile : defaultTransferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); findNearbyStops( defaultNearbyStopFinders.get(mode), @@ -239,12 +222,9 @@ public void buildGraph() { StreetMode mode = transferProfile.journey().transfer().mode(); // This code is for finding transfers from AreaStops to Stops, transfers // from Stops to AreaStops and between Stops are already covered above. - for (NearbyStop sd : defaultNearbyStopFinders.get(mode).findNearbyStops( - ts0, - transferProfile, - transferProfile.journey().transfer(), - true - )) { + for (NearbyStop sd : defaultNearbyStopFinders + .get(mode) + .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), true)) { // Skip the origin stop, loop transfers are not needed. if (sd.stop == stop) { continue; @@ -266,8 +246,7 @@ public void buildGraph() { } } } - - // This calculates transfers between stops that can use trips with cars. + // Calculate transfers between stops that can use trips with cars if configured. if (carsAllowedStops.contains(ts0)) { for (RouteRequest transferProfile : carsAllowedStopTransferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); From d6cd1e13f800269b6328626782d70d3efa3d8830 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Mon, 9 Dec 2024 16:52:08 +0000 Subject: [PATCH 041/195] parallel raptor cache generation --- .../transit/RaptorTransferIndex.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) 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 1d9b804067c..f8d0cf1f696 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,6 +5,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Function; +import java.util.stream.IntStream; import org.opentripplanner.raptor.api.model.RaptorTransfer; import org.opentripplanner.street.search.request.StreetSearchRequest; @@ -35,27 +36,33 @@ public static RaptorTransferIndex create( reversedTransfers.add(new ArrayList<>()); } - for (int fromStop = 0; fromStop < transfersByStopIndex.size(); fromStop++) { - // The transfers are filtered so that there is only one possible directional transfer - // for a stop pair. - var transfers = transfersByStopIndex - .get(fromStop) - .stream() - .flatMap(s -> s.asRaptorTransfer(request).stream()) - .collect( - toMap(RaptorTransfer::stop, Function.identity(), (a, b) -> a.c1() < b.c1() ? a : b) - ) - .values(); + IntStream + .range(0, transfersByStopIndex.size()) + .parallel() + .forEach(fromStop -> { + // The transfers are filtered so that there is only one possible directional transfer + // for a stop pair. + var transfers = transfersByStopIndex + .get(fromStop) + .stream() + .flatMap(s -> s.asRaptorTransfer(request).stream()) + .collect( + toMap(RaptorTransfer::stop, Function.identity(), (a, b) -> a.c1() < b.c1() ? a : b) + ) + .values(); - forwardTransfers.get(fromStop).addAll(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); } From 41a0674970e734bed4b4152002e94dbef72e0a4b Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 10 Dec 2024 12:02:37 +0200 Subject: [PATCH 042/195] Add tests and small improvements. --- .../module/DirectTransferGenerator.java | 2 +- .../transit/service/TimetableRepository.java | 9 ++ .../module/DirectTransferGeneratorTest.java | 143 +++++++++++++++--- 3 files changed, 136 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index f5978513080..e679bb52ca5 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -145,7 +145,7 @@ public void buildGraph() { StreetMode mode = transferProfile.journey().transfer().mode(); TransferParameters transferParameters = transferParametersForMode.get(mode); if (transferParameters != null) { - // Disable normal transfer calculations if disableDefaultTransfers is set in the build config. + // Disable normal transfer calculations for the specific mode, if disableDefaultTransfers is set in the build config. if (!transferParameters.disableDefaultTransfers()) { defaultTransferRequests.add(transferProfile); // Set mode-specific maxTransferDuration, if it is set in the build config. diff --git a/application/src/main/java/org/opentripplanner/transit/service/TimetableRepository.java b/application/src/main/java/org/opentripplanner/transit/service/TimetableRepository.java index b81dd7d4f14..d83c3f9b996 100644 --- a/application/src/main/java/org/opentripplanner/transit/service/TimetableRepository.java +++ b/application/src/main/java/org/opentripplanner/transit/service/TimetableRepository.java @@ -35,6 +35,7 @@ import org.opentripplanner.model.transfer.DefaultTransferService; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerUpdater; +import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.impl.DelegatingTransitAlertServiceImpl; import org.opentripplanner.routing.services.TransitAlertService; import org.opentripplanner.routing.util.ConcurrentPublished; @@ -433,6 +434,14 @@ public Collection getTransfersByStop(StopLocation stop) { return transfersByStop.get(stop); } + public Collection getTransfersByMode(StreetMode mode) { + return transfersByStop + .values() + .stream() + .filter(pathTransfer -> pathTransfer.getModes().contains(mode)) + .toList(); + } + public SiteRepository getSiteRepository() { return siteRepository; } diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java index 5d974c200d7..8446a87f21a 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java @@ -280,21 +280,9 @@ public void testPathTransfersWithModesForMultipleRequestsWithPatterns() { ) .buildGraph(); - var walkTransfers = timetableRepository - .getAllPathTransfers() - .stream() - .filter(pathTransfer -> pathTransfer.getModes().contains(StreetMode.WALK)) - .toList(); - var bikeTransfers = timetableRepository - .getAllPathTransfers() - .stream() - .filter(pathTransfer -> pathTransfer.getModes().contains(StreetMode.BIKE)) - .toList(); - var carTransfers = timetableRepository - .getAllPathTransfers() - .stream() - .filter(pathTransfer -> pathTransfer.getModes().contains(StreetMode.CAR)) - .toList(); + var walkTransfers = timetableRepository.getTransfersByMode(StreetMode.WALK); + var bikeTransfers = timetableRepository.getTransfersByMode(StreetMode.BIKE); + var carTransfers = timetableRepository.getTransfersByMode(StreetMode.CAR); assertTransfers( walkTransfers, @@ -434,15 +422,32 @@ public void testMultipleRequestsWithPatternsAndWithCarsAllowedPatterns() { ) .buildGraph(); + var walkTransfers = timetableRepository.getTransfersByMode(StreetMode.WALK); + var bikeTransfers = timetableRepository.getTransfersByMode(StreetMode.BIKE); + var carTransfers = timetableRepository.getTransfersByMode(StreetMode.CAR); + assertTransfers( - timetableRepository.getAllPathTransfers(), + walkTransfers, tr(S0, 100, List.of(V0, V11), S11), tr(S0, 100, List.of(V0, V21), S21), - tr(S0, 200, List.of(V0, V12), S12), tr(S11, 100, List.of(V11, V21), S21), + tr(S0, 200, List.of(V0, V12), S12), + tr(S11, 100, List.of(V11, V12), S12) + ); + assertTransfers( + bikeTransfers, + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 100, List.of(V0, V21), S21), tr(S11, 110, List.of(V11, V22), S22), + tr(S0, 200, List.of(V0, V12), S12), tr(S11, 100, List.of(V11, V12), S12) ); + assertTransfers( + carTransfers, + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 200, List.of(V0, V12), S12), + tr(S0, 100, List.of(V0, V21), S21) + ); } @Test @@ -518,6 +523,110 @@ public void testBikeRequestWithPatternsAndWithCarsAllowedPatternsWithoutCarInTra ); } + @Test + public void testDisableDefaultTransfersForMode() { + var reqWalk = new RouteRequest(); + reqWalk.journey().transfer().setMode(StreetMode.WALK); + + var reqBike = new RouteRequest(); + reqBike.journey().transfer().setMode(StreetMode.BIKE); + + var reqCar = new RouteRequest(); + reqCar.journey().transfer().setMode(StreetMode.CAR); + + var transferRequests = List.of(reqWalk, reqBike, reqCar); + + var otpModel = model(true, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + TransferParameters.Builder transferParametersBuilderBike = new TransferParameters.Builder(); + transferParametersBuilderBike.withDisableDefaultTransfers(true); + TransferParameters.Builder transferParametersBuilderCar = new TransferParameters.Builder(); + transferParametersBuilderCar.withDisableDefaultTransfers(true); + Map transferParametersForMode = new HashMap<>(); + transferParametersForMode.put(StreetMode.BIKE, transferParametersBuilderBike.build()); + transferParametersForMode.put(StreetMode.CAR, transferParametersBuilderCar.build()); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + MAX_TRANSFER_DURATION, + transferRequests, + transferParametersForMode + ) + .buildGraph(); + + var walkTransfers = timetableRepository.getTransfersByMode(StreetMode.WALK); + var bikeTransfers = timetableRepository.getTransfersByMode(StreetMode.BIKE); + var carTransfers = timetableRepository.getTransfersByMode(StreetMode.CAR); + + assertTransfers( + walkTransfers, + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 100, List.of(V0, V21), S21), + tr(S11, 100, List.of(V11, V21), S21), + tr(S0, 200, List.of(V0, V12), S12), + tr(S11, 100, List.of(V11, V12), S12) + ); + assertTransfers(bikeTransfers); + assertTransfers(carTransfers); + } + + @Test + public void testMaxTransferDurationForMode() { + var reqWalk = new RouteRequest(); + reqWalk.journey().transfer().setMode(StreetMode.WALK); + + var reqBike = new RouteRequest(); + reqBike.journey().transfer().setMode(StreetMode.BIKE); + + var transferRequests = List.of(reqWalk, reqBike); + + var otpModel = model(true, false, false, true); + var graph = otpModel.graph(); + graph.hasStreets = true; + var timetableRepository = otpModel.timetableRepository(); + + TransferParameters.Builder transferParametersBuilderWalk = new TransferParameters.Builder(); + transferParametersBuilderWalk.withMaxTransferDuration(Duration.ofSeconds(100)); + TransferParameters.Builder transferParametersBuilderBike = new TransferParameters.Builder(); + transferParametersBuilderBike.withMaxTransferDuration(Duration.ofSeconds(21)); + Map transferParametersForMode = new HashMap<>(); + transferParametersForMode.put(StreetMode.WALK, transferParametersBuilderWalk.build()); + transferParametersForMode.put(StreetMode.BIKE, transferParametersBuilderBike.build()); + + new DirectTransferGenerator( + graph, + timetableRepository, + DataImportIssueStore.NOOP, + MAX_TRANSFER_DURATION, + transferRequests, + transferParametersForMode + ) + .buildGraph(); + + var walkTransfers = timetableRepository.getTransfersByMode(StreetMode.WALK); + var bikeTransfers = timetableRepository.getTransfersByMode(StreetMode.BIKE); + var carTransfers = timetableRepository.getTransfersByMode(StreetMode.CAR); + + assertTransfers( + walkTransfers, + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 100, List.of(V0, V21), S21), + tr(S11, 100, List.of(V11, V21), S21), + tr(S11, 100, List.of(V11, V12), S12) + ); + assertTransfers( + bikeTransfers, + tr(S0, 100, List.of(V0, V11), S11), + tr(S0, 100, List.of(V0, V21), S21) + ); + assertTransfers(carTransfers); + } + private TestOtpModel model(boolean addPatterns) { return model(addPatterns, false); } From 1bf3f655ab25bb7194d8d0980665c58d34b4f359 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Thu, 12 Dec 2024 13:43:09 +0000 Subject: [PATCH 043/195] add feature check around the parallel operation --- .../transit/RaptorTransferIndex.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) 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 f8d0cf1f696..aa335939086 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 @@ -6,6 +6,7 @@ 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.street.search.request.StreetSearchRequest; @@ -36,25 +37,26 @@ public static RaptorTransferIndex create( reversedTransfers.add(new ArrayList<>()); } - IntStream - .range(0, transfersByStopIndex.size()) - .parallel() - .forEach(fromStop -> { - // The transfers are filtered so that there is only one possible directional transfer - // for a stop pair. - var transfers = transfersByStopIndex - .get(fromStop) - .stream() - .flatMap(s -> s.asRaptorTransfer(request).stream()) - .collect( - toMap(RaptorTransfer::stop, Function.identity(), (a, b) -> a.c1() < b.c1() ? a : b) - ) - .values(); + var stopIndices = IntStream.range(0, transfersByStopIndex.size()); + if (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 + .get(fromStop) + .stream() + .flatMap(s -> s.asRaptorTransfer(request).stream()) + .collect( + toMap(RaptorTransfer::stop, Function.identity(), (a, b) -> a.c1() < b.c1() ? a : b) + ) + .values(); - // 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); - }); + // 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 (int fromStop = 0; fromStop < transfersByStopIndex.size(); fromStop++) { for (var forwardTransfer : forwardTransfers.get(fromStop)) { From cded8677ecdae7553713395976c5502b20e41661 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Thu, 12 Dec 2024 15:16:49 +0000 Subject: [PATCH 044/195] Always parallelize cache building during server startup --- .../raptoradapter/transit/RaptorTransferIndex.java | 13 +++++++++++-- .../transit/request/RaptorRequestTransferCache.java | 5 +++-- 2 files changed, 14 insertions(+), 4 deletions(-) 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 aa335939086..61ece117388 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 @@ -25,9 +25,16 @@ public RaptorTransferIndex( this.reversedTransfers = reversedTransfers.stream().map(List::copyOf).toArray(List[]::new); } + /** + * Create an index to be put into the transfer cache + * + * @param isRuntimeRequest true if the request originates from the client during the runtime, + * false if the request comes from transferCacheRequests in router-config.json + */ public static RaptorTransferIndex create( List> transfersByStopIndex, - StreetSearchRequest request + StreetSearchRequest request, + boolean isRuntimeRequest ) { var forwardTransfers = new ArrayList>(transfersByStopIndex.size()); var reversedTransfers = new ArrayList>(transfersByStopIndex.size()); @@ -38,7 +45,9 @@ public static RaptorTransferIndex create( } var stopIndices = IntStream.range(0, transfersByStopIndex.size()); - if (OTPFeature.ParallelRouting.isOn()) { + // we want to always parallelize the cache building during the startup + // and only parallelize during runtime requests if the feature flag is on + if (!isRuntimeRequest || OTPFeature.ParallelRouting.isOn()) { stopIndices = stopIndices.parallel(); } stopIndices.forEach(fromStop -> { 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..c7e09e12729 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 @@ -38,7 +38,8 @@ public void put(List> transfersByStopIndex, RouteRequest request) final CacheKey cacheKey = new CacheKey(transfersByStopIndex, request); final RaptorTransferIndex raptorTransferIndex = RaptorTransferIndex.create( transfersByStopIndex, - cacheKey.request + cacheKey.request, + false ); LOG.info("Initializing cache with request: {}", cacheKey.options); @@ -58,7 +59,7 @@ 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.create(cacheKey.transfersByStopIndex, cacheKey.request, true); } }; } From 0440bac10b6776351ebd60a3919ef4e0adf7a25c Mon Sep 17 00:00:00 2001 From: a-limyr Date: Mon, 16 Dec 2024 10:14:44 +0100 Subject: [PATCH 045/195] Restructuring of the layout of the UI. Moved generated arguments to left side of screen and a lot of other minor changes. --- client/codegen-preprocess.ts | 21 ++ client/codegen.ts | 1 + client/package.json | 2 + .../ItineraryList/ItineraryListContainer.tsx | 114 +++++--- .../ItineraryPaginationControl.tsx | 2 +- .../components/MapView/DebugLayerButton.tsx | 43 +++ .../src/components/MapView/LayerControl.tsx | 230 ++++++--------- client/src/components/MapView/MapView.tsx | 14 +- client/src/components/MapView/RightMenu.tsx | 79 ++++++ .../SearchBar/DepartureArrivalSelect.tsx | 2 +- .../SearchBar/InputFieldsSection.tsx | 70 +++++ .../SearchBar/ItineraryFilterDebugSelect.tsx | 4 +- .../src/components/SearchBar/LogoSection.tsx | 31 +++ .../SearchBar/NumTripPatternsInput.tsx | 4 +- client/src/components/SearchBar/SearchBar.tsx | 93 ++++--- .../SearchInput/DefaultValueTooltip.tsx | 15 + .../components/SearchInput/TripArguments.ts | 20 ++ .../SearchInput/TripQueryArguments.tsx | 262 ++++++++++++++++++ .../SearchInput/ViewArgumentsRaw.tsx | 45 +++ .../components/SearchInput/nestedUtils.tsx | 32 +++ client/src/screens/App.tsx | 61 ++-- client/src/static/img/code.svg | 1 + client/src/static/img/data-visualization.svg | 1 + client/src/static/img/debug-layer.svg | 1 + client/src/static/img/filter.svg | 1 + client/src/static/img/graph.svg | 3 + client/src/static/img/graphic.svg | 1 + .../static/query/selector.fragment.graphql | 63 +++++ client/src/static/query/tripQuery.tsx | 95 ------- client/src/style.css | 197 +++++++++++-- client/src/util/generate-arguments.cjs | 138 +++++++++ client/src/util/generate-queries.cjs | 67 +++++ 32 files changed, 1344 insertions(+), 369 deletions(-) create mode 100644 client/codegen-preprocess.ts create mode 100644 client/src/components/MapView/DebugLayerButton.tsx create mode 100644 client/src/components/MapView/RightMenu.tsx create mode 100644 client/src/components/SearchBar/InputFieldsSection.tsx create mode 100644 client/src/components/SearchBar/LogoSection.tsx create mode 100644 client/src/components/SearchInput/DefaultValueTooltip.tsx create mode 100644 client/src/components/SearchInput/TripArguments.ts create mode 100644 client/src/components/SearchInput/TripQueryArguments.tsx create mode 100644 client/src/components/SearchInput/ViewArgumentsRaw.tsx create mode 100644 client/src/components/SearchInput/nestedUtils.tsx create mode 100644 client/src/static/img/code.svg create mode 100644 client/src/static/img/data-visualization.svg create mode 100644 client/src/static/img/debug-layer.svg create mode 100644 client/src/static/img/filter.svg create mode 100644 client/src/static/img/graph.svg create mode 100644 client/src/static/img/graphic.svg create mode 100644 client/src/static/query/selector.fragment.graphql delete mode 100644 client/src/static/query/tripQuery.tsx create mode 100644 client/src/util/generate-arguments.cjs create mode 100644 client/src/util/generate-queries.cjs diff --git a/client/codegen-preprocess.ts b/client/codegen-preprocess.ts new file mode 100644 index 00000000000..c0747c7e29d --- /dev/null +++ b/client/codegen-preprocess.ts @@ -0,0 +1,21 @@ +import type { CodegenConfig } from '@graphql-codegen/cli'; +import * as path from 'node:path'; + +const config: CodegenConfig = { + overwrite: true, + schema: '../application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql', + documents: 'src/**/*.{ts,tsx}', + generates: { + 'src/static/query/tripQuery.tsx': { + plugins: [path.resolve(__dirname, './src/util/generate-queries.cjs')], + }, + 'src/gql/query-arguments.json': { + plugins: [path.resolve(__dirname, './src/util/generate-arguments.cjs')], + config: { + excludeDeprecated: true, // Ensure this is set to true + }, + }, + }, +}; + +export default config; diff --git a/client/codegen.ts b/client/codegen.ts index a8ad1e40c49..2a80c1de5f5 100644 --- a/client/codegen.ts +++ b/client/codegen.ts @@ -1,4 +1,5 @@ import type { CodegenConfig } from '@graphql-codegen/cli'; +import * as path from 'node:path'; const config: CodegenConfig = { overwrite: true, diff --git a/client/package.json b/client/package.json index d511ba477bc..ee356837dd9 100644 --- a/client/package.json +++ b/client/package.json @@ -14,6 +14,8 @@ "preview": "vite preview", "prebuild": "npm run codegen && npm run lint && npm run check-format", "predev": "npm run codegen", + "codegen-preprocess": "graphql-codegen --config codegen-preprocess.ts", + "precodegen": "npm run codegen-preprocess", "codegen": "graphql-codegen --config codegen.ts" }, "dependencies": { diff --git a/client/src/components/ItineraryList/ItineraryListContainer.tsx b/client/src/components/ItineraryList/ItineraryListContainer.tsx index b474d2eb5ec..36ab2186bc7 100644 --- a/client/src/components/ItineraryList/ItineraryListContainer.tsx +++ b/client/src/components/ItineraryList/ItineraryListContainer.tsx @@ -1,4 +1,4 @@ -import { QueryType } from '../../gql/graphql.ts'; +import { QueryType, TripQueryVariables } from '../../gql/graphql.ts'; import { Accordion } from 'react-bootstrap'; import { useContainerWidth } from './useContainerWidth.ts'; import { ItineraryHeaderContent } from './ItineraryHeaderContent.tsx'; @@ -7,6 +7,9 @@ import { ItineraryDetails } from './ItineraryDetails.tsx'; import { ItineraryPaginationControl } from './ItineraryPaginationControl.tsx'; import { useContext } from 'react'; import { TimeZoneContext } from '../../hooks/TimeZoneContext.ts'; +import { useState } from 'react'; +import ViewArgumentsRaw from '../SearchInput/ViewArgumentsRaw.tsx'; +import TripQueryArguments from '../SearchInput/TripQueryArguments.tsx'; export function ItineraryListContainer({ tripQueryResult, @@ -14,54 +17,89 @@ export function ItineraryListContainer({ setSelectedTripPatternIndex, pageResults, loading, + tripQueryVariables, + setTripQueryVariables, }: { tripQueryResult: QueryType | null; selectedTripPatternIndex: number; setSelectedTripPatternIndex: (selectedTripPatterIndex: number) => void; pageResults: (cursor: string) => void; loading: boolean; + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (variables: TripQueryVariables) => void; }) { const [earliestStartTime, latestEndTime] = useEarliestAndLatestTimes(tripQueryResult); const { containerRef, containerWidth } = useContainerWidth(); const timeZone = useContext(TimeZoneContext); + // State for toggling between Accordion and new element + const [showArguments, setShowArguments] = useState(false); + return ( -
- - setSelectedTripPatternIndex(parseInt(eventKey as string))} - > - {tripQueryResult && - tripQueryResult.trip.tripPatterns.map((tripPattern, itineraryIndex) => ( - - - - - - - - - ))} - -
- All times in {timeZone} -
-
+
+
+ + +
+ + {showArguments ? ( +
+

+ Trip arguments +

+ + +
+ ) : ( + setSelectedTripPatternIndex(parseInt(eventKey as string))} + > + {tripQueryResult && + tripQueryResult.trip.tripPatterns.map((tripPattern, itineraryIndex) => ( + + + + + + + + + ))} + + )} + +
+ All times in {timeZone} +
+
); } diff --git a/client/src/components/ItineraryList/ItineraryPaginationControl.tsx b/client/src/components/ItineraryList/ItineraryPaginationControl.tsx index bf74c83fbca..2f06e289154 100644 --- a/client/src/components/ItineraryList/ItineraryPaginationControl.tsx +++ b/client/src/components/ItineraryList/ItineraryPaginationControl.tsx @@ -12,7 +12,7 @@ export function ItineraryPaginationControl({ loading: boolean; }) { return ( -
+
+ ); +} diff --git a/client/src/components/MapView/LayerControl.tsx b/client/src/components/MapView/LayerControl.tsx index e79ae95e61e..cfad6dcae48 100644 --- a/client/src/components/MapView/LayerControl.tsx +++ b/client/src/components/MapView/LayerControl.tsx @@ -1,155 +1,97 @@ +import React, { useEffect, useState } from 'react'; import type { ControlPosition } from 'react-map-gl'; -import { useControl } from 'react-map-gl'; -import { IControl, Map, TypedStyleLayer } from 'maplibre-gl'; +import type { MapRef } from 'react-map-gl/maplibre'; -type LayerControlProps = { +interface LayerControlProps { + mapRef: MapRef | null; position: ControlPosition; setInteractiveLayerIds: (interactiveLayerIds: string[]) => void; -}; +} + +const LayerControl: React.FC = ({ mapRef, setInteractiveLayerIds }) => { + const [layerGroups, setLayerGroups] = useState>({}); + + useEffect(() => { + if (!mapRef) return; + + const mapInstance = mapRef.getMap(); -/** - * A maplibre control that allows you to switch vector tile layers on and off. - * - * It appears that you cannot use React elements but have to drop down to raw DOM. Please correct - * me if I'm wrong. - */ -class LayerControl implements IControl { - private readonly container: HTMLDivElement = document.createElement('div'); - - private readonly setInteractiveLayerIds: (interactiveLayerIds: string[]) => void; - - constructor(setInteractiveLayerIds: (interactiveLayerIds: string[]) => void) { - this.setInteractiveLayerIds = setInteractiveLayerIds; - } - - onAdd(map: Map) { - this.container.className = 'maplibregl-ctrl maplibregl-ctrl-group layer-select'; - - map.on('load', () => { - // clean on - while (this.container.firstChild) { - this.container.removeChild(this.container.firstChild); - } - - const title = document.createElement('h4'); - title.textContent = 'Debug layers'; - this.container.appendChild(title); - - const groups: Record = {}; - map - .getLayersOrder() - .map((l) => map.getLayer(l)) - .filter((layer) => !!layer) - .filter((layer) => this.layerInteractive(layer)) - .reverse() - .forEach((layer) => { - if (layer) { - const meta: { group: string } = layer.metadata as { group: string }; - - let groupName: string = 'Misc'; - if (meta.group) { - groupName = meta.group; - } - - const layerDiv = this.buildLayerDiv(layer as TypedStyleLayer, map); - - if (groups[groupName]) { - groups[groupName]?.appendChild(layerDiv); - } else { - const groupDiv = this.buildGroupDiv(groupName, layerDiv); - groups[groupName] = groupDiv; - this.container.appendChild(groupDiv); - } + const loadLayers = () => { + const groups: Record = {}; + const allLayers = mapInstance.getStyle().layers || []; // Get all layers from the map style + + allLayers.reverse().forEach((layer) => { + if (layer?.type !== 'raster' && !layer?.id.startsWith('jsx')) { + const metadata = (layer as any)?.metadata || {}; + const groupName = metadata.group || 'Misc'; + + if (!groups[groupName]) { + groups[groupName] = []; } - }); - }); - - return this.container; - } - - private updateInteractiveLayerIds(map: Map) { - const visibleInteractiveLayerIds = map - .getLayersOrder() - .map((l) => map.getLayer(l)) - .filter((layer) => !!layer) - .filter((layer) => this.layerVisible(map, layer) && this.layerInteractive(layer)) - .map((layer) => layer.id); - this.setInteractiveLayerIds(visibleInteractiveLayerIds); - } - - private buildLayerDiv(layer: TypedStyleLayer, map: Map) { - const layerDiv = document.createElement('div'); - layerDiv.className = 'layer'; - const input = document.createElement('input'); - input.type = 'checkbox'; - input.value = layer.id; - input.id = layer.id; - input.onchange = (e) => { - e.preventDefault(); - e.stopPropagation(); - if (input.checked) { - map.setLayoutProperty(layer.id, 'visibility', 'visible'); - } else { - map.setLayoutProperty(layer.id, 'visibility', 'none'); - } - this.updateInteractiveLayerIds(map); - }; - input.checked = this.layerVisible(map, layer); - input.className = 'layer'; - const label = document.createElement('label'); - label.textContent = layer.id; - label.htmlFor = layer.id; - layerDiv.appendChild(input); - layerDiv.appendChild(label); - return layerDiv; - } - - private buildGroupDiv(groupName: string, layerDiv: HTMLDivElement) { - const groupDiv = document.createElement('div'); - groupDiv.className = 'group'; - - const groupInput = document.createElement('input'); - groupInput.onchange = () => { - groupDiv.querySelectorAll('input.layer').forEach((input) => { - input.checked = groupInput.checked; - const event = new Event('change'); - input.dispatchEvent(event); + groups[groupName].push({ id: layer.id, name: layer.id }); + } }); + + setLayerGroups(groups); }; - groupInput.type = 'checkbox'; - groupInput.id = groupName; - - const groupLabel = document.createElement('label'); - groupLabel.textContent = groupName; - groupLabel.htmlFor = groupName; - groupLabel.className = 'group-label'; - - groupDiv.appendChild(groupInput); - groupDiv.appendChild(groupLabel); - groupDiv.appendChild(layerDiv); - return groupDiv; - } - - private layerVisible(map: Map, layer: { id: string }) { - return map.getLayoutProperty(layer.id, 'visibility') !== 'none'; - } - - private layerInteractive(layer: { id: string; type: string }) { - // the polylines of the routing result are put in map layers called jsx-1, jsx-2... - // we don't want them to show up in the debug layer selector - return layer?.type !== 'raster' && !layer?.id.startsWith('jsx'); - } - - onRemove() { - this.container.parentNode?.removeChild(this.container); - } -} -export default function DebugLayerControl(props: LayerControlProps) { - useControl(() => new LayerControl(props.setInteractiveLayerIds), { - position: props.position, - }); + // Ensure layers are loaded + if (mapInstance.isStyleLoaded()) { + loadLayers(); + } else { + mapInstance.on('styledata', loadLayers); + } - return null; -} + return () => { + mapInstance.off('styledata', loadLayers); + }; + }, [mapRef]); + + const toggleLayerVisibility = (layerId: string, isVisible: boolean) => { + if (!mapRef) return; + + const mapInstance = mapRef.getMap(); + mapInstance.setLayoutProperty(layerId, 'visibility', isVisible ? 'visible' : 'none'); + + const visibleLayers = Object.values(layerGroups) + .flat() + .filter((layer) => mapInstance.getLayoutProperty(layer.id, 'visibility') === 'visible') + .map((layer) => layer.id); + + setInteractiveLayerIds(visibleLayers); + }; + + return ( +
+

Debug Layers

+ {Object.entries(layerGroups).map(([groupName, layers]) => ( +
+
{groupName}
+ {layers.map((layer) => ( + + ))} +
+ ))} +
+ ); +}; + +export default LayerControl; diff --git a/client/src/components/MapView/MapView.tsx b/client/src/components/MapView/MapView.tsx index 8eb66f8c446..f195857d962 100644 --- a/client/src/components/MapView/MapView.tsx +++ b/client/src/components/MapView/MapView.tsx @@ -6,16 +6,17 @@ import { MapMouseEvent, NavigationControl, VectorTileSource, + MapRef, } from 'react-map-gl/maplibre'; import 'maplibre-gl/dist/maplibre-gl.css'; import { TripPattern, TripQuery, TripQueryVariables } from '../../gql/graphql.ts'; import { NavigationMarkers } from './NavigationMarkers.tsx'; import { LegLines } from './LegLines.tsx'; import { useMapDoubleClick } from './useMapDoubleClick.ts'; -import { useState, useCallback } from 'react'; +import { useState, useCallback, useRef } from 'react'; import { ContextMenuPopup } from './ContextMenuPopup.tsx'; import { GeometryPropertyPopup } from './GeometryPropertyPopup.tsx'; -import DebugLayerControl from './LayerControl.tsx'; +import RightMenu from './RightMenu.tsx'; const styleUrl = import.meta.env.VITE_DEBUG_STYLE_URL; @@ -65,7 +66,7 @@ export function MapView({ map.fitBounds(source.bounds, { maxDuration: 50, linear: true }); } }; - + const mapRef = useRef(null); // Create a ref for MapRef return (
- + {tripQueryResult?.trip.tripPatterns.length && ( )} diff --git a/client/src/components/MapView/RightMenu.tsx b/client/src/components/MapView/RightMenu.tsx new file mode 100644 index 00000000000..27e91c8fb35 --- /dev/null +++ b/client/src/components/MapView/RightMenu.tsx @@ -0,0 +1,79 @@ +import { Component } from 'react'; +import DebugLayerControl from './LayerControl'; +import { ControlPosition } from 'react-map-gl'; +import debugLayerIcon from '../../static/img/graph.svg'; +import { MapRef } from 'react-map-gl/maplibre'; + +interface RightMenuProps { + setInteractiveLayerIds: (interactiveLayerIds: string[]) => void; + position: ControlPosition; + mapRef: MapRef | null; +} + +interface RightMenuState { + isSidebarOpen: boolean; + activeContent: 'debugLayer' | 'tripFilters' | null; // Track the active content +} + +class RightMenu extends Component { + constructor(props: RightMenuProps) { + super(props); + this.state = { + isSidebarOpen: false, + activeContent: null, + }; + } + + // Method to toggle the sidebar and set the active content + toggleSidebar = (content: RightMenuState['activeContent']) => { + this.setState((prevState) => ({ + isSidebarOpen: prevState.activeContent !== content || !prevState.isSidebarOpen, + activeContent: prevState.activeContent === content ? null : content, // Toggle content + })); + }; + + render() { + const { isSidebarOpen, activeContent } = this.state; + const { position, setInteractiveLayerIds, mapRef } = this.props; + + return ( +
+ {/* Buttons to control sidebar */} + + + {/* Sidebar */} +
+ {isSidebarOpen && activeContent === 'debugLayer' && ( + + )} +
+
+ ); + } +} + +export default RightMenu; diff --git a/client/src/components/SearchBar/DepartureArrivalSelect.tsx b/client/src/components/SearchBar/DepartureArrivalSelect.tsx index b6a92cdd495..861e795d74b 100644 --- a/client/src/components/SearchBar/DepartureArrivalSelect.tsx +++ b/client/src/components/SearchBar/DepartureArrivalSelect.tsx @@ -18,7 +18,7 @@ export function DepartureArrivalSelect({ return ( - Departure/Arrival + void; + onRoute: () => void; + loading: boolean; +}; + +export function InputFieldsSection({ + tripQueryVariables, + setTripQueryVariables, + onRoute, + loading, +}: InputFieldsSectionProps) { + return ( +
+
+ + + +
+
+ + +
+
+ + +
+
+ + + + +
+ + +
+ + + + +
+
+ ); +} diff --git a/client/src/components/SearchBar/ItineraryFilterDebugSelect.tsx b/client/src/components/SearchBar/ItineraryFilterDebugSelect.tsx index 6f479290947..5c781d93e7d 100644 --- a/client/src/components/SearchBar/ItineraryFilterDebugSelect.tsx +++ b/client/src/components/SearchBar/ItineraryFilterDebugSelect.tsx @@ -20,10 +20,10 @@ export function ItineraryFilterDebugSelect({ onChange={(e) => { setTripQueryVariables({ ...tripQueryVariables, - itineraryFiltersDebug: e.target.value as ItineraryFilterDebugProfile, + itineraryFilters: { debug: e.target.value as ItineraryFilterDebugProfile }, }); }} - value={tripQueryVariables.itineraryFiltersDebug || 'not_selected'} + value={tripQueryVariables.itineraryFilters?.debug || 'not_selected'} > {Object.values(ItineraryFilterDebugProfile).map((debugProfile) => ( diff --git a/client/src/components/SearchBar/LogoSection.tsx b/client/src/components/SearchBar/LogoSection.tsx new file mode 100644 index 00000000000..1acaa38a636 --- /dev/null +++ b/client/src/components/SearchBar/LogoSection.tsx @@ -0,0 +1,31 @@ +import { useState, useRef } from 'react'; +import Navbar from 'react-bootstrap/Navbar'; +import { ServerInfo } from '../../gql/graphql.ts'; +import { ServerInfoTooltip } from './ServerInfoTooltip.tsx'; +import logo from '../../static/img/otp-logo.svg'; + +type LogoSectionProps = { + serverInfo?: ServerInfo; +}; + +export function LogoSection({ serverInfo }: LogoSectionProps) { + const [showServerInfo, setShowServerInfo] = useState(false); + const target = useRef(null); + + return ( +
+ setShowServerInfo((v) => !v)}> +
+ + OTP Debug + {showServerInfo && } +
+
+
+
Version: {serverInfo?.version}
+
Time zone: {serverInfo?.internalTransitModelTimeZone}
+
+
+ ); +} diff --git a/client/src/components/SearchBar/NumTripPatternsInput.tsx b/client/src/components/SearchBar/NumTripPatternsInput.tsx index 360ce1c2c73..ae33e2f4e19 100644 --- a/client/src/components/SearchBar/NumTripPatternsInput.tsx +++ b/client/src/components/SearchBar/NumTripPatternsInput.tsx @@ -11,7 +11,7 @@ export function NumTripPatternsInput({ return ( - Num. results + # setTripQueryVariables({ diff --git a/client/src/components/SearchBar/SearchBar.tsx b/client/src/components/SearchBar/SearchBar.tsx index 73df12fe103..187115e6380 100644 --- a/client/src/components/SearchBar/SearchBar.tsx +++ b/client/src/components/SearchBar/SearchBar.tsx @@ -31,46 +31,63 @@ export function SearchBar({ onRoute, tripQueryVariables, setTripQueryVariables, const target = useRef(null); return ( -
- setShowServerInfo((v) => !v)}> -
- OTP Debug Client - {showServerInfo && } + + <> + +
+
+ setShowServerInfo((v) => !v)}> +
+ OTP Debug + {showServerInfo && } +
+
- - - - - - - - - - - - - - +
+ + + + + + + + + + + + + -
- - - - +
+ + + + +
+
-
+ ); } diff --git a/client/src/components/SearchInput/DefaultValueTooltip.tsx b/client/src/components/SearchInput/DefaultValueTooltip.tsx new file mode 100644 index 00000000000..a1eef4b0c65 --- /dev/null +++ b/client/src/components/SearchInput/DefaultValueTooltip.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +interface DefaultValueTooltipProps { + defaultValue: any; +} + +const DefaultValueTooltip: React.FC = ({ defaultValue }) => { + return ( + + ! + + ); +}; + +export default DefaultValueTooltip; diff --git a/client/src/components/SearchInput/TripArguments.ts b/client/src/components/SearchInput/TripArguments.ts new file mode 100644 index 00000000000..600fc9fd821 --- /dev/null +++ b/client/src/components/SearchInput/TripArguments.ts @@ -0,0 +1,20 @@ +export interface TripArguments { + trip: { + arguments: { + [key: string]: Argument; + }; + }; +} + +export interface Argument { + type: TypeDescriptor; + defaultValue?: any; +} + +export type TypeDescriptor = ScalarType | NestedObject; + +export type ScalarType = 'ID' | 'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime' | 'Duration'; + +export interface NestedObject { + [key: string]: Argument | string[]; // Allows for nested objects or enum arrays +} diff --git a/client/src/components/SearchInput/TripQueryArguments.tsx b/client/src/components/SearchInput/TripQueryArguments.tsx new file mode 100644 index 00000000000..40888f537e7 --- /dev/null +++ b/client/src/components/SearchInput/TripQueryArguments.tsx @@ -0,0 +1,262 @@ +import React, { useEffect, useState } from 'react'; +import tripArgumentsData from '../../gql/query-arguments.json'; +import { TripQueryVariables } from '../../gql/graphql'; +import { getNestedValue, setNestedValue } from './nestedUtils'; +import DefaultValueTooltip from './DefaultValueTooltip.tsx'; + +interface TripQueryArgumentsProps { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +} + +function formatArgumentName(input: string): string { + if (!input) { + return ' '; + } + const parts = input.split('.'); + const formatted = parts[parts.length - 1].replace(/([A-Z])/g, ' $1').trim(); + return formatted.replace(/\b\w/g, (char) => char.toUpperCase())+' '; +} + +const TripQueryArguments: React.FC = ({ tripQueryVariables, setTripQueryVariables }) => { + const [argumentsList, setArgumentsList] = useState< + { path: string; type: string; subtype?: string; defaultValue: any; enumValues?: string[]; isComplex?: boolean }[] + >([]); + const [expandedArguments, setExpandedArguments] = useState<{ [key: string]: boolean }>({}); + const [searchText, setSearchText] = useState(''); + + useEffect(() => { + const tripArgs = tripArgumentsData.trip.arguments; + const extractedArgs = extractAllArgs(tripArgs); + setArgumentsList(extractedArgs); + }, []); + + const extractAllArgs = ( + args: { [key: string]: any }, + parentPath: string[] = [], + ): { path: string; type: string; name?: string; defaultValue: any; enumValues?: string[]; isComplex?: boolean }[] => { + let allArgs: { + path: string; + type: string; + name?: string; + defaultValue: any; + enumValues?: string[]; + isComplex?: boolean; + }[] = []; + + Object.entries(args).forEach(([argName, argData]) => { + const currentPath = [...parentPath, argName].join('.'); + allArgs = allArgs.concat(processArgument(argName, argData, currentPath, parentPath)); + }); + + return allArgs; + }; + + const processArgument = ( + argName: string, + argData: any, + currentPath: string, + parentPath: string[], + ): { path: string; type: string; name?: string; defaultValue: any; enumValues?: string[]; isComplex?: boolean }[] => { + let allArgs: { + path: string; + type: string; + name?: string; + defaultValue: any; + enumValues?: string[]; + isComplex?: boolean; + }[] = []; + + if (typeof argData === 'object' && argData.type) { + if (argData.type.type === 'Enum') { + const enumValues = ['Not selected', ...argData.type.values]; + const defaultValue = argData.defaultValue !== undefined ? argData.defaultValue : 'Not selected'; + allArgs.push({ path: currentPath, type: 'Enum', defaultValue, enumValues }); + } else if (argData.type.type === 'InputObject' && argData.type.fields) { + allArgs.push({ path: currentPath, type: 'Group', defaultValue: null, isComplex: true }); + allArgs = allArgs.concat(extractAllArgs(argData.type.fields, [...parentPath, argName])); + } else if (argData.type.type === 'Scalar') { + allArgs.push({ path: currentPath, type: argData.type.subtype, defaultValue: argData.defaultValue }); + } + } else if (typeof argData === 'object' && argData.fields) { + allArgs.push({ path: currentPath, type: 'Group', defaultValue: null, isComplex: true }); + allArgs = allArgs.concat(extractAllArgs(argData.fields, [...parentPath, argName])); + } else { + allArgs.push({ path: currentPath, type: argData.type ?? typeof argData, defaultValue: argData.defaultValue }); + } + + return allArgs; + }; + + const handleInputChange = (path: string, value: any) => { + if (typeof value === 'number' && isNaN(value)) { + value = null; + } + let updatedTripQueryVariables = setNestedValue(tripQueryVariables, path, value); + if ( + value === undefined || + value === null || + value === argumentsList.find((arg) => arg.path === path)?.defaultValue + ) { + updatedTripQueryVariables = setNestedValue(updatedTripQueryVariables, path, undefined); + updatedTripQueryVariables = cleanUpParentIfEmpty(updatedTripQueryVariables, path); + } + setTripQueryVariables(updatedTripQueryVariables); + }; + + const cleanUpParentIfEmpty = (variables: any, path: string): any => { + const pathParts = path.split('.'); + for (let i = pathParts.length - 1; i > 0; i--) { + const parentPath = pathParts.slice(0, i).join('.'); + const parentValue = getNestedValue(variables, parentPath); + + if (parentValue && typeof parentValue === 'object') { + const allKeysUndefinedOrDefault = Object.keys(parentValue).every((key) => { + const childPath = `${parentPath}.${key}`; + const childValue = getNestedValue(variables, childPath); + const defaultValue = argumentsList.find((arg) => arg.path === childPath)?.defaultValue; + return childValue === undefined || childValue === null || childValue === defaultValue; + }); + + if (allKeysUndefinedOrDefault) { + variables = setNestedValue(variables, parentPath, undefined); + } + } + } + return variables; + }; + + const toggleExpand = (path: string) => { + setExpandedArguments((prev) => { + const newState = { ...prev }; + newState[path] = !prev[path]; + return newState; + }); + }; + + const filteredArgumentsList = argumentsList.filter(({ path }) => + formatArgumentName(path).toLowerCase().includes(searchText.toLowerCase()), + ); + + const renderArgumentInputs = ( + args: { path: string; type: string; defaultValue: any; enumValues?: string[]; isComplex?: boolean }[], + level: number, + ) => { + return args.map(({ path, type, defaultValue, enumValues, isComplex }) => { + const isExpanded = expandedArguments[path]; + const nestedArgs = argumentsList.filter((arg) => arg.path.startsWith(`${path}.`) && arg.path !== path); + const nestedLevel = level + 1; + + return ( +
+ {isComplex ? ( +
+ toggleExpand(path)} + > + {formatArgumentName(path)} {isExpanded ? '▼' : '▶'} + + {isExpanded && renderArgumentInputs(nestedArgs, nestedLevel)} +
+ ) : ( +
+ + {type === 'Boolean' && ( + handleInputChange(path, e.target.checked)} + /> + )} + {['String', 'DoubleFunction', 'ID'].includes(type) && ( + handleInputChange(path, e.target.value || undefined)} + /> + )} + {type === 'Int' && ( + handleInputChange(path, parseInt(e.target.value, 10) || undefined)} + /> + )} + {type === 'Float' && ( + handleInputChange(path, parseFloat(e.target.value) || undefined)} + /> + )} + {type === 'DateTime' && ( + { + const newValue = e.target.value ? new Date(e.target.value).toISOString() : undefined; + handleInputChange(path, newValue); + }} + /> + )} + {type === 'Enum' && enumValues && ( + + )} +
+ )} +
+ ); + }); + }; + + return ( +
+
+ setSearchText(e.target.value)} + style={{ width: '100%', padding: '8px', fontSize: '16px' }} + /> +
+ {filteredArgumentsList.length === 0 ? ( +

No arguments found.

+ ) : ( +
+ {renderArgumentInputs( + filteredArgumentsList.filter((arg) => arg.path.split('.').length === 1), + 0, + )} +
+ )} +
+ ); +}; + +export default TripQueryArguments; diff --git a/client/src/components/SearchInput/ViewArgumentsRaw.tsx b/client/src/components/SearchInput/ViewArgumentsRaw.tsx new file mode 100644 index 00000000000..3b6788b3b5f --- /dev/null +++ b/client/src/components/SearchInput/ViewArgumentsRaw.tsx @@ -0,0 +1,45 @@ +import React, { useState } from 'react'; +import { Button, Modal } from 'react-bootstrap'; +import codeIcon from '../../static/img/code.svg'; + +interface ViewArgumentsRawProps { + tripQueryVariables: any; +} + +const ViewArgumentsRaw: React.FC = ({ tripQueryVariables }) => { + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const openDialog = () => { + setIsDialogOpen(true); + }; + + const closeDialog = () => { + setIsDialogOpen(false); + }; + + return ( + <> + + + + + Trip Query Variables + + +
+              {JSON.stringify(tripQueryVariables, null, 2)}
+            
+
+ + + +
+ + ); +}; + +export default ViewArgumentsRaw; diff --git a/client/src/components/SearchInput/nestedUtils.tsx b/client/src/components/SearchInput/nestedUtils.tsx new file mode 100644 index 00000000000..94b9e9149b4 --- /dev/null +++ b/client/src/components/SearchInput/nestedUtils.tsx @@ -0,0 +1,32 @@ +// src/utils/nestedUtils.ts + +/** + * Retrieves a nested value from an object based on a dot-separated path. + * @param obj - The object to traverse. + * @param path - The dot-separated path string. + * @returns The value at the specified path or undefined if not found. + */ +export const getNestedValue = (obj: any, path: string): any => { + return path.split('.').reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), obj); +}; + +/** + * Sets a nested value in an object based on a dot-separated path. + * Returns a new object with the updated value, ensuring immutability. + * @param obj - The original object. + * @param path - The dot-separated path string. + * @param value - The value to set. + * @returns A new object with the updated value. + */ +export const setNestedValue = (obj: any, path: string, value: any): any => { + const keys = path.split('.'); + const newObj = { ...obj }; + let current = newObj; + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + current[key] = { ...current[key] }; + current = current[key]; + } + current[keys[keys.length - 1]] = value; + return newObj; +}; diff --git a/client/src/screens/App.tsx b/client/src/screens/App.tsx index 1b6b86b7a81..9e85aace7b2 100644 --- a/client/src/screens/App.tsx +++ b/client/src/screens/App.tsx @@ -1,12 +1,12 @@ -import { Stack } from 'react-bootstrap'; import { MapView } from '../components/MapView/MapView.tsx'; -import { SearchBar } from '../components/SearchBar/SearchBar.tsx'; import { ItineraryListContainer } from '../components/ItineraryList/ItineraryListContainer.tsx'; import { useState } from 'react'; import { useTripQuery } from '../hooks/useTripQuery.ts'; import { useServerInfo } from '../hooks/useServerInfo.ts'; import { useTripQueryVariables } from '../hooks/useTripQueryVariables.ts'; import { TimeZoneContext } from '../hooks/TimeZoneContext.ts'; +import { LogoSection } from '../components/SearchBar/LogoSection.tsx'; +import { InputFieldsSection } from '../components/SearchBar/InputFieldsSection.tsx'; export function App() { const serverInfo = useServerInfo(); @@ -18,30 +18,39 @@ export function App() { return (
- - - - - - +
+
+ +
+
+ +
+
+ +
+
+ +
+
); diff --git a/client/src/static/img/code.svg b/client/src/static/img/code.svg new file mode 100644 index 00000000000..d303b8d18b5 --- /dev/null +++ b/client/src/static/img/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/static/img/data-visualization.svg b/client/src/static/img/data-visualization.svg new file mode 100644 index 00000000000..043b9ee35a4 --- /dev/null +++ b/client/src/static/img/data-visualization.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/static/img/debug-layer.svg b/client/src/static/img/debug-layer.svg new file mode 100644 index 00000000000..ac614e639dc --- /dev/null +++ b/client/src/static/img/debug-layer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/static/img/filter.svg b/client/src/static/img/filter.svg new file mode 100644 index 00000000000..cbda5f955d5 --- /dev/null +++ b/client/src/static/img/filter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/static/img/graph.svg b/client/src/static/img/graph.svg new file mode 100644 index 00000000000..6eef9e5100a --- /dev/null +++ b/client/src/static/img/graph.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/src/static/img/graphic.svg b/client/src/static/img/graphic.svg new file mode 100644 index 00000000000..344e8f9d5d5 --- /dev/null +++ b/client/src/static/img/graphic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/static/query/selector.fragment.graphql b/client/src/static/query/selector.fragment.graphql new file mode 100644 index 00000000000..6f3bc847ee7 --- /dev/null +++ b/client/src/static/query/selector.fragment.graphql @@ -0,0 +1,63 @@ +{ + previousPageCursor + nextPageCursor + tripPatterns { + aimedStartTime + aimedEndTime + expectedEndTime + expectedStartTime + duration + distance + legs { + id + mode + aimedStartTime + aimedEndTime + expectedEndTime + expectedStartTime + realtime + distance + duration + fromPlace { + name + quay { + id + } + } + toPlace { + name + quay { + id + } + } + toEstimatedCall { + destinationDisplay { + frontText + } + } + line { + publicCode + name + id + presentation { + colour + } + } + authority { + name + id + } + pointsOnLink { + points + } + interchangeTo { + staySeated + } + interchangeFrom { + staySeated + } + } + systemNotices { + tag + } + } \ No newline at end of file diff --git a/client/src/static/query/tripQuery.tsx b/client/src/static/query/tripQuery.tsx deleted file mode 100644 index 57cca5d4056..00000000000 --- a/client/src/static/query/tripQuery.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { graphql } from '../../gql'; -import { print } from 'graphql/index'; - -export const query = graphql(` - query trip( - $from: Location! - $to: Location! - $arriveBy: Boolean - $dateTime: DateTime - $numTripPatterns: Int - $searchWindow: Int - $modes: Modes - $itineraryFiltersDebug: ItineraryFilterDebugProfile - $wheelchairAccessible: Boolean - $pageCursor: String - ) { - trip( - from: $from - to: $to - arriveBy: $arriveBy - dateTime: $dateTime - numTripPatterns: $numTripPatterns - searchWindow: $searchWindow - modes: $modes - itineraryFilters: { debug: $itineraryFiltersDebug } - wheelchairAccessible: $wheelchairAccessible - pageCursor: $pageCursor - ) { - previousPageCursor - nextPageCursor - tripPatterns { - aimedStartTime - aimedEndTime - expectedEndTime - expectedStartTime - duration - distance - legs { - id - mode - aimedStartTime - aimedEndTime - expectedEndTime - expectedStartTime - realtime - distance - duration - fromPlace { - name - quay { - id - } - } - toPlace { - name - quay { - id - } - } - toEstimatedCall { - destinationDisplay { - frontText - } - } - line { - publicCode - name - id - presentation { - colour - } - } - authority { - name - id - } - pointsOnLink { - points - } - interchangeTo { - staySeated - } - interchangeFrom { - staySeated - } - } - systemNotices { - tag - } - } - } - } -`); - -export const queryAsString = print(query); diff --git a/client/src/style.css b/client/src/style.css index c33f5dff711..e9fd49a92b5 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -1,39 +1,65 @@ -.app { - min-width: 810px; +.layout { + display: grid; + grid-template-columns: 1fr 3fr; + grid-template-rows: 1fr 2fr; + height: 100vh; + gap: 0; } + +.box { + display: flex; + justify-content: center; + align-items: center; +} + +.logo-section { + grid-column: 1 / 2; + grid-row: 1 / 2; +} + +.input-section { + grid-column: 2 / 3; + grid-row: 1 / 2; +} + +.trip-section { + grid-column: 1 / 2; + grid-row: 2 / 3; +} + +.map-section { + grid-column: 2 / 3; + grid-row: 2 / 3; +} + .navbar-brand { color: #4078bc; margin-top: 20px; margin-right: 14px; + font-size: 2rem; } @media (min-width: 1895px) { - .top-content { - height: 75px; - } - .below-content { - height: calc(100vh - 75px); + height: calc(100vh - 175px); } } @media (max-width: 1896px) { - .top-content { - height: 150px; - } - .below-content { - height: calc(100vh - 150px); + height: calc(100vh - 175px); } } -@media (max-width: 1120px) { - .top-content { - height: 200px; +@media (max-width: 1250px) { + .below-content { + height: calc(100vh - 250px); } +} +@media (max-width: 900px) { .below-content { - height: calc(100vh - 200px); + height: calc(100vh - 325px); } } @@ -50,6 +76,10 @@ margin-right: 1rem; } +.search-bar input.input-tiny { + max-width: 50px; +} + .search-bar input.input-small { max-width: 100px; } @@ -73,12 +103,26 @@ margin: 30px 0 auto 0; } -.search-bar .swap-from-to img { +.swap-from-to img { width: 15px; } +.logo-container { + width: 265px; + display: flex; + flex-direction: column; +} + +.logo-container .details { + font-size: 0.8rem; + color: #666; + margin-top: 4px; + text-align: left; +} + .itinerary-list-container { - width: 36rem; + font-size: 12px; + width: 100%; overflow-y: auto; } @@ -197,3 +241,120 @@ .maplibregl-ctrl-group.layer-select div.layer { margin-left: 17px; } + +.right-menu-container { + position: absolute; + top: 0; + right: 0; + width: 0; + height: 100%; + background-color: #f4f4f4; + overflow-x: hidden; + transition: 0.3s; + padding-top: 60px; + box-shadow: none; +} + +.right-menu-container.open { + width: 250px; + box-shadow: -2px 0 5px rgba(0, 0, 0, 0.2); +} + +.left-menu-container { + position: absolute; + top: 0; + left: 0; /* Align to the left side */ + width: 0; + height: 100%; + background-color: #f4f4f4; + overflow-x: hidden; + transition: 0.3s; /* Smooth transition for width */ + padding-top: 60px; + box-shadow: none; +} + +.left-menu-container.open { + width: 250px; /* Expand the sidebar when open */ + box-shadow: 2px 0 5px rgba(0, 0, 0, 0.2); /* Shadow on the right of the sidebar */ +} + +.sidebar-button.right { + position: absolute; + right: 0px; /* Default position when sidebar is closed */ + background: #ccc; + color: white; + border: none; + border-radius: 4px; + padding: 10px; + cursor: pointer; + transition: + right 0.3s, + background-color 0.2s; /* Smooth transitions */ +} + +.sidebar-button.right.open { + right: 270px; /* Shifted position when sidebar is open */ +} + +.sidebar-button.left { + position: absolute; + left: 0px; /* Default position when sidebar is closed */ + background: #ccc; + color: white; + border: none; + border-radius: 4px; + padding: 10px; + cursor: pointer; + transition: + right 0.3s, + background-color 0.2s; /* Smooth transitions */ +} + +.sidebar-button.left.open { + left: 270px; /* Shifted position when sidebar is open */ +} + +.sidebar-button.active { + background: #fff; +} + +.sidebar-button:hover { + background: #4078bc; /* Slightly darker when hovered */ +} + +.sidebar-button.active:hover { + background: #fff; +} + +input[type='number']::-webkit-inner-spin-button, +input[type='number']::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* For Firefox */ +input[type='number'] { + -moz-appearance: textfield; +} + +.default-tooltip-container { + position: relative; + cursor: pointer; +} + +.default-tooltip-icon { + font-size: 14px; + color: #888; + border: 1px solid #ccc; + border-radius: 50%; + width: 18px; + height: 18px; + display: flex; + align-items: center; + justify-content: center; + background-color: #f9f9f9; +} + +.default-tooltip-icon:hover { + background-color: #e6e6e6; +} diff --git a/client/src/util/generate-arguments.cjs b/client/src/util/generate-arguments.cjs new file mode 100644 index 00000000000..ad66daebb8a --- /dev/null +++ b/client/src/util/generate-arguments.cjs @@ -0,0 +1,138 @@ +const fs = require('fs'); +const path = require('path'); +const { + isScalarType, + isInputObjectType, + isNonNullType, + isListType, + isEnumType, +} = require('graphql'); + +/** + * Utility function to resolve the named type (unwrapping NonNull and List types) + */ +function getNamedType(type) { + let namedType = type; + while (isNonNullType(namedType) || isListType(namedType)) { + namedType = namedType.ofType; + } + return namedType; +} + +/** + * Recursively breaks down a GraphQL type into its primitive fields with default values + */ +function resolveType(type, schema = new Set()) { + const namedType = getNamedType(type); + + // Debug: Log the named type + console.log('Resolving type:', namedType.name); + + if (isScalarType(namedType)) { + return { type: 'Scalar', subtype: namedType.name }; + } + + if (isEnumType(namedType)) { + // Debug: Log enum type values + console.log('Enum type detected:', namedType.name, 'Values:', namedType.getValues().map((val) => val.name)); + // Return enum type explicitly + return { type: 'Enum', values: namedType.getValues().map((val) => val.name) }; + } + + if (isInputObjectType(namedType)) { + const fields = namedType.getFields(); + const fieldTypes = {}; + + // Debug: Log the fields of the input object type + console.log('Input object type detected:', namedType.name, 'Fields:', Object.keys(fields)); + + Object.keys(fields).forEach((fieldName) => { + const field = fields[fieldName]; + + // Exclude deprecated fields within input objects + if (field.deprecationReason) { + return; // Skip deprecated fields + } + + const fieldType = field.type; + const fieldDefaultValue = field.defaultValue !== undefined ? field.defaultValue : null; + + // Include defaultValue consistently, setting it to null if not defined + fieldTypes[fieldName] = { + type: resolveType(fieldType, schema), + defaultValue: fieldDefaultValue + }; + }); + return { type: 'InputObject', name: namedType.name, fields: fieldTypes }; + } + + // Handle interfaces and unions if necessary + // For simplicity, treating them as strings + return { type: 'Scalar', subtype: 'String' }; +} + +/** + * Plugin to generate a JSON file with all arguments from a specified query, + * excluding deprecated arguments based on deprecationReason, + * and including their types and default values, + * breaking down complex types into primitives. + */ +const generateTripArgsJsonPlugin = async (schema) => { + try { + const queryType = schema.getQueryType(); + if (!queryType) { + console.error('No Query type found in the schema.'); + return JSON.stringify({ error: 'No Query type found in the schema' }, null, 2); + } + + const tripField = queryType.getFields()['trip']; + if (!tripField) { + console.error('No trip query found in the schema.'); + return JSON.stringify({ error: 'No trip query found in the schema' }, null, 2); + } + + const args = tripField.args; + const argsJson = {}; + + args.forEach((arg) => { + if (arg.deprecationReason) { + return; // Skip deprecated arguments + } + + // Debug: Log each argument being processed + console.log('Processing argument:', arg.name, 'Type:', arg.type.toString()); + + const argName = arg.name; + const argType = resolveType(arg.type, schema); + const argDefaultValue = arg.defaultValue !== undefined ? arg.defaultValue : null; + + // Consistent representation for enum types + if (argDefaultValue !== null) { + argsJson[argName] = { + type: argType, + defaultValue: argDefaultValue, + }; + } else { + argsJson[argName] = { + type: argType, + }; + } + }); + + const output = { + trip: { + arguments: argsJson, + }, + }; + + // Stringify the JSON with indentation for readability + return JSON.stringify(output, null, 2); + } catch (error) { + console.error('Error generating tripArguments.json:', error); + return JSON.stringify({ error: 'Failed to generate trip arguments JSON' }, null, 2); + } +}; + +module.exports = { + plugin: generateTripArgsJsonPlugin, +}; diff --git a/client/src/util/generate-queries.cjs b/client/src/util/generate-queries.cjs new file mode 100644 index 00000000000..ee666211106 --- /dev/null +++ b/client/src/util/generate-queries.cjs @@ -0,0 +1,67 @@ +const { getNullableType, isObjectType } = require('graphql'); +const fs = require('fs'); +const path = require('path'); + +/** + * Plugin to generate GraphQL queries dynamically from schema + */ +const generateQueriesPlugin = async (schema, documents, config) => { + const queryType = schema.getQueryType(); + if (!queryType) { + return '// No Query type found in the schema'; + } + + // Read the content from the input file to replace "replacementContent" + const inputFilePath = path.join(__dirname, '../static/query/selector.fragment.graphql'); + let replacementContent = ''; + + try { + replacementContent = fs.readFileSync(inputFilePath, 'utf-8').trim(); + } catch (error) { + console.error(`Failed to read the input file at ${inputFilePath}`, error); + return '// Error: Failed to read the input file'; + } + + const queryFields = queryType.getFields(); + const queries = []; + + Object.keys(queryFields).forEach((fieldName) => { + if (fieldName === 'trip') { // Only interested in the trip query + const field = queryFields[fieldName]; + + // Filter out deprecated arguments using deprecationReason - isDeprecated does not work + const validArgs = field.args.filter(arg => !arg.deprecationReason); + + // Generate the arguments for the query with filtered arguments + const args = validArgs.map((arg) => ` $${arg.name}: ${arg.type}`).join('\n'); + + // Generate the arguments for the query variables with filtered arguments + const argsForQuery = validArgs.map((arg) => ` ${arg.name}: $${arg.name}`).join('\n'); + + const query = `import { graphql } from '../../gql'; +import { print } from 'graphql/index'; + +// Generated trip query based on schema.graphql + +export const query = graphql(\` +query ${fieldName}( +${args} +) { + ${fieldName}( +${argsForQuery} + ) + ${replacementContent} + } +}\`); + +export const queryAsString = print(query);`; + queries.push(query.trim()); // Trim unnecessary whitespace + } + }); + + return queries.join('\n\n'); // Separate queries with a blank line +}; + +module.exports = { + plugin: generateQueriesPlugin, +}; From 3e74e7e53dd66d0b856ab816eeab850df5104fed Mon Sep 17 00:00:00 2001 From: a-limyr Date: Mon, 16 Dec 2024 10:17:13 +0100 Subject: [PATCH 046/195] Forgotten fil --- client/src/static/query/tripQuery.tsx | 155 ++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 client/src/static/query/tripQuery.tsx diff --git a/client/src/static/query/tripQuery.tsx b/client/src/static/query/tripQuery.tsx new file mode 100644 index 00000000000..14c5ed2ec26 --- /dev/null +++ b/client/src/static/query/tripQuery.tsx @@ -0,0 +1,155 @@ +import { graphql } from '../../gql'; +import { print } from 'graphql/index'; + +// Generated trip query based on schema.graphql + +export const query = graphql(` +query trip( + $accessEgressPenalty: [PenaltyForStreetMode!] + $alightSlackDefault: Int + $alightSlackList: [TransportModeSlack] + $arriveBy: Boolean + $banned: InputBanned + $bicycleOptimisationMethod: BicycleOptimisationMethod + $bikeSpeed: Float + $boardSlackDefault: Int + $boardSlackList: [TransportModeSlack] + $bookingTime: DateTime + $dateTime: DateTime + $filters: [TripFilterInput!] + $from: Location! + $ignoreRealtimeUpdates: Boolean + $includePlannedCancellations: Boolean + $includeRealtimeCancellations: Boolean + $itineraryFilters: ItineraryFilters + $locale: Locale + $maxAccessEgressDurationForMode: [StreetModeDurationInput!] + $maxDirectDurationForMode: [StreetModeDurationInput!] + $maximumAdditionalTransfers: Int + $maximumTransfers: Int + $modes: Modes + $numTripPatterns: Int + $pageCursor: String + $relaxTransitGroupPriority: RelaxCostInput + $searchWindow: Int + $timetableView: Boolean + $to: Location! + $transferPenalty: Int + $transferSlack: Int + $triangleFactors: TriangleFactors + $useBikeRentalAvailabilityInformation: Boolean + $via: [TripViaLocationInput!] + $waitReluctance: Float + $walkReluctance: Float + $walkSpeed: Float + $wheelchairAccessible: Boolean + $whiteListed: InputWhiteListed +) { + trip( + accessEgressPenalty: $accessEgressPenalty + alightSlackDefault: $alightSlackDefault + alightSlackList: $alightSlackList + arriveBy: $arriveBy + banned: $banned + bicycleOptimisationMethod: $bicycleOptimisationMethod + bikeSpeed: $bikeSpeed + boardSlackDefault: $boardSlackDefault + boardSlackList: $boardSlackList + bookingTime: $bookingTime + dateTime: $dateTime + filters: $filters + from: $from + ignoreRealtimeUpdates: $ignoreRealtimeUpdates + includePlannedCancellations: $includePlannedCancellations + includeRealtimeCancellations: $includeRealtimeCancellations + itineraryFilters: $itineraryFilters + locale: $locale + maxAccessEgressDurationForMode: $maxAccessEgressDurationForMode + maxDirectDurationForMode: $maxDirectDurationForMode + maximumAdditionalTransfers: $maximumAdditionalTransfers + maximumTransfers: $maximumTransfers + modes: $modes + numTripPatterns: $numTripPatterns + pageCursor: $pageCursor + relaxTransitGroupPriority: $relaxTransitGroupPriority + searchWindow: $searchWindow + timetableView: $timetableView + to: $to + transferPenalty: $transferPenalty + transferSlack: $transferSlack + triangleFactors: $triangleFactors + useBikeRentalAvailabilityInformation: $useBikeRentalAvailabilityInformation + via: $via + waitReluctance: $waitReluctance + walkReluctance: $walkReluctance + walkSpeed: $walkSpeed + wheelchairAccessible: $wheelchairAccessible + whiteListed: $whiteListed + ) + { + previousPageCursor + nextPageCursor + tripPatterns { + aimedStartTime + aimedEndTime + expectedEndTime + expectedStartTime + duration + distance + legs { + id + mode + aimedStartTime + aimedEndTime + expectedEndTime + expectedStartTime + realtime + distance + duration + fromPlace { + name + quay { + id + } + } + toPlace { + name + quay { + id + } + } + toEstimatedCall { + destinationDisplay { + frontText + } + } + line { + publicCode + name + id + presentation { + colour + } + } + authority { + name + id + } + pointsOnLink { + points + } + interchangeTo { + staySeated + } + interchangeFrom { + staySeated + } + } + systemNotices { + tag + } + } + } +}`); + +export const queryAsString = print(query); \ No newline at end of file From 2e048f9e8a3032f4acfe396b97820a487fe657f2 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Mon, 16 Dec 2024 15:47:14 +0200 Subject: [PATCH 047/195] Refactor transfer parameter parsing. --- .../module/DirectTransferGenerator.java | 116 ++++++++++-------- 1 file changed, 67 insertions(+), 49 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index 88b9cc5b19b..90cab4019e3 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -140,55 +140,16 @@ public void buildGraph() { List carsAllowedStopTransferRequests = new ArrayList<>(); List flexTransferRequests = new ArrayList<>(); - // Parse transfer parameters. - for (RouteRequest transferProfile : transferRequests) { - StreetMode mode = transferProfile.journey().transfer().mode(); - TransferParameters transferParameters = transferParametersForMode.get(mode); - if (transferParameters != null) { - // Disable normal transfer calculations for the specific mode, if disableDefaultTransfers is set in the build config. - if (!transferParameters.disableDefaultTransfers()) { - defaultTransferRequests.add(transferProfile); - // Set mode-specific maxTransferDuration, if it is set in the build config. - Duration maxTransferDuration = transferParameters.maxTransferDuration(); - if (maxTransferDuration != Duration.ZERO) { - defaultNearbyStopFinders.put( - mode, - createNearbyStopFinder(maxTransferDuration, Set.of()) - ); - } else { - defaultNearbyStopFinders.put(mode, nearbyStopFinder); - } - } - // Create transfers between carsAllowedStops for the specific mode if carsAllowedStopMaxTransferDuration is set in the build config. - Duration carsAllowedStopMaxTransferDuration = transferParameters.carsAllowedStopMaxTransferDuration(); - if (carsAllowedStopMaxTransferDuration != Duration.ZERO) { - carsAllowedStopTransferRequests.add(transferProfile); - carsAllowedStopNearbyStopFinders.put( - mode, - createNearbyStopFinder( - carsAllowedStopMaxTransferDuration, - Collections.unmodifiableSet(carsAllowedStops) - ) - ); - } - } else { - defaultTransferRequests.add(transferProfile); - defaultNearbyStopFinders.put(mode, nearbyStopFinder); - } - } - - // Flex transfer requests only use the WALK mode. - if (OTPFeature.FlexRouting.isOn()) { - flexTransferRequests.addAll( - transferRequests - .stream() - .filter(transferProfile -> transferProfile.journey().transfer().mode() == StreetMode.WALK) - .toList() - ); - if (!defaultNearbyStopFinders.containsKey(StreetMode.WALK)) { - defaultNearbyStopFinders.put(StreetMode.WALK, nearbyStopFinder); - } - } + // Parse the transfer configuration from the parameters given in the build config. + parseTransferParameters( + defaultTransferRequests, + carsAllowedStopTransferRequests, + flexTransferRequests, + defaultNearbyStopFinders, + carsAllowedStopNearbyStopFinders, + nearbyStopFinder, + carsAllowedStops + ); stops .stream() @@ -332,6 +293,63 @@ private NearbyStopFinder createNearbyStopFinder( } } + private void parseTransferParameters( + List defaultTransferRequests, + List carsAllowedStopTransferRequests, + List flexTransferRequests, + HashMap defaultNearbyStopFinders, + HashMap carsAllowedStopNearbyStopFinders, + NearbyStopFinder nearbyStopFinder, + Set carsAllowedStops + ) { + for (RouteRequest transferProfile : transferRequests) { + StreetMode mode = transferProfile.journey().transfer().mode(); + TransferParameters transferParameters = transferParametersForMode.get(mode); + if (transferParameters != null) { + // Disable normal transfer calculations for the specific mode, if disableDefaultTransfers is set in the build config. + // WALK mode transfers can not be disabled. For example, flex transfers need them. + if (!transferParameters.disableDefaultTransfers() || mode == StreetMode.WALK) { + defaultTransferRequests.add(transferProfile); + // Set mode-specific maxTransferDuration, if it is set in the build config. + Duration maxTransferDuration = transferParameters.maxTransferDuration(); + if (maxTransferDuration != Duration.ZERO) { + defaultNearbyStopFinders.put( + mode, + createNearbyStopFinder(maxTransferDuration, Set.of()) + ); + } else { + defaultNearbyStopFinders.put(mode, nearbyStopFinder); + } + } + // Create transfers between carsAllowedStops for the specific mode if carsAllowedStopMaxTransferDuration is set in the build config. + Duration carsAllowedStopMaxTransferDuration = transferParameters.carsAllowedStopMaxTransferDuration(); + if (carsAllowedStopMaxTransferDuration != Duration.ZERO) { + carsAllowedStopTransferRequests.add(transferProfile); + carsAllowedStopNearbyStopFinders.put( + mode, + createNearbyStopFinder( + carsAllowedStopMaxTransferDuration, + Collections.unmodifiableSet(carsAllowedStops) + ) + ); + } + } else { + defaultTransferRequests.add(transferProfile); + defaultNearbyStopFinders.put(mode, nearbyStopFinder); + } + } + + // Flex transfer requests only use the WALK mode. + if (OTPFeature.FlexRouting.isOn()) { + flexTransferRequests.addAll( + transferRequests + .stream() + .filter(transferProfile -> transferProfile.journey().transfer().mode() == StreetMode.WALK) + .toList() + ); + } + } + private void findNearbyStops( NearbyStopFinder nearbyStopFinder, TransitStopVertex ts0, From c90c3ecb8acdaa76792571fdd38f1c26ee6126f4 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Mon, 16 Dec 2024 15:52:07 +0200 Subject: [PATCH 048/195] Remove duplicate test. --- .../module/DirectTransferGeneratorTest.java | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java index fae47da7b0a..7d75aecf89c 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java @@ -238,39 +238,6 @@ public void testMultipleRequestsWithPatterns() { graph.hasStreets = true; var timetableRepository = model.timetableRepository(); - new DirectTransferGenerator( - graph, - timetableRepository, - DataImportIssueStore.NOOP, - MAX_TRANSFER_DURATION, - transferRequests - ) - .buildGraph(); - - assertTransfers( - timetableRepository.getAllPathTransfers(), - tr(S0, 100, List.of(V0, V11), S11), - tr(S0, 100, List.of(V0, V21), S21), - tr(S11, 100, List.of(V11, V21), S21), - tr(S11, 110, List.of(V11, V22), S22) - ); - } - - @Test - public void testPathTransfersWithModesForMultipleRequestsWithPatterns() { - var reqWalk = new RouteRequest(); - reqWalk.journey().transfer().setMode(StreetMode.WALK); - - var reqBike = new RouteRequest(); - reqBike.journey().transfer().setMode(StreetMode.BIKE); - - var transferRequests = List.of(reqWalk, reqBike); - - TestOtpModel model = model(true); - var graph = model.graph(); - graph.hasStreets = true; - var timetableRepository = model.timetableRepository(); - new DirectTransferGenerator( graph, timetableRepository, From 30cc9251a31bb1a7ee88e15a67b9f244c0d70b47 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 17 Dec 2024 09:47:50 +0200 Subject: [PATCH 049/195] Simplify implementation by removing changes to StreetNearbyStopFinder. --- .../module/DirectTransferGenerator.java | 141 ++++++++---------- .../nearbystops/StreetNearbyStopFinder.java | 28 +--- .../StreetNearbyStopFinderTest.java | 72 --------- 3 files changed, 61 insertions(+), 180 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index 90cab4019e3..6b7c53efc15 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -93,23 +93,13 @@ public void buildGraph() { timetableRepository.index(); /* The linker will use streets if they are available, or straight-line distance otherwise. */ - NearbyStopFinder nearbyStopFinder = createNearbyStopFinder( - defaultMaxTransferDuration, - Set.of() - ); + NearbyStopFinder nearbyStopFinder = createNearbyStopFinder(defaultMaxTransferDuration); HashMap defaultNearbyStopFinders = new HashMap<>(); /* These are used for calculating transfers only between carsAllowedStops. */ HashMap carsAllowedStopNearbyStopFinders = new HashMap<>(); List stops = graph.getVerticesOfType(TransitStopVertex.class); - Set carsAllowedStops = timetableRepository - .getStopLocationsUsedForCarsAllowedTrips() - .stream() - .map(StopLocation::getId) - .map(graph::getStopVertexForStopId) - // filter out null values if no TransitStopVertex is found for ID - .filter(TransitStopVertex.class::isInstance) - .collect(Collectors.toSet()); + Set carsAllowedStops = timetableRepository.getStopLocationsUsedForCarsAllowedTrips(); LOG.info("Creating transfers based on requests:"); transferRequests.forEach(transferProfile -> LOG.info(transferProfile.toString())); @@ -147,8 +137,7 @@ public void buildGraph() { flexTransferRequests, defaultNearbyStopFinders, carsAllowedStopNearbyStopFinders, - nearbyStopFinder, - carsAllowedStops + nearbyStopFinder ); stops @@ -169,14 +158,29 @@ public void buildGraph() { // Calculate default transfers. for (RouteRequest transferProfile : defaultTransferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); - findNearbyStops( - defaultNearbyStopFinders.get(mode), - ts0, - transferProfile, - stop, - distinctTransfers, - mode - ); + for (NearbyStop sd : defaultNearbyStopFinders + .get(mode) + .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false)) { + // Skip the origin stop, loop transfers are not needed. + if (sd.stop == stop) { + continue; + } + if (sd.stop.transfersNotAllowed()) { + continue; + } + TransferKey transferKey = new TransferKey(stop, sd.stop, sd.edges); + PathTransfer pathTransfer = distinctTransfers.get(transferKey); + if (pathTransfer == null) { + // If the PathTransfer can't be found, it is created. + distinctTransfers.put( + transferKey, + new PathTransfer(stop, sd.stop, sd.distance, sd.edges, EnumSet.of(mode)) + ); + } else { + // If the PathTransfer is found, a new PathTransfer with the added mode is created. + distinctTransfers.put(transferKey, pathTransfer.withAddedMode(mode)); + } + } } // Calculate flex transfers if flex routing is enabled. for (RouteRequest transferProfile : flexTransferRequests) { @@ -210,17 +214,36 @@ public void buildGraph() { } } // Calculate transfers between stops that can use trips with cars if configured. - if (carsAllowedStops.contains(ts0)) { + if (carsAllowedStops.contains(stop)) { for (RouteRequest transferProfile : carsAllowedStopTransferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); - findNearbyStops( - carsAllowedStopNearbyStopFinders.get(mode), - ts0, - transferProfile, - stop, - distinctTransfers, - mode - ); + for (NearbyStop sd : carsAllowedStopNearbyStopFinders + .get(mode) + .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false)) { + // Skip the origin stop, loop transfers are not needed. + if (sd.stop == stop) { + continue; + } + if (sd.stop.transfersNotAllowed()) { + continue; + } + // Only calculate transfers between carsAllowedStops. + if (!carsAllowedStops.contains(sd.stop)) { + continue; + } + TransferKey transferKey = new TransferKey(stop, sd.stop, sd.edges); + PathTransfer pathTransfer = distinctTransfers.get(transferKey); + if (pathTransfer == null) { + // If the PathTransfer can't be found, it is created. + distinctTransfers.put( + transferKey, + new PathTransfer(stop, sd.stop, sd.distance, sd.edges, EnumSet.of(mode)) + ); + } else { + // If the PathTransfer is found, a new PathTransfer with the added mode is created. + distinctTransfers.put(transferKey, pathTransfer.withAddedMode(mode)); + } + } } } @@ -270,10 +293,7 @@ public void buildGraph() { * whether the graph has a street network and if ConsiderPatternsForDirectTransfers feature is * enabled. */ - private NearbyStopFinder createNearbyStopFinder( - Duration radiusByDuration, - Set findOnlyVertices - ) { + private NearbyStopFinder createNearbyStopFinder(Duration radiusByDuration) { var transitService = new DefaultTransitService(timetableRepository); NearbyStopFinder finder; if (!graph.hasStreets) { @@ -283,7 +303,7 @@ private NearbyStopFinder createNearbyStopFinder( finder = new StraightLineNearbyStopFinder(transitService, radiusByDuration); } else { LOG.info("Creating direct transfer edges between stops using the street network from OSM..."); - finder = new StreetNearbyStopFinder(radiusByDuration, 0, null, Set.of(), findOnlyVertices); + finder = new StreetNearbyStopFinder(radiusByDuration, 0, null, Set.of()); } if (OTPFeature.ConsiderPatternsForDirectTransfers.isOn()) { @@ -299,8 +319,7 @@ private void parseTransferParameters( List flexTransferRequests, HashMap defaultNearbyStopFinders, HashMap carsAllowedStopNearbyStopFinders, - NearbyStopFinder nearbyStopFinder, - Set carsAllowedStops + NearbyStopFinder nearbyStopFinder ) { for (RouteRequest transferProfile : transferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); @@ -313,10 +332,7 @@ private void parseTransferParameters( // Set mode-specific maxTransferDuration, if it is set in the build config. Duration maxTransferDuration = transferParameters.maxTransferDuration(); if (maxTransferDuration != Duration.ZERO) { - defaultNearbyStopFinders.put( - mode, - createNearbyStopFinder(maxTransferDuration, Set.of()) - ); + defaultNearbyStopFinders.put(mode, createNearbyStopFinder(maxTransferDuration)); } else { defaultNearbyStopFinders.put(mode, nearbyStopFinder); } @@ -327,10 +343,7 @@ private void parseTransferParameters( carsAllowedStopTransferRequests.add(transferProfile); carsAllowedStopNearbyStopFinders.put( mode, - createNearbyStopFinder( - carsAllowedStopMaxTransferDuration, - Collections.unmodifiableSet(carsAllowedStops) - ) + createNearbyStopFinder(carsAllowedStopMaxTransferDuration) ); } } else { @@ -350,41 +363,5 @@ private void parseTransferParameters( } } - private void findNearbyStops( - NearbyStopFinder nearbyStopFinder, - TransitStopVertex ts0, - RouteRequest transferProfile, - RegularStop stop, - Map distinctTransfers, - StreetMode mode - ) { - for (NearbyStop sd : nearbyStopFinder.findNearbyStops( - ts0, - transferProfile, - transferProfile.journey().transfer(), - false - )) { - // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { - continue; - } - if (sd.stop.transfersNotAllowed()) { - continue; - } - TransferKey transferKey = new TransferKey(stop, sd.stop, sd.edges); - PathTransfer pathTransfer = distinctTransfers.get(transferKey); - if (pathTransfer == null) { - // If the PathTransfer can't be found, it is created. - distinctTransfers.put( - transferKey, - new PathTransfer(stop, sd.stop, sd.distance, sd.edges, EnumSet.of(mode)) - ); - } else { - // If the PathTransfer is found, a new PathTransfer with the added mode is created. - distinctTransfers.put(transferKey, pathTransfer.withAddedMode(mode)); - } - } - } - private record TransferKey(StopLocation source, StopLocation target, List edges) {} } diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java index 327315a0be9..8277bd47e4c 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java @@ -38,7 +38,6 @@ public class StreetNearbyStopFinder implements NearbyStopFinder { private final int maxStopCount; private final DataOverlayContext dataOverlayContext; private final Set ignoreVertices; - private final Set findOnlyVertices; /** * Construct a NearbyStopFinder for the given graph and search radius. @@ -51,7 +50,7 @@ public StreetNearbyStopFinder( int maxStopCount, DataOverlayContext dataOverlayContext ) { - this(durationLimit, maxStopCount, dataOverlayContext, Set.of(), Set.of()); + this(durationLimit, maxStopCount, dataOverlayContext, Set.of()); } /** @@ -66,31 +65,11 @@ public StreetNearbyStopFinder( int maxStopCount, DataOverlayContext dataOverlayContext, Set ignoreVertices - ) { - this(durationLimit, maxStopCount, dataOverlayContext, ignoreVertices, Set.of()); - } - - /** - * Construct a NearbyStopFinder for the given graph and search radius. - * - * @param maxStopCount The maximum stops to return. 0 means no limit. Regardless of the maxStopCount - * we will always return all the directly connected stops. - * @param ignoreVertices A set of stop vertices to ignore and not return NearbyStops for. - * - * @param findOnlyVertices Only return NearbyStops that are in this set. If this is empty, no filtering is performed. - */ - public StreetNearbyStopFinder( - Duration durationLimit, - int maxStopCount, - DataOverlayContext dataOverlayContext, - Set ignoreVertices, - Set findOnlyVertices ) { this.dataOverlayContext = dataOverlayContext; this.durationLimit = durationLimit; this.maxStopCount = maxStopCount; this.ignoreVertices = ignoreVertices; - this.findOnlyVertices = findOnlyVertices; } /** @@ -170,10 +149,7 @@ public Collection findNearbyStops( continue; } if (targetVertex instanceof TransitStopVertex tsv && state.isFinal()) { - // If a set of findOnlyVertices is provided, only add stops that are in the set. - if (findOnlyVertices.isEmpty() || findOnlyVertices.contains(targetVertex)) { - stopsFound.add(NearbyStop.nearbyStopForState(state, tsv.getStop())); - } + stopsFound.add(NearbyStop.nearbyStopForState(state, tsv.getStop())); } if ( OTPFeature.FlexRouting.isOn() && diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java index d970569d091..aae02451b33 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinderTest.java @@ -163,78 +163,6 @@ void testIgnoreStopsWithMaxStops() { assertStopAtDistance(stopC, 200, sortedNearbyStops.get(0)); } - @Test - void testFindOnlyVerticesStops() { - var durationLimit = Duration.ofMinutes(10); - var maxStopCount = 0; - Set findOnlyStops = Set.of(stopB, stopC); - var finder = new StreetNearbyStopFinder( - durationLimit, - maxStopCount, - null, - Set.of(), - findOnlyStops - ); - - var sortedNearbyStops = sort( - finder.findNearbyStops(stopA, new RouteRequest(), new StreetRequest(), false) - ); - - assertThat(sortedNearbyStops).hasSize(3); - assertZeroDistanceStop(stopA, sortedNearbyStops.get(0)); - assertStopAtDistance(stopB, 100, sortedNearbyStops.get(1)); - assertStopAtDistance(stopC, 200, sortedNearbyStops.get(2)); - } - - @Test - void testFindOnlyVerticesStopsWithIgnore() { - var durationLimit = Duration.ofMinutes(10); - var maxStopCount = 0; - Set findOnlyStops = Set.of(stopB, stopC); - Set ignore = Set.of(stopB); - var finder = new StreetNearbyStopFinder( - durationLimit, - maxStopCount, - null, - ignore, - findOnlyStops - ); - - var sortedNearbyStops = sort( - finder.findNearbyStops(stopA, new RouteRequest(), new StreetRequest(), false) - ); - - assertThat(sortedNearbyStops).hasSize(2); - assertZeroDistanceStop(stopA, sortedNearbyStops.get(0)); - assertStopAtDistance(stopC, 200, sortedNearbyStops.get(1)); - } - - @Test - void testFindOnlyVerticesStopsWithDurationLimit() { - // If we only allow walk for 101 seconds and speed is 1 m/s we should only be able to reach - // one extra stop. - var durationLimit = Duration.ofSeconds(101); - var maxStopCount = 0; - Set findOnlyStops = Set.of(stopB, stopC); - var routeRequest = new RouteRequest() - .withPreferences(b -> b.withWalk(walkPreferences -> walkPreferences.withSpeed(1.0))); - - var finder = new StreetNearbyStopFinder( - durationLimit, - maxStopCount, - null, - Set.of(), - findOnlyStops - ); - var sortedNearbyStops = sort( - finder.findNearbyStops(stopA, routeRequest, new StreetRequest(), false) - ); - - assertThat(sortedNearbyStops).hasSize(2); - assertZeroDistanceStop(stopA, sortedNearbyStops.get(0)); - assertStopAtDistance(stopB, 100, sortedNearbyStops.get(1)); - } - static List sort(Collection stops) { return stops.stream().sorted(Comparator.comparing(x -> x.distance)).toList(); } From 2e57c2f72b8c69af02af67d7637eaa92e11c333e Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 17 Dec 2024 09:59:00 +0200 Subject: [PATCH 050/195] Remove unnecessary parameter from function call. --- .../graph_builder/module/DirectTransferGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index 6b7c53efc15..d80144636a4 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -303,7 +303,7 @@ private NearbyStopFinder createNearbyStopFinder(Duration radiusByDuration) { finder = new StraightLineNearbyStopFinder(transitService, radiusByDuration); } else { LOG.info("Creating direct transfer edges between stops using the street network from OSM..."); - finder = new StreetNearbyStopFinder(radiusByDuration, 0, null, Set.of()); + finder = new StreetNearbyStopFinder(radiusByDuration, 0, null); } if (OTPFeature.ConsiderPatternsForDirectTransfers.isOn()) { From 04d35b7c34c5ab646ee044c7bd80caa50b99355b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Dec 2024 00:08:13 +0100 Subject: [PATCH 051/195] Clean up code a little --- .../apis/gtfs/GtfsGraphQLIndex.java | 2 +- .../mapping/StatesToWalkStepsMapper.java | 31 +++++++------------ .../model/vertex/StationEntranceVertex.java | 26 +++++++++++----- .../model/site/StationElementBuilder.java | 3 +- .../opentripplanner/apis/gtfs/schema.graphqls | 1 - .../apis/gtfs/expectations/walk-steps.json | 17 +++++----- 6 files changed, 42 insertions(+), 38 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index cd72633a886..d3f64288417 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -39,8 +39,8 @@ import org.opentripplanner.apis.gtfs.datafetchers.CurrencyImpl; import org.opentripplanner.apis.gtfs.datafetchers.DefaultFareProductImpl; import org.opentripplanner.apis.gtfs.datafetchers.DepartureRowImpl; -import org.opentripplanner.apis.gtfs.datafetchers.EstimatedTimeImpl; import org.opentripplanner.apis.gtfs.datafetchers.EntranceImpl; +import org.opentripplanner.apis.gtfs.datafetchers.EstimatedTimeImpl; import org.opentripplanner.apis.gtfs.datafetchers.FareProductTypeResolver; import org.opentripplanner.apis.gtfs.datafetchers.FareProductUseImpl; import org.opentripplanner.apis.gtfs.datafetchers.FeedImpl; 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 956ec6d5701..97310a47453 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 @@ -30,7 +30,6 @@ import org.opentripplanner.street.search.TraverseMode; import org.opentripplanner.street.search.state.State; import org.opentripplanner.transit.model.basic.Accessibility; -import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.Entrance; /** @@ -179,8 +178,8 @@ private void processState(State backState, State forwardState) { if (edge instanceof ElevatorAlightEdge) { addStep(createElevatorWalkStep(backState, forwardState, edge)); return; - } else if (backState.getVertex() instanceof StationEntranceVertex) { - addStep(createStationEntranceWalkStep(backState, forwardState, edge)); + } 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); @@ -525,29 +524,23 @@ private WalkStepBuilder createElevatorWalkStep(State backState, State forwardSta private WalkStepBuilder createStationEntranceWalkStep( State backState, State forwardState, - Edge edge + StationEntranceVertex vertex ) { - // don't care what came before or comes after - var step = 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. - step.withRelativeDirection(RelativeDirection.ENTER_OR_EXIT_STATION); - - StationEntranceVertex vertex = (StationEntranceVertex) backState.getVertex(); - - FeedScopedId entranceId = new FeedScopedId("osm", vertex.getId()); - Entrance entrance = Entrance - .of(entranceId) - .withCode(vertex.getCode()) + .of(vertex.id()) + .withCode(vertex.code()) .withCoordinate(new WgsCoordinate(vertex.getCoordinate())) .withWheelchairAccessibility( - vertex.isAccessible() ? Accessibility.POSSIBLE : Accessibility.NOT_POSSIBLE + vertex.wheelchairAccessibility() ) .build(); - step.withEntrance(entrance); - return step; + // 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( 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 index e55ac7078db..6dd528204b2 100644 --- a/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java +++ b/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java @@ -1,5 +1,10 @@ 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; + public class StationEntranceVertex extends OsmVertex { private final String code; @@ -11,19 +16,26 @@ public StationEntranceVertex(double x, double y, long nodeId, String code, boole this.accessible = accessible; } - public String getCode() { - return code; + public FeedScopedId id() { + return new FeedScopedId("osm", String.valueOf(nodeId)); } - public boolean isAccessible() { - return accessible; + @Nullable + public String code() { + return code; } - public String getId() { - return Long.toString(nodeId); + public Accessibility wheelchairAccessibility() { + return accessible ? Accessibility.POSSIBLE : Accessibility.NOT_POSSIBLE; } + @Override public String toString() { - return "StationEntranceVertex(" + super.toString() + ", code=" + code + ")"; + return ToStringBuilder + .of(StationEntranceVertex.class) + .addNum("nodeId", nodeId) + .addStr("code", code) + .toString(); } + } 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 1c4951b782c..3eac957f7bb 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3542,7 +3542,6 @@ enum RelativeDirection { CONTINUE DEPART ELEVATOR - ENTER_OR_EXIT_STATION ENTER_STATION EXIT_STATION FOLLOW_SIGNS 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 19f7f5cc758..be952f72303 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,22 +11,21 @@ "streetName": "street", "area": false, "relativeDirection": "DEPART", - "absoluteDirection" : "NORTHEAST", - "feature" : null + "absoluteDirection": "NORTHEAST", + "feature": null }, { "streetName": "elevator", "area": false, "relativeDirection": "ELEVATOR", - "absoluteDirection" : null, - "feature" : null - + "absoluteDirection": null, + "feature": null }, { - "streetName" : "entrance", - "area" : false, - "relativeDirection" : "ENTER_OR_EXIT_STATION", - "absoluteDirection" : null, + "streetName": "entrance", + "area": false, + "relativeDirection": "ENTER_OR_EXIT_STATION", + "absoluteDirection": null, "feature": { "__typename": "Entrance", "code": "A", From c4d665d21bb995678bc3e3b56a526b1cd3ce0c5f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Dec 2024 00:20:12 +0100 Subject: [PATCH 052/195] Reformat code and schema --- .../gtfs/generated/GraphQLDataFetchers.java | 15 +++++----- .../apis/gtfs/generated/GraphQLTypes.java | 1 - .../apis/gtfs/mapping/DirectionMapper.java | 3 +- .../mapping/StatesToWalkStepsMapper.java | 4 +-- .../model/vertex/StationEntranceVertex.java | 1 - .../opentripplanner/apis/gtfs/schema.graphqls | 28 +++++++++---------- 6 files changed, 24 insertions(+), 28 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 760e890045b..d9c9ceb67e8 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -392,13 +392,6 @@ public interface GraphQLEmissions { public DataFetcher co2(); } - /** Real-time estimates for an arrival or departure at a certain place. */ - public interface GraphQLEstimatedTime { - public DataFetcher delay(); - - public DataFetcher time(); - } - /** Station entrance or exit, originating from OSM or GTFS data. */ public interface GraphQLEntrance { public DataFetcher code(); @@ -410,6 +403,13 @@ public interface GraphQLEntrance { public DataFetcher wheelchairAccessible(); } + /** Real-time estimates for an arrival or departure at a certain place. */ + public interface GraphQLEstimatedTime { + public DataFetcher delay(); + + public DataFetcher time(); + } + /** A 'medium' that a fare product applies to, for example cash, 'Oyster Card' or 'DB Navigator App'. */ public interface GraphQLFareMedium { public DataFetcher id(); @@ -1035,6 +1035,7 @@ public interface GraphQLRoutingError { public DataFetcher inputField(); } + /** A feature for a step */ public interface GraphQLStepFeature extends TypeResolver {} /** diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index fe598f88e40..a969b5223b1 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -4334,7 +4334,6 @@ public enum GraphQLRelativeDirection { CONTINUE, DEPART, ELEVATOR, - ENTER_OR_EXIT_STATION, ENTER_STATION, EXIT_STATION, FOLLOW_SIGNS, diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapper.java index 1439cdd34c3..3f69047f94d 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectionMapper.java @@ -27,7 +27,7 @@ public static GraphQLRelativeDirection map(RelativeDirection relativeDirection) case HARD_LEFT -> GraphQLRelativeDirection.HARD_LEFT; case LEFT -> GraphQLRelativeDirection.LEFT; case SLIGHTLY_LEFT -> GraphQLRelativeDirection.SLIGHTLY_LEFT; - case CONTINUE -> GraphQLRelativeDirection.CONTINUE; + case CONTINUE, ENTER_OR_EXIT_STATION -> GraphQLRelativeDirection.CONTINUE; case SLIGHTLY_RIGHT -> GraphQLRelativeDirection.SLIGHTLY_RIGHT; case RIGHT -> GraphQLRelativeDirection.RIGHT; case HARD_RIGHT -> GraphQLRelativeDirection.HARD_RIGHT; @@ -38,7 +38,6 @@ public static GraphQLRelativeDirection map(RelativeDirection relativeDirection) case UTURN_RIGHT -> GraphQLRelativeDirection.UTURN_RIGHT; case ENTER_STATION -> GraphQLRelativeDirection.ENTER_STATION; case EXIT_STATION -> GraphQLRelativeDirection.EXIT_STATION; - case ENTER_OR_EXIT_STATION -> GraphQLRelativeDirection.ENTER_OR_EXIT_STATION; case FOLLOW_SIGNS -> GraphQLRelativeDirection.FOLLOW_SIGNS; }; } 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 97310a47453..9365c50509c 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 @@ -530,9 +530,7 @@ private WalkStepBuilder createStationEntranceWalkStep( .of(vertex.id()) .withCode(vertex.code()) .withCoordinate(new WgsCoordinate(vertex.getCoordinate())) - .withWheelchairAccessibility( - vertex.wheelchairAccessibility() - ) + .withWheelchairAccessibility(vertex.wheelchairAccessibility()) .build(); // don't care what came before or comes after 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 index 6dd528204b2..ef94bbb64ef 100644 --- a/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java +++ b/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java @@ -37,5 +37,4 @@ public String toString() { .addStr("code", code) .toString(); } - } 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 3eac957f7bb..748b69607e0 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -491,18 +491,6 @@ type Emissions { co2: Grams } -"Real-time estimates for an arrival or departure at a certain place." -type EstimatedTime { - """ - The delay or "earliness" of the vehicle at a certain place. This estimate can change quite often. - - If the vehicle is early then this is a negative duration. - """ - delay: Duration! - "The estimate for a call event (such as arrival or departure) at a certain place. This estimate can change quite often." - time: OffsetDateTime! -} - "Station entrance or exit, originating from OSM or GTFS data." type Entrance { "Short text or a number that identifies the entrance or exit for passengers. For example, `A` or `B`." @@ -515,6 +503,18 @@ type Entrance { wheelchairAccessible: WheelchairBoarding } +"Real-time estimates for an arrival or departure at a certain place." +type EstimatedTime { + """ + The delay or "earliness" of the vehicle at a certain place. This estimate can change quite often. + + If the vehicle is early then this is a negative duration. + """ + delay: Duration! + "The estimate for a call event (such as arrival or departure) at a certain place. This estimate can change quite often." + time: OffsetDateTime! +} + "A 'medium' that a fare product applies to, for example cash, 'Oyster Card' or 'DB Navigator App'." type FareMedium { "ID of the medium" @@ -2359,7 +2359,7 @@ type Stoptime { """ The position of the stop in the pattern. This is required to start from 0 and be consecutive along the pattern, up to n-1 for a pattern with n stops. - + The purpose of this field is to identify the position of the stop within the pattern so it can be cross-referenced between different trips on the same pattern, as stopPosition can be different between trips even within the same pattern. @@ -2518,7 +2518,7 @@ type TripOnServiceDate { end: StopCall! """ The service date when the trip occurs. - + **Note**: A service date is a technical term useful for transit planning purposes and might not correspond to a how a passenger thinks of a calendar date. For example, a night bus running on Sunday morning at 1am to 3am, might have the previous Saturday's service date. From bf89f4969fd39cc2b1265f35b3c79836ac6ca496 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Dec 2024 00:25:08 +0100 Subject: [PATCH 053/195] Fix tests --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 6 +++--- .../apis/gtfs/mapping/DirectionMapperTest.java | 1 + .../opentripplanner/apis/gtfs/expectations/walk-steps.json | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) 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 748b69607e0..6ac56be3d1d 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -507,7 +507,7 @@ type Entrance { type EstimatedTime { """ The delay or "earliness" of the vehicle at a certain place. This estimate can change quite often. - + If the vehicle is early then this is a negative duration. """ delay: Duration! @@ -2359,7 +2359,7 @@ type Stoptime { """ The position of the stop in the pattern. This is required to start from 0 and be consecutive along the pattern, up to n-1 for a pattern with n stops. - + The purpose of this field is to identify the position of the stop within the pattern so it can be cross-referenced between different trips on the same pattern, as stopPosition can be different between trips even within the same pattern. @@ -2518,7 +2518,7 @@ type TripOnServiceDate { end: StopCall! """ The service date when the trip occurs. - + **Note**: A service date is a technical term useful for transit planning purposes and might not correspond to a how a passenger thinks of a calendar date. For example, a night bus running on Sunday morning at 1am to 3am, might have the previous Saturday's service date. 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/resources/org/opentripplanner/apis/gtfs/expectations/walk-steps.json b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/walk-steps.json index be952f72303..0e089aac428 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 @@ -24,7 +24,7 @@ { "streetName": "entrance", "area": false, - "relativeDirection": "ENTER_OR_EXIT_STATION", + "relativeDirection": "CONTINUE", "absoluteDirection": null, "feature": { "__typename": "Entrance", From 2eb0e7b45c87941ea5fb928c4929f748fa089d5b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Dec 2024 11:33:51 +0100 Subject: [PATCH 054/195] Add documentation --- .../model/vertex/StationEntranceVertex.java | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) 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 index ef94bbb64ef..254c71c527d 100644 --- a/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java +++ b/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java @@ -5,28 +5,46 @@ 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 boolean accessible; - - public StationEntranceVertex(double x, double y, long nodeId, String code, boolean accessible) { - super(x, y, nodeId); + private final boolean wheelchairAccessible; + + public StationEntranceVertex( + double lat, + double lon, + long nodeId, + String code, + boolean wheelchairAccessible + ) { + super(lat, lon, nodeId); this.code = code; - this.accessible = accessible; + this.wheelchairAccessible = wheelchairAccessible; } + /** + * The id of the entrance which may or may not be human-readable. + */ public FeedScopedId id() { - return new FeedScopedId("osm", String.valueOf(nodeId)); + 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 accessible ? Accessibility.POSSIBLE : Accessibility.NOT_POSSIBLE; + return wheelchairAccessible ? Accessibility.POSSIBLE : Accessibility.NOT_POSSIBLE; } @Override From 977d8ebc943d0476f3e1723ed56f1e9ee48be39d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Dec 2024 12:07:09 +0100 Subject: [PATCH 055/195] Clean up --- .../ext/restapi/mapping/RelativeDirectionMapper.java | 3 +-- .../opentripplanner/ext/restapi/mapping/WalkStepMapper.java | 2 +- .../ext/restapi/model/ApiRelativeDirection.java | 1 - .../opentripplanner/apis/gtfs/datafetchers/stepImpl.java | 2 +- .../apis/transmodel/model/plan/PathGuidanceType.java | 2 +- .../module/osm/parameters/OsmExtractParameters.java | 1 - .../org/opentripplanner/model/plan/RelativeDirection.java | 6 ++++++ .../main/java/org/opentripplanner/model/plan/WalkStep.java | 2 +- 8 files changed, 11 insertions(+), 8 deletions(-) diff --git a/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/RelativeDirectionMapper.java b/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/RelativeDirectionMapper.java index 708da1fd6c3..ab9abaa4481 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/RelativeDirectionMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/RelativeDirectionMapper.java @@ -14,7 +14,7 @@ public static ApiRelativeDirection mapRelativeDirection(RelativeDirection domain case HARD_LEFT -> ApiRelativeDirection.HARD_LEFT; case LEFT -> ApiRelativeDirection.LEFT; case SLIGHTLY_LEFT -> ApiRelativeDirection.SLIGHTLY_LEFT; - case CONTINUE -> ApiRelativeDirection.CONTINUE; + case CONTINUE, ENTER_OR_EXIT_STATION -> ApiRelativeDirection.CONTINUE; case SLIGHTLY_RIGHT -> ApiRelativeDirection.SLIGHTLY_RIGHT; case RIGHT -> ApiRelativeDirection.RIGHT; case HARD_RIGHT -> ApiRelativeDirection.HARD_RIGHT; @@ -25,7 +25,6 @@ public static ApiRelativeDirection mapRelativeDirection(RelativeDirection domain case UTURN_RIGHT -> ApiRelativeDirection.UTURN_RIGHT; case ENTER_STATION -> ApiRelativeDirection.ENTER_STATION; case EXIT_STATION -> ApiRelativeDirection.EXIT_STATION; - case ENTER_OR_EXIT_STATION -> ApiRelativeDirection.ENTER_OR_EXIT_STATION; case FOLLOW_SIGNS -> ApiRelativeDirection.FOLLOW_SIGNS; }; } diff --git a/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java b/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java index 9360620d40b..49e467a89db 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java @@ -39,7 +39,7 @@ public ApiWalkStep mapWalkStep(WalkStep domain) { api.streetName = domain.getDirectionText().toString(locale); api.absoluteDirection = domain.getAbsoluteDirection().map(AbsoluteDirectionMapper::mapAbsoluteDirection).orElse(null); - api.exit = domain.getHighwayExit(); + api.exit = domain.isHighwayExit(); api.stayOn = domain.isStayOn(); api.area = domain.getArea(); api.bogusName = domain.nameIsDerived(); diff --git a/application/src/ext/java/org/opentripplanner/ext/restapi/model/ApiRelativeDirection.java b/application/src/ext/java/org/opentripplanner/ext/restapi/model/ApiRelativeDirection.java index eb624df5ea6..02a530f06de 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/model/ApiRelativeDirection.java +++ b/application/src/ext/java/org/opentripplanner/ext/restapi/model/ApiRelativeDirection.java @@ -21,6 +21,5 @@ public enum ApiRelativeDirection { UTURN_RIGHT, ENTER_STATION, EXIT_STATION, - ENTER_OR_EXIT_STATION, FOLLOW_SIGNS, } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java index 4953d887713..7b1df1693d3 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java @@ -51,7 +51,7 @@ public DataFetcher> elevationProfile() { @Override public DataFetcher exit() { - return environment -> getSource(environment).getHighwayExit(); + return environment -> getSource(environment).isHighwayExit(); } @Override diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java index 5c2fa6f3a5e..74b30c83f44 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java @@ -65,7 +65,7 @@ public static GraphQLObjectType create(GraphQLObjectType elevationStepType) { .name("exit") .description("When exiting a highway or traffic circle, the exit name/number.") .type(Scalars.GraphQLString) - .dataFetcher(environment -> ((WalkStep) environment.getSource()).getHighwayExit()) + .dataFetcher(environment -> ((WalkStep) environment.getSource()).isHighwayExit()) .build() ) .field( 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 37edaf687ab..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 @@ -49,7 +49,6 @@ public ZoneId timeZone() { return timeZone; } - @Nullable public boolean includeOsmSubwayEntrances() { return includeOsmSubwayEntrances; } 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 fbdb836ab6a..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,12 @@ 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; 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 eb59196b971..7edae8d7174 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java +++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java @@ -130,7 +130,7 @@ public Optional getAbsoluteDirection() { /** * When exiting a highway or traffic circle, the exit name/number. */ - public String getHighwayExit() { + public String isHighwayExit() { return exit; } From b7cc6fddb042f6f69a098208925c8d71cba7ee29 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Dec 2024 12:36:16 +0100 Subject: [PATCH 056/195] Remove enum mapper test for REST API --- .../ext/restapi/mapping/EnumMapperTest.java | 41 ------------------- .../ext/restapi/mapping/WalkStepMapper.java | 2 +- .../apis/gtfs/datafetchers/stepImpl.java | 11 +---- .../model/plan/PathGuidanceType.java | 4 +- .../opentripplanner/model/plan/WalkStep.java | 8 ++-- 5 files changed, 10 insertions(+), 56 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/restapi/mapping/EnumMapperTest.java b/application/src/ext-test/java/org/opentripplanner/ext/restapi/mapping/EnumMapperTest.java index 35cc368fec4..5b03ada1c1f 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/restapi/mapping/EnumMapperTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/restapi/mapping/EnumMapperTest.java @@ -8,39 +8,11 @@ import java.util.Map; import java.util.function.Function; import org.junit.jupiter.api.Test; -import org.opentripplanner.ext.restapi.model.ApiAbsoluteDirection; -import org.opentripplanner.ext.restapi.model.ApiRelativeDirection; import org.opentripplanner.ext.restapi.model.ApiVertexType; -import org.opentripplanner.model.plan.AbsoluteDirection; -import org.opentripplanner.model.plan.RelativeDirection; import org.opentripplanner.model.plan.VertexType; public class EnumMapperTest { - private static final String MSG = - "Assert that the API enums have the exact same values that " + - "the domain enums of the same type, and that the specialized mapper is mapping all " + - "values. If this assumtion does not hold, create a new test."; - - @Test - public void map() { - try { - verifyExactMatch( - AbsoluteDirection.class, - ApiAbsoluteDirection.class, - AbsoluteDirectionMapper::mapAbsoluteDirection - ); - verifyExactMatch( - RelativeDirection.class, - ApiRelativeDirection.class, - RelativeDirectionMapper::mapRelativeDirection - ); - } catch (RuntimeException ex) { - System.out.println(MSG); - throw ex; - } - } - @Test public void testVertexTypeMapping() { verifyExplicitMatch( @@ -75,17 +47,4 @@ private , A extends Enum> void verifyExplicitMatch( assertTrue(rest.isEmpty()); } - private , A extends Enum> void verifyExactMatch( - Class domainClass, - Class apiClass, - Function mapper - ) { - List rest = new ArrayList<>(List.of(apiClass.getEnumConstants())); - for (D it : domainClass.getEnumConstants()) { - A result = mapper.apply(it); - assertEquals(result.name(), it.name()); - rest.remove(result); - } - assertTrue(rest.isEmpty()); - } } diff --git a/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java b/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java index 49e467a89db..c4aa11904cc 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java +++ b/application/src/ext/java/org/opentripplanner/ext/restapi/mapping/WalkStepMapper.java @@ -39,7 +39,7 @@ public ApiWalkStep mapWalkStep(WalkStep domain) { api.streetName = domain.getDirectionText().toString(locale); api.absoluteDirection = domain.getAbsoluteDirection().map(AbsoluteDirectionMapper::mapAbsoluteDirection).orElse(null); - api.exit = domain.isHighwayExit(); + api.exit = domain.highwayExit().orElse(null); api.stayOn = domain.isStayOn(); api.area = domain.getArea(); api.bogusName = domain.nameIsDerived(); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java index 7b1df1693d3..409bb2abb1d 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/stepImpl.java @@ -7,7 +7,6 @@ import org.opentripplanner.apis.gtfs.mapping.DirectionMapper; import org.opentripplanner.apis.gtfs.mapping.StreetNoteMapper; import org.opentripplanner.model.plan.ElevationProfile.Step; -import org.opentripplanner.model.plan.RelativeDirection; import org.opentripplanner.model.plan.WalkStep; import org.opentripplanner.routing.alertpatch.TransitAlert; @@ -51,18 +50,12 @@ public DataFetcher> elevationProfile() { @Override public DataFetcher exit() { - return environment -> getSource(environment).isHighwayExit(); + return environment -> getSource(environment).highwayExit().orElse(null); } @Override public DataFetcher feature() { - return environment -> { - WalkStep source = getSource(environment); - if (source.getRelativeDirection() == RelativeDirection.ENTER_OR_EXIT_STATION) { - return source.getEntrance(); - } - return null; - }; + return environment -> getSource(environment).entrance().orElse(null); } @Override diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java index 74b30c83f44..86c8359c026 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java @@ -65,7 +65,9 @@ public static GraphQLObjectType create(GraphQLObjectType elevationStepType) { .name("exit") .description("When exiting a highway or traffic circle, the exit name/number.") .type(Scalars.GraphQLString) - .dataFetcher(environment -> ((WalkStep) environment.getSource()).isHighwayExit()) + .dataFetcher(environment -> + ((WalkStep) environment.getSource()).highwayExit().orElse(null) + ) .build() ) .field( 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 7edae8d7174..fea605fe910 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java +++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java @@ -130,15 +130,15 @@ public Optional getAbsoluteDirection() { /** * When exiting a highway or traffic circle, the exit name/number. */ - public String isHighwayExit() { - return exit; + public Optional highwayExit() { + return exit.describeConstable(); } /** * Get information about a subway station entrance or exit. */ - public Entrance getEntrance() { - return entrance; + public Optional entrance() { + return Optional.ofNullable(entrance); } /** From e473061d17441245d415771ee7f1c25c404a02a0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Dec 2024 12:44:25 +0100 Subject: [PATCH 057/195] Fix highway exits --- .../java/org/opentripplanner/model/plan/WalkStep.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 fea605fe910..7ade16de39a 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java +++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStep.java @@ -44,7 +44,7 @@ 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; @@ -57,7 +57,7 @@ public final class WalkStep { AbsoluteDirection absoluteDirection, I18NString directionText, Set streetNotes, - String exit, + String highwayExit, Entrance entrance, ElevationProfile elevationProfile, boolean nameIsDerived, @@ -78,7 +78,7 @@ 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; @@ -131,7 +131,7 @@ public Optional getAbsoluteDirection() { * When exiting a highway or traffic circle, the exit name/number. */ public Optional highwayExit() { - return exit.describeConstable(); + return Optional.ofNullable(highwayExit); } /** From 082dafbb55077f7f49613f8585f339b32be80dee Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Thu, 19 Dec 2024 15:31:42 +0000 Subject: [PATCH 058/195] use an enum to specify the request source --- .../raptoradapter/transit/RaptorTransferIndex.java | 7 ++----- .../raptoradapter/transit/RequestSource.java | 12 ++++++++++++ .../transit/request/RaptorRequestTransferCache.java | 9 +++++++-- 3 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RequestSource.java 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 eaa472214b2..d034a9d8e35 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 @@ -28,14 +28,11 @@ public RaptorTransferIndex( /** * Create an index to be put into the transfer cache - * - * @param isRuntimeRequest true if the request originates from the client during the runtime, - * false if the request comes from transferCacheRequests in router-config.json */ public static RaptorTransferIndex create( List> transfersByStopIndex, StreetSearchRequest request, - boolean isRuntimeRequest + RequestSource requestSource ) { var forwardTransfers = new ArrayList>(transfersByStopIndex.size()); var reversedTransfers = new ArrayList>(transfersByStopIndex.size()); @@ -49,7 +46,7 @@ public static RaptorTransferIndex create( 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 (!isRuntimeRequest || OTPFeature.ParallelRouting.isOn()) { + if (requestSource == RequestSource.CONFIG || OTPFeature.ParallelRouting.isOn()) { stopIndices = stopIndices.parallel(); } stopIndices.forEach(fromStop -> { diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RequestSource.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RequestSource.java new file mode 100644 index 00000000000..9e49d2da78e --- /dev/null +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RequestSource.java @@ -0,0 +1,12 @@ +package org.opentripplanner.routing.algorithm.raptoradapter.transit; + +public enum RequestSource { + /** + * The request comes from transferCacheRequests in router-config.json + */ + CONFIG, + /** + * The request comes from a client routing request + */ + RUNTIME, +} 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 c7e09e12729..23724e0dd34 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 @@ -7,6 +7,7 @@ import java.util.Objects; import java.util.concurrent.ExecutionException; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RaptorTransferIndex; +import org.opentripplanner.routing.algorithm.raptoradapter.transit.RequestSource; import org.opentripplanner.routing.algorithm.raptoradapter.transit.Transfer; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; @@ -39,7 +40,7 @@ public void put(List> transfersByStopIndex, RouteRequest request) final RaptorTransferIndex raptorTransferIndex = RaptorTransferIndex.create( transfersByStopIndex, cacheKey.request, - false + RequestSource.CONFIG ); LOG.info("Initializing cache with request: {}", cacheKey.options); @@ -59,7 +60,11 @@ 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, true); + return RaptorTransferIndex.create( + cacheKey.transfersByStopIndex, + cacheKey.request, + RequestSource.RUNTIME + ); } }; } From 133cc8a34eed03370783952059e83a6d9121a086 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Fri, 20 Dec 2024 14:12:33 +0100 Subject: [PATCH 059/195] refactor: Extract TextVariablesSubstitution from EnvironmentVariableReplacer. This enable us to use this in places where we do not want to inject environment variables. --- .../project/EnvironmentVariableReplacer.java | 67 ++++++------ .../utils/text/TextVariablesSubstitution.java | 102 ++++++++++++++++++ .../text/TextVariablesSubstitutionTest.java | 56 ++++++++++ 3 files changed, 192 insertions(+), 33 deletions(-) create mode 100644 utils/src/main/java/org/opentripplanner/utils/text/TextVariablesSubstitution.java create mode 100644 utils/src/test/java/org/opentripplanner/utils/text/TextVariablesSubstitutionTest.java 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/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); + } +} From 1c8bf7b5d795c46152659fda3498c1cd1f9f280f Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 2 Jan 2025 12:23:00 +0100 Subject: [PATCH 060/195] feature: Add the ability to load custom documentation from a properties file --- .../custom-documentation-entur.properties | 27 +++ .../injectdoc/ApiDocumentationProfile.java | 27 +++ .../injectdoc/CustomDocumentation.java | 171 ++++++++++++++++++ .../injectdoc/CustomDocumentationTest.java | 76 ++++++++ 4 files changed, 301 insertions(+) create mode 100644 application/src/ext/resources/org/opentripplanner/ext/apis/transmodel/custom-documentation-entur.properties create mode 100644 application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java create mode 100644 application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentation.java create mode 100644 application/src/test/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentationTest.java diff --git a/application/src/ext/resources/org/opentripplanner/ext/apis/transmodel/custom-documentation-entur.properties b/application/src/ext/resources/org/opentripplanner/ext/apis/transmodel/custom-documentation-entur.properties new file mode 100644 index 00000000000..29c44b67f96 --- /dev/null +++ b/application/src/ext/resources/org/opentripplanner/ext/apis/transmodel/custom-documentation-entur.properties @@ -0,0 +1,27 @@ +# Use: +# [.].(description|deprecated)[.append] +# +# Examples +# // Replace the existing type description +# Quay.description=The place for boarding/alighting a vehicle +# +# // Append to the existing type description +# Quay.description.append=Append +# +# // Replace the existing field description +# Quay.name.description=The public name +# +# // Append to the existing field description +# Quay.name.description.append=(Source NSR) +# +# // Insert deprecated reason. Due to a bug in the Java GraphQL lib, an existing deprecated +# // reason cannot be updated. Deleting the reason from the schema, and adding it back using +# // the "default" TransmodelApiDocumentationProfile is a workaround. +# Quay.name.deprecated=This field is deprecated ... + + +TariffZone.description=A **zone** used to define a zonal fare structure in a zone-counting or \ + zone-matrix system. This includes TariffZone, as well as the specialised FareZone elements. \ + TariffZones are deprecated, please use FareZones. \ + \ + **TariffZone data will not be maintained from 1. MAY 2025 (Entur).** diff --git a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java new file mode 100644 index 00000000000..71b4e06a864 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java @@ -0,0 +1,27 @@ +package org.opentripplanner.apis.support.graphql.injectdoc; + +import org.opentripplanner.framework.doc.DocumentedEnum; + +public enum ApiDocumentationProfile implements DocumentedEnum { + DEFAULT, + ENTUR; + + private static final String TYPE_DOC = + "List of available custom documentation profiles. " + + "The default should be used in most cases. A profile may be used to deprecate part of the " + + "API in case it is not supported."; + + @Override + public String typeDescription() { + return TYPE_DOC; + } + + @Override + public String enumValueDescription() { + return switch (this) { + case DEFAULT -> "Default documentation is used."; + case ENTUR -> "Entur specific documentation. This deprecate features not supported at Entur," + + " Norway."; + }; + } +} diff --git a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentation.java b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentation.java new file mode 100644 index 00000000000..44629659b96 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentation.java @@ -0,0 +1,171 @@ +package org.opentripplanner.apis.support.graphql.injectdoc; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import javax.annotation.Nullable; +import org.opentripplanner.framework.application.OtpAppException; +import org.opentripplanner.utils.text.TextVariablesSubstitution; + +/** + * Load custom documentation from a properties file and make it available to any + * consumer using the {@code type-name[.field-name]} as key for lookups. + */ +public class CustomDocumentation { + + private static final String APPEND_SUFFIX = ".append"; + private static final String DESCRIPTION_SUFFIX = ".description"; + private static final String DEPRECATED_SUFFIX = ".deprecated"; + + /** Put custom documentaion in the following sandbox package */ + private static final String DOC_PATH = "org/opentripplanner/ext/apis/transmodel/"; + private static final String FILE_NAME = "custom-documentation"; + private static final String FILE_EXTENSION = ".properties"; + + private static final CustomDocumentation EMPTY = new CustomDocumentation(Map.of()); + + private final Map textMap; + + /** + * Pacakge local to be unit-testable + */ + CustomDocumentation(Map textMap) { + this.textMap = textMap; + } + + public static CustomDocumentation of(ApiDocumentationProfile profile) { + if (profile == ApiDocumentationProfile.DEFAULT) { + return EMPTY; + } + var map = loadCustomDocumentationFromPropertiesFile(profile); + return map.isEmpty() ? EMPTY : new CustomDocumentation(map); + } + + public boolean isEmpty() { + return textMap.isEmpty(); + } + + /** + * Get documentation for a type. The given {@code typeName} is used as the key. The + * documentation text is resolved by: + *
    + *
  1. + * first looking up the given {@code key} + {@code ".description"}. If a value is found, then + * the value is returned. + *
  2. + * then {@code key} + {@code ".description.append"} is used. If a value is found the + * {@code originalDoc} + {@code value} is returned. + *
  3. + *
+ * @param typeName Use {@code TYPE_NAME} or {@code TYPE_NAME.FIELD_NAME} as key. + */ + public Optional typeDescription(String typeName, @Nullable String originalDoc) { + return text(typeName, DESCRIPTION_SUFFIX, originalDoc); + } + + /** + * Same as {@link #typeDescription(String, String)} except the given {@code typeName} and + * {@code fieldName} is used as the key. + *
+   * key := typeName + "." fieldNAme
+   * 
+ */ + public Optional fieldDescription( + String typeName, + String fieldName, + @Nullable String originalDoc + ) { + return text(key(typeName, fieldName), DESCRIPTION_SUFFIX, originalDoc); + } + + /** + * Get deprecated reason for a field (types cannot be deprecated). The key + * ({@code key = typeName + '.' + fieldName} is used to retrieve the reason from the properties + * file. The deprecated documentation text is resolved by: + *
    + *
  1. + * first looking up the given {@code key} + {@code ".deprecated"}. If a value is found, then + * the value is returned. + *
  2. + * then {@code key} + {@code ".deprecated.append"} is used. If a value is found the + * {@code originalDoc} + {@code text} is returned. + *
  3. + *
+ * Any {@code null} values are excluded from the result and if both the input {@code originalDoc} + * and the resolved value is {@code null}, then {@code empty} is returned. + */ + public Optional fieldDeprecatedReason( + String typeName, + String fieldName, + @Nullable String originalDoc + ) { + return text(key(typeName, fieldName), DEPRECATED_SUFFIX, originalDoc); + } + + /* private methods */ + + /** + * Create a key from the given {@code typeName} and {@code fieldName} + */ + private static String key(String typeName, String fieldName) { + return typeName + "." + fieldName; + } + + private Optional text(String key, String suffix, @Nullable String originalText) { + final String k = key + suffix; + return text(k).or(() -> appendText(k, originalText)); + } + + private Optional text(String key) { + return Optional.ofNullable(textMap.get(key)); + } + + private Optional appendText(String key, @Nullable String originalText) { + String value = textMap.get(key + APPEND_SUFFIX); + if (value == null) { + return Optional.empty(); + } + return originalText == null ? Optional.of(value) : Optional.of(originalText + "\n\n" + value); + } + + /* private methods */ + + private static Map loadCustomDocumentationFromPropertiesFile( + ApiDocumentationProfile profile + ) { + try { + final String resource = resourceName(profile); + var input = ClassLoader.getSystemResourceAsStream(resource); + if (input == null) { + throw new OtpAppException("Resource not found: %s", resource); + } + var props = new Properties(); + props.load(input); + Map map = new HashMap<>(); + + for (String key : props.stringPropertyNames()) { + String value = props.getProperty(key); + if (value == null) { + value = ""; + } + map.put(key, value); + } + return TextVariablesSubstitution.insertVariables( + map, + varName -> errorHandlerVariableSubstitution(varName, resource) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void errorHandlerVariableSubstitution(String name, String source) { + throw new OtpAppException("Variable substitution failed for '${%s}' in %s.", name, source); + } + + private static String resourceName(ApiDocumentationProfile profile) { + return DOC_PATH + FILE_NAME + "-" + profile.name().toLowerCase() + FILE_EXTENSION; + } +} 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)); + } +} From 85e124f3dfd88359fc0732b542441f7ea13403c3 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 2 Jan 2025 12:25:07 +0100 Subject: [PATCH 061/195] feature: Make a GraphQL schema visitor to inject custom documentation --- .../injectdoc/InjectCustomDocumentation.java | 173 ++++++++++++++++++ .../InjectCustomDocumentationTest.java | 134 ++++++++++++++ .../InjectCustomDocumentationTest.graphql | 52 ++++++ ...ctCustomDocumentationTest.graphql.expected | 93 ++++++++++ 4 files changed, 452 insertions(+) create mode 100644 application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java create mode 100644 application/src/test/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.java create mode 100644 application/src/test/resources/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.graphql create mode 100644 application/src/test/resources/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.graphql.expected diff --git a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java new file mode 100644 index 00000000000..f2793a4e6c3 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java @@ -0,0 +1,173 @@ +package org.opentripplanner.apis.support.graphql.injectdoc; + +import static graphql.util.TraversalControl.CONTINUE; + +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLEnumValueDefinition; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInterfaceType; +import graphql.schema.GraphQLNamedSchemaElement; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLScalarType; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLTypeVisitor; +import graphql.schema.GraphQLTypeVisitorStub; +import graphql.schema.GraphQLUnionType; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * This is GraphQL visitor witch inject custom documentation on types and fields. + */ +public class InjectCustomDocumentation + extends GraphQLTypeVisitorStub + implements GraphQLTypeVisitor { + + private final CustomDocumentation customDocumentation; + + public InjectCustomDocumentation(CustomDocumentation customDocumentation) { + this.customDocumentation = customDocumentation; + } + + @Override + public TraversalControl visitGraphQLScalarType( + GraphQLScalarType scalar, + TraverserContext context + ) { + return typeDoc(context, scalar, (s, doc) -> s.transform(b -> b.description(doc))); + } + + @Override + public TraversalControl visitGraphQLInterfaceType( + GraphQLInterfaceType interface_, + TraverserContext context + ) { + return typeDoc(context, interface_, (f, doc) -> f.transform(b -> b.description(doc))); + } + + @Override + public TraversalControl visitGraphQLEnumType( + GraphQLEnumType enumType, + TraverserContext context + ) { + return typeDoc(context, enumType, (f, doc) -> f.transform(b -> b.description(doc))); + } + + @Override + public TraversalControl visitGraphQLEnumValueDefinition( + GraphQLEnumValueDefinition enumValue, + TraverserContext context + ) { + return fieldDoc( + context, + enumValue, + enumValue.getDeprecationReason(), + (f, doc) -> f.transform(b -> b.description(doc)), + (f, reason) -> f.transform(b -> b.deprecationReason(reason)) + ); + } + + @Override + public TraversalControl visitGraphQLFieldDefinition( + GraphQLFieldDefinition field, + TraverserContext context + ) { + return fieldDoc( + context, + field, + field.getDeprecationReason(), + (f, doc) -> f.transform(b -> b.description(doc)), + (f, reason) -> f.transform(b -> b.deprecate(reason)) + ); + } + + @Override + public TraversalControl visitGraphQLInputObjectField( + GraphQLInputObjectField inputField, + TraverserContext context + ) { + return fieldDoc( + context, + inputField, + inputField.getDeprecationReason(), + (f, doc) -> f.transform(b -> b.description(doc)), + (f, reason) -> f.transform(b -> b.deprecate(reason)) + ); + } + + @Override + public TraversalControl visitGraphQLInputObjectType( + GraphQLInputObjectType inputType, + TraverserContext context + ) { + return typeDoc(context, inputType, (f, doc) -> f.transform(b -> b.description(doc))); + } + + @Override + public TraversalControl visitGraphQLObjectType( + GraphQLObjectType object, + TraverserContext context + ) { + return typeDoc(context, object, (f, doc) -> f.transform(b -> b.description(doc))); + } + + @Override + public TraversalControl visitGraphQLUnionType( + GraphQLUnionType union, + TraverserContext context + ) { + return typeDoc(context, union, (f, doc) -> f.transform(b -> b.description(doc))); + } + + /* private methods */ + + /** + * Set or append description on a Scalar, Object, InputType, Union, Interface or Enum. + */ + private TraversalControl typeDoc( + TraverserContext context, + T element, + BiFunction setDescription + ) { + customDocumentation + .typeDescription(element.getName(), element.getDescription()) + .map(doc -> setDescription.apply(element, doc)) + .ifPresent(f -> changeNode(context, f)); + return CONTINUE; + } + + /** + * Set or append description and deprecated reason on a field [Object, InputType, Interface, + * Union or Enum]. + */ + private TraversalControl fieldDoc( + TraverserContext context, + T field, + String originalDeprecatedReason, + BiFunction setDescription, + BiFunction setDeprecatedReason + ) { + // All fields need to be defined in a named element + if (!(context.getParentNode() instanceof GraphQLNamedSchemaElement parent)) { + throw new IllegalArgumentException("The field does not have a named parent: " + field); + } + var fieldName = field.getName(); + var typeName = parent.getName(); + + Optional f1 = customDocumentation + .fieldDescription(typeName, fieldName, field.getDescription()) + .map(doc -> setDescription.apply(field, doc)); + + Optional f2 = customDocumentation + .fieldDeprecatedReason(typeName, fieldName, originalDeprecatedReason) + .map(doc -> setDeprecatedReason.apply(f1.orElse(field), doc)); + + f2.or(() -> f1).ifPresent(f -> changeNode(context, f)); + + return CONTINUE; + } +} 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..0f326a373aa --- /dev/null +++ b/application/src/test/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.java @@ -0,0 +1,134 @@ +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; + +/** + * This test read in a schema file, inject 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 sdl; + private String sdlExpected; + + @BeforeEach + void setUp() throws IOException { + 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", + "Duration.description", + "InputType.description", + "InputType.a.description", + "InputType.b.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) + // Some editors like IntelliJ remove space characters at the end of a + // line, so we do the same here to avoid false positive results. + .replaceAll(" +\\n", "\n"); + + 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("BType.a.deprecated", "CType.b.deprecated.append"); + + assertEquals(expected, missingValues); + assertEquals(sdlExpected, result); + } + + 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/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..599c4d3b12a --- /dev/null +++ b/application/src/test/resources/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.graphql @@ -0,0 +1,52 @@ +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 +} + +# Add doc to scalar +scalar Duration + +# Add doc to input type +input InputType { + a: String + b: String +} 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..c8cb7f680bf --- /dev/null +++ b/application/src/test/resources/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.graphql.expected @@ -0,0 +1,93 @@ +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") +} + +"Duration.description" +scalar Duration + +"InputType.description" +input InputType { + "InputType.a.description" + a: String + b: String @deprecated(reason : "InputType.b.deprecated") +} From d2fd01b2ca7c2a664f9e12996fe6ffa8cc968bbd Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 2 Jan 2025 12:33:24 +0100 Subject: [PATCH 062/195] feature: Make the API documentation profile configurable --- .../injectdoc/ApiDocumentationProfile.java | 9 +- .../config/routerconfig/ServerConfig.java | 26 ++++- doc/user/RouterConfiguration.md | 95 +++++++++++-------- 3 files changed, 83 insertions(+), 47 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java index 71b4e06a864..1ed63a9bd96 100644 --- a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java +++ b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java @@ -7,9 +7,12 @@ public enum ApiDocumentationProfile implements DocumentedEnum 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/doc/user/RouterConfiguration.md b/doc/user/RouterConfiguration.md index 7dae97fd74c..10065eab6db 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 support 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 support 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"` From 90ac6d2bf29b8183707fdbd891e35eadbb6bcedb Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 2 Jan 2025 12:40:52 +0100 Subject: [PATCH 063/195] feature: Inject custom documentation in Transmodel API --- .../apis/transmodel/TransmodelAPI.java | 3 ++ .../transmodel/TransmodelGraphQLSchema.java | 31 ++++++++++++++++--- .../configure/ConstructApplication.java | 1 + .../TransmodelGraphQLSchemaTest.java | 2 ++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelAPI.java b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelAPI.java index 66377a56390..62b9b5f0a45 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelAPI.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelAPI.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; +import org.opentripplanner.apis.support.graphql.injectdoc.ApiDocumentationProfile; import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.standalone.api.OtpServerRequestContext; @@ -80,6 +81,7 @@ public static void setUp( TransmodelAPIParameters config, TimetableRepository timetableRepository, RouteRequest defaultRouteRequest, + ApiDocumentationProfile documentationProfile, TransitRoutingConfig transitRoutingConfig ) { if (config.hideFeedId()) { @@ -91,6 +93,7 @@ public static void setUp( TransmodelGraphQLSchema.create( defaultRouteRequest, timetableRepository.getTimeZone(), + documentationProfile, transitRoutingConfig ); } diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 922f9f5244b..430aa8d740f 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -27,6 +27,7 @@ import graphql.schema.GraphQLOutputType; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; +import graphql.schema.SchemaTransformer; import java.time.LocalDate; import java.time.ZoneId; import java.util.ArrayList; @@ -42,8 +43,12 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nullable; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Envelope; +import org.opentripplanner.apis.support.graphql.injectdoc.ApiDocumentationProfile; +import org.opentripplanner.apis.support.graphql.injectdoc.CustomDocumentation; +import org.opentripplanner.apis.support.graphql.injectdoc.InjectCustomDocumentation; import org.opentripplanner.apis.transmodel.mapping.PlaceMapper; import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; import org.opentripplanner.apis.transmodel.model.DefaultRouteRequestType; @@ -155,10 +160,12 @@ private TransmodelGraphQLSchema( public static GraphQLSchema create( RouteRequest defaultRequest, ZoneId timeZoneId, - TransitTuningParameters transitTuningParameters + ApiDocumentationProfile docProfile, + TransitTuningParameters transitTuning ) { - return new TransmodelGraphQLSchema(defaultRequest, timeZoneId, transitTuningParameters) - .create(); + var schema = new TransmodelGraphQLSchema(defaultRequest, timeZoneId, transitTuning).create(); + schema = decorateSchemaWithCustomDocumentation(schema, docProfile); + return schema; } @SuppressWarnings("unchecked") @@ -1620,7 +1627,7 @@ private GraphQLSchema create() { .field(DatedServiceJourneyQuery.createQuery(datedServiceJourneyType)) .build(); - return GraphQLSchema + var schema = GraphQLSchema .newSchema() .query(queryType) .additionalType(placeInterface) @@ -1628,9 +1635,23 @@ private GraphQLSchema create() { .additionalType(Relay.pageInfoType) .additionalDirective(TransmodelDirectives.TIMING_DATA) .build(); + + return schema; + } + + private static GraphQLSchema decorateSchemaWithCustomDocumentation( + GraphQLSchema schema, + ApiDocumentationProfile docProfile + ) { + var customDocumentation = CustomDocumentation.of(docProfile); + if (customDocumentation.isEmpty()) { + return schema; + } + var visitor = new InjectCustomDocumentation(customDocumentation); + return SchemaTransformer.transformSchema(schema, visitor); } - private List toIdList(List ids) { + private List toIdList(@Nullable List ids) { if (ids == null) { return Collections.emptyList(); } 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..65a1146f8f2 100644 --- a/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/application/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -183,6 +183,7 @@ private void setupTransitRoutingServer() { routerConfig().transmodelApi(), timetableRepository(), routerConfig().routingRequestDefaults(), + routerConfig().server().apiDocumentationProfile(), routerConfig().transitTuningConfig() ); } 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); From 0bf3c72fc2a8ff15e20b129348737cd1fef53473 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 2 Jan 2025 14:53:36 +0100 Subject: [PATCH 064/195] test: Ignore white-space when comparing text In the case used, we compare to GraphQL schemas. --- .../_support/text/TextAssertions.java | 64 +++++++++++++++++++ .../_support/text/TextAssertionsTest.java | 48 ++++++++++++++ .../InjectCustomDocumentationTest.java | 4 +- 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 application/src/test/java/org/opentripplanner/_support/text/TextAssertions.java create mode 100644 application/src/test/java/org/opentripplanner/_support/text/TextAssertionsTest.java 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..b1bdb3792cb --- /dev/null +++ b/application/src/test/java/org/opentripplanner/_support/text/TextAssertionsTest.java @@ -0,0 +1,48 @@ +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.assertEquals( + """ + Expected(@end-of-text): <> + Actual (@line 1): + """, + ex.getMessage() + ); + } +} 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 index 0f326a373aa..3e856faf413 100644 --- 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 @@ -18,6 +18,7 @@ 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 read in a schema file, inject documentation and convert the @@ -120,7 +121,8 @@ void test() { var expected = List.of("BType.a.deprecated", "CType.b.deprecated.append"); assertEquals(expected, missingValues); - assertEquals(sdlExpected, result); + + TextAssertions.assertLinesEquals(sdlExpected, result); } String loadSchemaResource(String suffix) throws IOException { From f2406e7cafb0b13f09f9b7769ec31cdeb61b5cd8 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 2 Jan 2025 16:53:29 +0100 Subject: [PATCH 065/195] refactor: Cleanup test and make test run on Windows OS --- .../_support/text/TextAssertionsTest.java | 13 ++++++------ .../InjectCustomDocumentationTest.java | 21 +++++++++++-------- .../InjectCustomDocumentationTest.graphql | 2 ++ ...ctCustomDocumentationTest.graphql.expected | 2 ++ 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/_support/text/TextAssertionsTest.java b/application/src/test/java/org/opentripplanner/_support/text/TextAssertionsTest.java index b1bdb3792cb..739b7b59c4b 100644 --- a/application/src/test/java/org/opentripplanner/_support/text/TextAssertionsTest.java +++ b/application/src/test/java/org/opentripplanner/_support/text/TextAssertionsTest.java @@ -37,12 +37,13 @@ void testEndOfText() { org.opentest4j.AssertionFailedError.class, () -> assertLinesEquals("A\n", "A\nExtra Line") ); - Assertions.assertEquals( - """ - Expected(@end-of-text): <> - Actual (@line 1): - """, - ex.getMessage() + 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/support/graphql/injectdoc/InjectCustomDocumentationTest.java b/application/src/test/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentationTest.java index 3e856faf413..4231d7079e5 100644 --- 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 @@ -34,12 +34,11 @@ class InjectCustomDocumentationTest { private GraphQLSchema schema; - private String sdl; private String sdlExpected; @BeforeEach void setUp() throws IOException { - sdl = loadSchemaResource(".graphql"); + var sdl = loadSchemaResource(".graphql"); sdlExpected = loadSchemaResource(".graphql.expected"); var parser = new SchemaParser(); @@ -88,10 +87,12 @@ static Map text() { "AEnum.description", "AEnum.E1.description", "AEnum.E2.deprecated", + "AEnum.E3.deprecated", "Duration.description", "InputType.description", "InputType.a.description", - "InputType.b.deprecated" + "InputType.b.deprecated", + "InputType.c.deprecated" ) .collect(Collectors.toMap(e -> e, e -> e)); } @@ -103,11 +104,7 @@ void test() { var visitor = new InjectCustomDocumentation(customDocumentation); var newSchema = SchemaTransformer.transformSchema(schema, visitor); var p = new SchemaPrinter(); - var result = p - .print(newSchema) - // Some editors like IntelliJ remove space characters at the end of a - // line, so we do the same here to avoid false positive results. - .replaceAll(" +\\n", "\n"); + var result = p.print(newSchema); var missingValues = texts .values() @@ -118,13 +115,19 @@ void test() { // There is a bug in the Java GraphQL API, existing deprecated // doc is not updated or replaced. - var expected = List.of("BType.a.deprecated", "CType.b.deprecated.append"); + 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; 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 index 599c4d3b12a..33deaa2a364 100644 --- 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 @@ -40,6 +40,7 @@ type QueryType { enum AEnum { E1 E2 + E3 @deprecated(reason: "REPLACE") } # Add doc to scalar @@ -49,4 +50,5 @@ scalar Duration 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 index c8cb7f680bf..47319e07ae0 100644 --- 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 @@ -80,6 +80,7 @@ enum AEnum { "AEnum.E1.description" E1 E2 @deprecated(reason : "AEnum.E2.deprecated") + E3 @deprecated(reason : "REPLACE") } "Duration.description" @@ -90,4 +91,5 @@ input InputType { "InputType.a.description" a: String b: String @deprecated(reason : "InputType.b.deprecated") + c: String @deprecated(reason : "REPLACE") } From 713143b74df15a5cb25cafe3300530bdc5f7d6ba Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 2 Jan 2025 22:58:53 +0100 Subject: [PATCH 066/195] Use three states for accessibility --- .../module/osm/ElevatorProcessor.java | 4 ++-- .../graph_builder/module/osm/VertexGenerator.java | 4 +--- .../opentripplanner/osm/model/OsmWithTags.java | 2 +- .../mapping/StatesToWalkStepsMapper.java | 1 - .../model/vertex/StationEntranceVertex.java | 8 ++++---- .../street/model/vertex/VertexFactory.java | 7 +++++-- .../osm/model/OsmWithTagsTest.java | 15 +++++++++++++++ 7 files changed, 28 insertions(+), 13 deletions(-) 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/VertexGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/VertexGenerator.java index b4c3c08aa8b..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 @@ -104,9 +104,7 @@ IntersectionVertex getVertexForOsmNode(OsmNode node, OsmWithTags way) { if (includeOsmSubwayEntrances && node.isSubwayEntrance()) { String ref = node.getTag("ref"); - - boolean accessible = node.isTag("wheelchair", "yes"); - iv = vertexFactory.stationEntrance(nid, coordinate, ref, accessible); + iv = vertexFactory.stationEntrance(nid, coordinate, ref, node.wheelchairAccessibility()); } if (iv == null) { 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/StatesToWalkStepsMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/StatesToWalkStepsMapper.java index 9365c50509c..ae7674c7a96 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 @@ -29,7 +29,6 @@ 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.basic.Accessibility; import org.opentripplanner.transit.model.site.Entrance; /** 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 index 254c71c527d..7b9a94b0725 100644 --- a/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java +++ b/application/src/main/java/org/opentripplanner/street/model/vertex/StationEntranceVertex.java @@ -13,18 +13,18 @@ public class StationEntranceVertex extends OsmVertex { private static final String FEED_ID = "osm"; private final String code; - private final boolean wheelchairAccessible; + private final Accessibility wheelchairAccessibility; public StationEntranceVertex( double lat, double lon, long nodeId, String code, - boolean wheelchairAccessible + Accessibility wheelchairAccessibility ) { super(lat, lon, nodeId); this.code = code; - this.wheelchairAccessible = wheelchairAccessible; + this.wheelchairAccessibility = wheelchairAccessibility; } /** @@ -44,7 +44,7 @@ public String code() { } public Accessibility wheelchairAccessibility() { - return wheelchairAccessible ? Accessibility.POSSIBLE : Accessibility.NOT_POSSIBLE; + return wheelchairAccessibility; } @Override 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 853d66c56aa..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; @@ -98,9 +99,11 @@ public StationEntranceVertex stationEntrance( long nid, Coordinate coordinate, String code, - boolean accessible + Accessibility wheelchairAccessibility ) { - return addToGraph(new StationEntranceVertex(coordinate.x, coordinate.y, nid, code, accessible)); + return addToGraph( + new StationEntranceVertex(coordinate.x, coordinate.y, nid, code, wheelchairAccessibility) + ); } public OsmVertex osm( 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()); From 0b6a74a840c16b02b6bd8d85393084e572ce664d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 2 Jan 2025 23:07:39 +0100 Subject: [PATCH 067/195] Update documentation --- .../opentripplanner/apis/gtfs/schema.graphqls | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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 6ac56be3d1d..f8a51325a69 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3535,15 +3535,29 @@ 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 known then entrance information is in the `step.entity` field. + """ CONTINUE DEPART ELEVATOR + "Entering a public transport station. If known then entrance information is in the `step.entity` field." ENTER_STATION + "Exiting a public transport station. If known then entrance information is in the `step.entity` field." EXIT_STATION + "Follow the signs indicating a specific location like \"platform 1\" or \"exit B\"." FOLLOW_SIGNS HARD_LEFT HARD_RIGHT From cb810498275544f0e60c68a68758d84a8a5c500e Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 3 Jan 2025 09:59:42 +0100 Subject: [PATCH 068/195] Revert "Street search: traverse edges which may return multiple states" This reverts commit 4d11307a --- .../flex/template/AbstractFlexTemplate.java | 2 +- .../flex/template/FlexDirectPathFactory.java | 2 +- .../mapping/RaptorPathToItineraryMapper.java | 30 ++- .../raptoradapter/transit/Transfer.java | 9 +- .../search/request/StreetSearchRequest.java | 4 - .../street/search/state/EdgeTraverser.java | 58 ++---- .../_support/geometry/Coordinates.java | 2 - .../search/state/EdgeTraverserTest.java | 191 ------------------ .../street/search/state/TestStateBuilder.java | 2 +- 9 files changed, 42 insertions(+), 258 deletions(-) delete mode 100644 application/src/test/java/org/opentripplanner/street/search/state/EdgeTraverserTest.java diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java index aeeab84259c..8dbcf4d785e 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java @@ -199,7 +199,7 @@ private FlexAccessEgress createFlexAccessEgress( return null; } - final var finalStateOpt = EdgeTraverser.traverseEdges(afterFlexState, transferEdges); + final var finalStateOpt = EdgeTraverser.traverseEdges(afterFlexState[0], transferEdges); return finalStateOpt .map(finalState -> { diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java index ae35c262a1e..f27a502911f 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java @@ -113,7 +113,7 @@ private Optional createDirectGraphPath( final State[] afterFlexState = flexEdge.traverse(accessNearbyStop.state); - var finalStateOpt = EdgeTraverser.traverseEdges(afterFlexState, egress.edges); + var finalStateOpt = EdgeTraverser.traverseEdges(afterFlexState[0], egress.edges); if (finalStateOpt.isEmpty()) { return Optional.empty(); 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/raptoradapter/transit/Transfer.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/Transfer.java index 2643067398e..1cf8b015816 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 @@ -6,7 +6,6 @@ import java.util.EnumSet; import java.util.List; import java.util.Optional; -import java.util.Set; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.raptor.api.model.RaptorCostConverter; import org.opentripplanner.raptor.api.model.RaptorTransfer; @@ -15,7 +14,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 +96,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/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/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/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; } From 868a8dbabf76bac3ffce1e53a5711597f9df642f Mon Sep 17 00:00:00 2001 From: Vincent Paturet Date: Fri, 3 Jan 2025 10:05:46 +0100 Subject: [PATCH 069/195] Fix merge conflict --- .../routing/algorithm/raptoradapter/transit/Transfer.java | 1 + 1 file changed, 1 insertion(+) 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 1cf8b015816..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 @@ -6,6 +6,7 @@ import java.util.EnumSet; import java.util.List; import java.util.Optional; +import java.util.Set; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner.raptor.api.model.RaptorCostConverter; import org.opentripplanner.raptor.api.model.RaptorTransfer; From 2ae88164d7d2ebb90778b0a87df8fce39e6af9b5 Mon Sep 17 00:00:00 2001 From: Michael Tsang Date: Fri, 3 Jan 2025 10:30:42 +0000 Subject: [PATCH 070/195] apply review feedback --- .../transit/RaptorTransferIndex.java | 28 +++++++++++++++++-- .../raptoradapter/transit/RequestSource.java | 12 -------- .../request/RaptorRequestTransferCache.java | 11 +++----- 3 files changed, 29 insertions(+), 22 deletions(-) delete mode 100644 application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RequestSource.java 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 d034a9d8e35..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 @@ -13,6 +13,11 @@ public class RaptorTransferIndex { + private enum RequestSource { + SETUP, + REQUEST_SCOPE, + } + private final List[] forwardTransfers; private final List[] reversedTransfers; @@ -27,9 +32,26 @@ public RaptorTransferIndex( } /** - * Create an index to be put into the transfer cache + * 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 create( + 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 @@ -46,7 +68,7 @@ public static RaptorTransferIndex create( 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.CONFIG || OTPFeature.ParallelRouting.isOn()) { + if (requestSource == RequestSource.SETUP || OTPFeature.ParallelRouting.isOn()) { stopIndices = stopIndices.parallel(); } stopIndices.forEach(fromStop -> { diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RequestSource.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RequestSource.java deleted file mode 100644 index 9e49d2da78e..00000000000 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RequestSource.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.opentripplanner.routing.algorithm.raptoradapter.transit; - -public enum RequestSource { - /** - * The request comes from transferCacheRequests in router-config.json - */ - CONFIG, - /** - * The request comes from a client routing request - */ - RUNTIME, -} 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 23724e0dd34..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 @@ -7,7 +7,6 @@ import java.util.Objects; import java.util.concurrent.ExecutionException; import org.opentripplanner.routing.algorithm.raptoradapter.transit.RaptorTransferIndex; -import org.opentripplanner.routing.algorithm.raptoradapter.transit.RequestSource; import org.opentripplanner.routing.algorithm.raptoradapter.transit.Transfer; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; @@ -37,10 +36,9 @@ 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, - RequestSource.CONFIG + cacheKey.request ); LOG.info("Initializing cache with request: {}", cacheKey.options); @@ -60,10 +58,9 @@ private CacheLoader cacheLoader() { @Override public RaptorTransferIndex load(CacheKey cacheKey) { LOG.info("Adding runtime request to cache: {}", cacheKey.options); - return RaptorTransferIndex.create( + return RaptorTransferIndex.createRequestScope( cacheKey.transfersByStopIndex, - cacheKey.request, - RequestSource.RUNTIME + cacheKey.request ); } }; From 5b12c7f5bfdd4264d3b81322924b5a8f2e68e138 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 6 Jan 2025 09:33:22 +0100 Subject: [PATCH 071/195] Update docs --- .../opentripplanner/apis/gtfs/schema.graphqls | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) 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 f8a51325a69..afae31e19f0 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3548,14 +3548,26 @@ enum RelativeDirection { - 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 known then entrance information is in the `step.entity` field. + entering or exiting. If it _is_ known then `ENTER_STATION`/`EXIT_STATION` is used. + + If available, entrance information is in the `step.feature` field. """ CONTINUE DEPART ELEVATOR - "Entering a public transport station. If known then entrance information is in the `step.entity` field." + """ + Entering a public transport station. If it's not known if the passenger is entering or exiting + then `CONTINUE` is used. + + If available, entrance information is in the `step.feature` field. + """ ENTER_STATION - "Exiting a public transport station. If known then entrance information is in the `step.entity` field." + """ + Exiting a public transport station. If it's not known if the passenger is entering or exiting + then `CONTINUE` is used. + + If available then entrance information is in the `step.feature` field. + """ EXIT_STATION "Follow the signs indicating a specific location like \"platform 1\" or \"exit B\"." FOLLOW_SIGNS From 8c6fda4e0df377deadd25e6649090e518290bac9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 6 Jan 2025 09:46:37 +0100 Subject: [PATCH 072/195] Extract entrance from transit link --- .../model/plan/WalkStepBuilder.java | 2 +- .../mapping/StatesToWalkStepsMapper.java | 16 +++++++++++++--- .../model/edge/StreetTransitEntranceLink.java | 13 +++++++++++++ .../opentripplanner/apis/gtfs/schema.graphqls | 6 +++--- 4 files changed, 30 insertions(+), 7 deletions(-) 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 877b40f24fd..75589718861 100644 --- a/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java +++ b/application/src/main/java/org/opentripplanner/model/plan/WalkStepBuilder.java @@ -76,7 +76,7 @@ public WalkStepBuilder withExit(String exit) { return this; } - public WalkStepBuilder withEntrance(Entrance entrance) { + public WalkStepBuilder withEntrance(@Nullable Entrance entrance) { this.entrance = entrance; return this; } 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 ae7674c7a96..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; @@ -160,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; } @@ -181,7 +182,14 @@ private void processState(State backState, State forwardState) { 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; } @@ -545,7 +553,8 @@ private void createAndSaveStep( State forwardState, I18NString name, RelativeDirection direction, - Edge edge + Edge edge, + @Nullable Entrance entrance ) { addStep( createWalkStep(forwardState, backState) @@ -553,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/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/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index afae31e19f0..8582fc7ba6f 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3549,7 +3549,7 @@ enum RelativeDirection { - 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. - + If available, entrance information is in the `step.feature` field. """ CONTINUE @@ -3558,14 +3558,14 @@ enum RelativeDirection { """ Entering a public transport station. If it's not known if the passenger is entering or exiting then `CONTINUE` is used. - + If available, entrance information 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. - + If available then entrance information is in the `step.feature` field. """ EXIT_STATION From bd94b1390dc720379d7394feb8824e8225e1c2b4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 6 Jan 2025 09:49:35 +0100 Subject: [PATCH 073/195] Add test for extracting entrance from pathway data --- .../algorithm/mapping/StatesToWalkStepsMapperTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 From 74106b5f2fd16c6fe8024089553bb58713df2faf Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 6 Jan 2025 10:00:57 +0100 Subject: [PATCH 074/195] Update schema docs --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 8582fc7ba6f..c9d0f3d73af 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3549,8 +3549,7 @@ enum RelativeDirection { - 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. - - If available, entrance information is in the `step.feature` field. + More information about the entrance is in the `step.feature` field. """ CONTINUE DEPART @@ -3559,14 +3558,14 @@ enum RelativeDirection { Entering a public transport station. If it's not known if the passenger is entering or exiting then `CONTINUE` is used. - If available, entrance information is in the `step.feature` field. + 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. - If available then entrance information is in the `step.feature` field. + 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\"." From f2296cd62497c2804e6d23c62644cfd361508b28 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 7 Jan 2025 08:29:59 +0200 Subject: [PATCH 075/195] Add formatting for JSON in documentation. --- .../standalone/config/buildconfig/TransferConfig.java | 2 ++ doc/user/BuildConfiguration.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java index aa70a2788c4..ce6a87f21e7 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java @@ -22,6 +22,7 @@ public static Map map(NodeAdapter root, String p **Example** +```JSON // build-config.json { "transfers": { @@ -35,6 +36,7 @@ public static Map map(NodeAdapter root, String p } } } +``` """ ) .asEnumMap(StreetMode.class, TransferParametersMapper::map, new EnumMap<>(StreetMode.class)); diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index dee3a266f8a..80bd08e8e0c 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -966,6 +966,7 @@ To configure mode-specific parameters, the modes should also be used in the `tra **Example** +```JSON // build-config.json { "transfers": { @@ -979,6 +980,7 @@ To configure mode-specific parameters, the modes should also be used in the `tra } } } +```

transitFeeds

From 0ac172ec2955eab5446365c91f1a905306fce6e0 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 7 Jan 2025 08:42:37 +0200 Subject: [PATCH 076/195] Rename variables. --- .../module/DirectTransferGenerator.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index d80144636a4..ffdfc1f6793 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -94,9 +94,9 @@ public void buildGraph() { /* The linker will use streets if they are available, or straight-line distance otherwise. */ NearbyStopFinder nearbyStopFinder = createNearbyStopFinder(defaultMaxTransferDuration); - HashMap defaultNearbyStopFinders = new HashMap<>(); + HashMap defaultNearbyStopFinderForMode = new HashMap<>(); /* These are used for calculating transfers only between carsAllowedStops. */ - HashMap carsAllowedStopNearbyStopFinders = new HashMap<>(); + HashMap carsAllowedStopNearbyStopFinderForMode = new HashMap<>(); List stops = graph.getVerticesOfType(TransitStopVertex.class); Set carsAllowedStops = timetableRepository.getStopLocationsUsedForCarsAllowedTrips(); @@ -135,8 +135,8 @@ public void buildGraph() { defaultTransferRequests, carsAllowedStopTransferRequests, flexTransferRequests, - defaultNearbyStopFinders, - carsAllowedStopNearbyStopFinders, + defaultNearbyStopFinderForMode, + carsAllowedStopNearbyStopFinderForMode, nearbyStopFinder ); @@ -158,7 +158,7 @@ public void buildGraph() { // Calculate default transfers. for (RouteRequest transferProfile : defaultTransferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); - for (NearbyStop sd : defaultNearbyStopFinders + for (NearbyStop sd : defaultNearbyStopFinderForMode .get(mode) .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false)) { // Skip the origin stop, loop transfers are not needed. @@ -188,7 +188,7 @@ public void buildGraph() { StreetMode mode = StreetMode.WALK; // This code is for finding transfers from AreaStops to Stops, transfers // from Stops to AreaStops and between Stops are already covered above. - for (NearbyStop sd : defaultNearbyStopFinders + for (NearbyStop sd : defaultNearbyStopFinderForMode .get(mode) .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), true)) { // Skip the origin stop, loop transfers are not needed. @@ -217,7 +217,7 @@ public void buildGraph() { if (carsAllowedStops.contains(stop)) { for (RouteRequest transferProfile : carsAllowedStopTransferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); - for (NearbyStop sd : carsAllowedStopNearbyStopFinders + for (NearbyStop sd : carsAllowedStopNearbyStopFinderForMode .get(mode) .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false)) { // Skip the origin stop, loop transfers are not needed. @@ -317,8 +317,8 @@ private void parseTransferParameters( List defaultTransferRequests, List carsAllowedStopTransferRequests, List flexTransferRequests, - HashMap defaultNearbyStopFinders, - HashMap carsAllowedStopNearbyStopFinders, + HashMap defaultNearbyStopFinderForMode, + HashMap carsAllowedStopNearbyStopFinderForMode, NearbyStopFinder nearbyStopFinder ) { for (RouteRequest transferProfile : transferRequests) { @@ -332,23 +332,23 @@ private void parseTransferParameters( // Set mode-specific maxTransferDuration, if it is set in the build config. Duration maxTransferDuration = transferParameters.maxTransferDuration(); if (maxTransferDuration != Duration.ZERO) { - defaultNearbyStopFinders.put(mode, createNearbyStopFinder(maxTransferDuration)); + defaultNearbyStopFinderForMode.put(mode, createNearbyStopFinder(maxTransferDuration)); } else { - defaultNearbyStopFinders.put(mode, nearbyStopFinder); + defaultNearbyStopFinderForMode.put(mode, nearbyStopFinder); } } // Create transfers between carsAllowedStops for the specific mode if carsAllowedStopMaxTransferDuration is set in the build config. Duration carsAllowedStopMaxTransferDuration = transferParameters.carsAllowedStopMaxTransferDuration(); if (carsAllowedStopMaxTransferDuration != Duration.ZERO) { carsAllowedStopTransferRequests.add(transferProfile); - carsAllowedStopNearbyStopFinders.put( + carsAllowedStopNearbyStopFinderForMode.put( mode, createNearbyStopFinder(carsAllowedStopMaxTransferDuration) ); } } else { defaultTransferRequests.add(transferProfile); - defaultNearbyStopFinders.put(mode, nearbyStopFinder); + defaultNearbyStopFinderForMode.put(mode, nearbyStopFinder); } } From bff77a5c91937518c96a246cab080d14d986f0cb Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 7 Jan 2025 09:18:14 +0200 Subject: [PATCH 077/195] Refactor transfer generation. --- .../module/DirectTransferGenerator.java | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index ffdfc1f6793..ec776ae2b77 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -94,9 +94,6 @@ public void buildGraph() { /* The linker will use streets if they are available, or straight-line distance otherwise. */ NearbyStopFinder nearbyStopFinder = createNearbyStopFinder(defaultMaxTransferDuration); - HashMap defaultNearbyStopFinderForMode = new HashMap<>(); - /* These are used for calculating transfers only between carsAllowedStops. */ - HashMap carsAllowedStopNearbyStopFinderForMode = new HashMap<>(); List stops = graph.getVerticesOfType(TransitStopVertex.class); Set carsAllowedStops = timetableRepository.getStopLocationsUsedForCarsAllowedTrips(); @@ -126,19 +123,8 @@ public void buildGraph() { HashMultimap.create() ); - List defaultTransferRequests = new ArrayList<>(); - List carsAllowedStopTransferRequests = new ArrayList<>(); - List flexTransferRequests = new ArrayList<>(); - // Parse the transfer configuration from the parameters given in the build config. - parseTransferParameters( - defaultTransferRequests, - carsAllowedStopTransferRequests, - flexTransferRequests, - defaultNearbyStopFinderForMode, - carsAllowedStopNearbyStopFinderForMode, - nearbyStopFinder - ); + TransferConfiguration transferConfiguration = parseTransferParameters(nearbyStopFinder); stops .stream() @@ -156,11 +142,12 @@ public void buildGraph() { LOG.debug("Linking stop '{}' {}", stop, ts0); // Calculate default transfers. - for (RouteRequest transferProfile : defaultTransferRequests) { + for (RouteRequest transferProfile : transferConfiguration.defaultTransferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); - for (NearbyStop sd : defaultNearbyStopFinderForMode + var nearbyStops = transferConfiguration.defaultNearbyStopFinderForMode .get(mode) - .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false)) { + .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false); + for (NearbyStop sd : nearbyStops) { // Skip the origin stop, loop transfers are not needed. if (sd.stop == stop) { continue; @@ -183,14 +170,15 @@ public void buildGraph() { } } // Calculate flex transfers if flex routing is enabled. - for (RouteRequest transferProfile : flexTransferRequests) { + for (RouteRequest transferProfile : transferConfiguration.flexTransferRequests) { // Flex transfer requests only use the WALK mode. StreetMode mode = StreetMode.WALK; + var nearbyStops = transferConfiguration.defaultNearbyStopFinderForMode + .get(mode) + .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), true); // This code is for finding transfers from AreaStops to Stops, transfers // from Stops to AreaStops and between Stops are already covered above. - for (NearbyStop sd : defaultNearbyStopFinderForMode - .get(mode) - .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), true)) { + for (NearbyStop sd : nearbyStops) { // Skip the origin stop, loop transfers are not needed. if (sd.stop == stop) { continue; @@ -213,13 +201,14 @@ public void buildGraph() { } } } - // Calculate transfers between stops that can use trips with cars if configured. + // Calculate transfers between stops that are visited by trips that allow cars, if configured. if (carsAllowedStops.contains(stop)) { - for (RouteRequest transferProfile : carsAllowedStopTransferRequests) { + for (RouteRequest transferProfile : transferConfiguration.carsAllowedStopTransferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); - for (NearbyStop sd : carsAllowedStopNearbyStopFinderForMode + var nearbyStops = transferConfiguration.carsAllowedStopNearbyStopFinderForMode .get(mode) - .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false)) { + .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false); + for (NearbyStop sd : nearbyStops) { // Skip the origin stop, loop transfers are not needed. if (sd.stop == stop) { continue; @@ -313,14 +302,14 @@ private NearbyStopFinder createNearbyStopFinder(Duration radiusByDuration) { } } - private void parseTransferParameters( - List defaultTransferRequests, - List carsAllowedStopTransferRequests, - List flexTransferRequests, - HashMap defaultNearbyStopFinderForMode, - HashMap carsAllowedStopNearbyStopFinderForMode, - NearbyStopFinder nearbyStopFinder - ) { + private TransferConfiguration parseTransferParameters(NearbyStopFinder nearbyStopFinder) { + List defaultTransferRequests = new ArrayList<>(); + List carsAllowedStopTransferRequests = new ArrayList<>(); + List flexTransferRequests = new ArrayList<>(); + HashMap defaultNearbyStopFinderForMode = new HashMap<>(); + /* These are used for calculating transfers only between carsAllowedStops. */ + HashMap carsAllowedStopNearbyStopFinderForMode = new HashMap<>(); + for (RouteRequest transferProfile : transferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); TransferParameters transferParameters = transferParametersForMode.get(mode); @@ -361,7 +350,23 @@ private void parseTransferParameters( .toList() ); } + + return new TransferConfiguration( + defaultTransferRequests, + carsAllowedStopTransferRequests, + flexTransferRequests, + defaultNearbyStopFinderForMode, + carsAllowedStopNearbyStopFinderForMode + ); } + private record TransferConfiguration( + List defaultTransferRequests, + List carsAllowedStopTransferRequests, + List flexTransferRequests, + HashMap defaultNearbyStopFinderForMode, + HashMap carsAllowedStopNearbyStopFinderForMode + ) {} + private record TransferKey(StopLocation source, StopLocation target, List edges) {} } From 9f5d63c441e19e64af9357a25195f685549bbdcb Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 7 Jan 2025 11:42:29 +0200 Subject: [PATCH 078/195] Rename transfers to transferParameters in the build config and throw errors when invalid transferParameters are given. --- .../module/DirectTransferGenerator.java | 20 +++++++++++++++++-- .../standalone/config/BuildConfig.java | 2 +- .../module/DirectTransferGeneratorTest.java | 9 +-------- doc/user/BuildConfiguration.md | 4 ++-- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index ec776ae2b77..f173c52d6a0 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -310,13 +310,29 @@ private TransferConfiguration parseTransferParameters(NearbyStopFinder nearbySto /* These are used for calculating transfers only between carsAllowedStops. */ HashMap carsAllowedStopNearbyStopFinderForMode = new HashMap<>(); + // Check that the mode specified in transferParameters can also be found in transferRequests. + for (StreetMode mode : transferParametersForMode.keySet()) { + if ( + !transferRequests + .stream() + .anyMatch(transferProfile -> transferProfile.journey().transfer().mode() == mode) + ) { + throw new IllegalArgumentException( + String.format("Mode %s is used in transferParameters but not in transferRequests", mode) + ); + } + } + for (RouteRequest transferProfile : transferRequests) { StreetMode mode = transferProfile.journey().transfer().mode(); TransferParameters transferParameters = transferParametersForMode.get(mode); if (transferParameters != null) { - // Disable normal transfer calculations for the specific mode, if disableDefaultTransfers is set in the build config. // WALK mode transfers can not be disabled. For example, flex transfers need them. - if (!transferParameters.disableDefaultTransfers() || mode == StreetMode.WALK) { + if (transferParameters.disableDefaultTransfers() && mode == StreetMode.WALK) { + throw new IllegalArgumentException("WALK mode transfers can not be disabled"); + } + // Disable normal transfer calculations for the specific mode, if disableDefaultTransfers is set in the build config. + if (!transferParameters.disableDefaultTransfers()) { defaultTransferRequests.add(transferProfile); // Set mode-specific maxTransferDuration, if it is set in the build config. Duration maxTransferDuration = transferParameters.maxTransferDuration(); diff --git a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index d7ed602aef5..f9efbb8faa4 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -293,7 +293,7 @@ When set to true (it is false by default), the elevation module will include the "Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph." ) .asDuration(Duration.ofMinutes(30)); - transferParametersForMode = TransferConfig.map(root, "transfers"); + transferParametersForMode = TransferConfig.map(root, "transferParameters"); maxStopToShapeSnapDistance = root .of("maxStopToShapeSnapDistance") diff --git a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java index 7d75aecf89c..c18200793df 100644 --- a/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java +++ b/application/src/test/java/org/opentripplanner/graph_builder/module/DirectTransferGeneratorTest.java @@ -466,19 +466,12 @@ public void testBikeRequestWithPatternsAndWithCarsAllowedPatternsWithoutCarInTra graph.hasStreets = true; var timetableRepository = otpModel.timetableRepository(); - TransferParameters.Builder transferParametersBuilder = new TransferParameters.Builder(); - transferParametersBuilder.withCarsAllowedStopMaxTransferDuration(Duration.ofSeconds(120)); - transferParametersBuilder.withDisableDefaultTransfers(true); - Map transferParametersForMode = new HashMap<>(); - transferParametersForMode.put(StreetMode.CAR, transferParametersBuilder.build()); - new DirectTransferGenerator( graph, timetableRepository, DataImportIssueStore.NOOP, Duration.ofSeconds(30), - transferRequests, - transferParametersForMode + transferRequests ) .buildGraph(); diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index 80bd08e8e0c..ea0ef0c5855 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -90,8 +90,8 @@ Sections follow that describe particular settings in more depth. | osmDefaults | `object` | Default properties for OpenStreetMap feeds. | *Optional* | | 2.2 | |    [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 | +| [transferParameters](#transferParameters) | `enum map of object` | Configures mode-specific properties for transfer calculations. | *Optional* | | 2.7 | | [transferRequests](RouteRequest.md) | `object[]` | Routing requests to use for pre-calculating stop-to-stop transfers. | *Optional* | | 2.1 | -| [transfers](#transfers) | `enum map of object` | Configures mode-specific properties for transfer calculations. | *Optional* | | 2.7 | | [transitFeeds](#transitFeeds) | `object[]` | Scan for transit data files | *Optional* | | 2.2 | |    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | |       type = "gtfs" | `enum` | The feed input format. | *Required* | | 2.2 | @@ -953,7 +953,7 @@ The named set of mapping rules applied when parsing OSM tags. Overrides the valu The named set of mapping rules applied when parsing OSM tags. -

transfers

+

transferParameters

**Since version:** `2.7` ∙ **Type:** `enum map of object` ∙ **Cardinality:** `Optional` **Path:** / From 4f84804a493b8f267a69d8ab7dfcd7a804dc073e Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 7 Jan 2025 11:57:26 +0200 Subject: [PATCH 079/195] Change wording of documentation. --- .../config/buildconfig/TransferParametersMapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java index d14858d79c4..8a65ccdf712 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java @@ -12,7 +12,7 @@ public static TransferParameters map(NodeAdapter c) { builder.withMaxTransferDuration( c .of("maxTransferDuration") - .summary("This overwrites the `maxTransferDuration` for the given mode.") + .summary("This overwrites the default `maxTransferDuration` for the given mode.") .since(V2_7) .asDuration(TransferParameters.DEFAULT_MAX_TRANSFER_DURATION) ); @@ -20,7 +20,7 @@ public static TransferParameters map(NodeAdapter c) { c .of("carsAllowedStopMaxTransferDuration") .summary( - "This is used for specifying a `maxTransferDuration` value to use with transfers between stops that have trips with cars." + "This is used for specifying a `maxTransferDuration` value to use with transfers between stops which are visited by trips that allow cars." ) .description( """ From 4d599a2603513521763f76d572e013492d75d223 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 7 Jan 2025 13:11:40 +0200 Subject: [PATCH 080/195] Add comments and refactor transfer generation. --- .../module/DirectTransferGenerator.java | 71 ++++++++----------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index f173c52d6a0..c9b6c04bfcc 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -4,14 +4,12 @@ import com.google.common.collect.Multimaps; import java.time.Duration; import java.util.ArrayList; -import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.graph_builder.issues.StopNotLinkedForTransfers; @@ -27,7 +25,6 @@ import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.vertex.TransitStopVertex; -import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.service.DefaultTransitService; @@ -56,6 +53,9 @@ public class DirectTransferGenerator implements GraphBuilderModule { private final TimetableRepository timetableRepository; private final DataImportIssueStore issueStore; + /** + * Constructor used in tests. This initializes transferParametersForMode as an empty map. + */ public DirectTransferGenerator( Graph graph, TimetableRepository timetableRepository, @@ -68,7 +68,7 @@ public DirectTransferGenerator( this.issueStore = issueStore; this.defaultMaxTransferDuration = defaultMaxTransferDuration; this.transferRequests = transferRequests; - this.transferParametersForMode = Collections.emptyMap(); + this.transferParametersForMode = Map.of(); } public DirectTransferGenerator( @@ -155,18 +155,7 @@ public void buildGraph() { if (sd.stop.transfersNotAllowed()) { continue; } - TransferKey transferKey = new TransferKey(stop, sd.stop, sd.edges); - PathTransfer pathTransfer = distinctTransfers.get(transferKey); - if (pathTransfer == null) { - // If the PathTransfer can't be found, it is created. - distinctTransfers.put( - transferKey, - new PathTransfer(stop, sd.stop, sd.distance, sd.edges, EnumSet.of(mode)) - ); - } else { - // If the PathTransfer is found, a new PathTransfer with the added mode is created. - distinctTransfers.put(transferKey, pathTransfer.withAddedMode(mode)); - } + createPathTransfer(stop, sd.stop, sd, distinctTransfers, mode); } } // Calculate flex transfers if flex routing is enabled. @@ -187,18 +176,7 @@ public void buildGraph() { continue; } // The TransferKey and PathTransfer are created differently for flex routing. - TransferKey transferKey = new TransferKey(sd.stop, stop, sd.edges); - PathTransfer pathTransfer = distinctTransfers.get(transferKey); - if (pathTransfer == null) { - // If the PathTransfer can't be found, it is created. - distinctTransfers.put( - transferKey, - new PathTransfer(sd.stop, stop, sd.distance, sd.edges, EnumSet.of(mode)) - ); - } else { - // If the PathTransfer is found, a new PathTransfer with the added mode is created. - distinctTransfers.put(transferKey, pathTransfer.withAddedMode(mode)); - } + createPathTransfer(sd.stop, stop, sd, distinctTransfers, mode); } } // Calculate transfers between stops that are visited by trips that allow cars, if configured. @@ -220,18 +198,7 @@ public void buildGraph() { if (!carsAllowedStops.contains(sd.stop)) { continue; } - TransferKey transferKey = new TransferKey(stop, sd.stop, sd.edges); - PathTransfer pathTransfer = distinctTransfers.get(transferKey); - if (pathTransfer == null) { - // If the PathTransfer can't be found, it is created. - distinctTransfers.put( - transferKey, - new PathTransfer(stop, sd.stop, sd.distance, sd.edges, EnumSet.of(mode)) - ); - } else { - // If the PathTransfer is found, a new PathTransfer with the added mode is created. - distinctTransfers.put(transferKey, pathTransfer.withAddedMode(mode)); - } + createPathTransfer(stop, sd.stop, sd, distinctTransfers, mode); } } } @@ -302,6 +269,30 @@ private NearbyStopFinder createNearbyStopFinder(Duration radiusByDuration) { } } + private void createPathTransfer( + StopLocation from, + StopLocation to, + NearbyStop sd, + Map distinctTransfers, + StreetMode mode + ) { + TransferKey transferKey = new TransferKey(from, to, sd.edges); + PathTransfer pathTransfer = distinctTransfers.get(transferKey); + if (pathTransfer == null) { + // If the PathTransfer can't be found, it is created. + distinctTransfers.put( + transferKey, + new PathTransfer(from, to, sd.distance, sd.edges, EnumSet.of(mode)) + ); + } else { + // If the PathTransfer is found, a new PathTransfer with the added mode is created. + distinctTransfers.put(transferKey, pathTransfer.withAddedMode(mode)); + } + } + + /** + * This method parses the given transfer parameters into a transfer configuration and checks for invalid input. + */ private TransferConfiguration parseTransferParameters(NearbyStopFinder nearbyStopFinder) { List defaultTransferRequests = new ArrayList<>(); List carsAllowedStopTransferRequests = new ArrayList<>(); From 2eb0912beadf2fdb07afd0db2b6248e56d9a6625 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 7 Jan 2025 13:25:39 +0200 Subject: [PATCH 081/195] Rename transfers to transferParameters in documentation. --- .../standalone/config/buildconfig/TransferConfig.java | 2 +- doc/user/BuildConfiguration.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java index ce6a87f21e7..39683e16c18 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java @@ -25,7 +25,7 @@ public static Map map(NodeAdapter root, String p ```JSON // build-config.json { - "transfers": { + "transferParameters": { "CAR": { "disableDefaultTransfers": true, "carsAllowedStopMaxTransferDuration": "3h" diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index ea0ef0c5855..50c1dbab21a 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -969,7 +969,7 @@ To configure mode-specific parameters, the modes should also be used in the `tra ```JSON // build-config.json { - "transfers": { + "transferParameters": { "CAR": { "disableDefaultTransfers": true, "carsAllowedStopMaxTransferDuration": "3h" From 7fb3810535dfcf772036206d99ca64d775b95bc9 Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 5 Nov 2024 16:13:33 +0100 Subject: [PATCH 082/195] add currentFuelPercent to VehicleRentalVehicle and gtfs graphql API --- .../apis/gtfs/datafetchers/RentalVehicleImpl.java | 5 +++++ .../apis/gtfs/generated/GraphQLDataFetchers.java | 2 ++ .../service/vehiclerental/model/VehicleRentalVehicle.java | 5 +++++ .../datasources/GbfsFreeVehicleStatusMapper.java | 1 + .../org/opentripplanner/apis/gtfs/schema.graphqls | 2 ++ .../model/TestFreeFloatingRentalVehicleBuilder.java | 8 ++++++++ .../apis/gtfs/expectations/rental-vehicle.json | 3 ++- .../apis/gtfs/queries/rental-vehicle.graphql | 1 + 8 files changed, 26 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java index c4fb92c0ef4..5dec579e2e9 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java @@ -16,6 +16,11 @@ public DataFetcher allowPickupNow() { return environment -> getSource(environment).allowPickupNow(); } + @Override + public DataFetcher currentFuelPercent() { + return environment -> getSource(environment).getCurrentFuelPercent(); + } + @Override public DataFetcher id() { return environment -> diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 9c95ea65e91..b2391accd64 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -897,6 +897,8 @@ public interface GraphQLRentalPlace extends TypeResolver {} public interface GraphQLRentalVehicle { public DataFetcher allowPickupNow(); + public DataFetcher currentFuelPercent(); + public DataFetcher id(); public DataFetcher lat(); diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java index 042e608c88f..2838ec43ea1 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java @@ -25,6 +25,7 @@ public class VehicleRentalVehicle implements VehicleRentalPlace { public Double currentRangeMeters; public VehicleRentalStation station; public String pricingPlanId; + public Double currentFuelPercent; @Override public FeedScopedId getId() { @@ -133,4 +134,8 @@ public VehicleRentalStationUris getRentalUris() { public VehicleRentalSystem getVehicleRentalSystem() { return system; } + + public Double getCurrentFuelPercent() { + return currentFuelPercent; + } } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index 959521e017e..873bff1f8ff 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -61,6 +61,7 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { String webUri = rentalUris.getWeb(); rentalVehicle.rentalUris = new VehicleRentalStationUris(androidUri, iosUri, webUri); } + rentalVehicle.currentFuelPercent = vehicle.getCurrentFuelPercent(); return rentalVehicle; } else { 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..a57631df69c 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1874,6 +1874,8 @@ type RealTimeEstimate { type RentalVehicle implements Node & PlaceInterface { "If true, vehicle is currently available for renting." allowPickupNow: Boolean + "Current fuel percentage of the free floating vehicle. Value expressed from 0 to 1" + currentFuelPercent: Float "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." id: ID! "Latitude of the vehicle (WGS 84)" diff --git a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java index a9b2398f686..c0787b73493 100644 --- a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java +++ b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java @@ -9,9 +9,11 @@ public class TestFreeFloatingRentalVehicleBuilder { public static final String NETWORK_1 = "Network-1"; public static final double DEFAULT_LATITUDE = 47.520; public static final double DEFAULT_LONGITUDE = 19.01; + public static final double DEFAULT_CURRENT_FUEL_PERCENT = 0.5; private double latitude = DEFAULT_LATITUDE; private double longitude = DEFAULT_LONGITUDE; + private double currentFuelPercent = DEFAULT_CURRENT_FUEL_PERCENT; private VehicleRentalSystem system = null; private RentalVehicleType vehicleType = RentalVehicleType.getDefaultType(NETWORK_1); @@ -30,6 +32,11 @@ public TestFreeFloatingRentalVehicleBuilder withLongitude(double longitude) { return this; } + public TestFreeFloatingRentalVehicleBuilder withCurrentFuelPercent(double currentFuelPercent) { + this.currentFuelPercent = currentFuelPercent; + return this; + } + public TestFreeFloatingRentalVehicleBuilder withSystem(String id, String url) { this.system = new VehicleRentalSystem( @@ -85,6 +92,7 @@ public VehicleRentalVehicle build() { vehicle.longitude = longitude; vehicle.vehicleType = vehicleType; vehicle.system = system; + vehicle.currentFuelPercent = currentFuelPercent; return vehicle; } } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json index bcff74d0413..6036df5c12b 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json @@ -15,7 +15,8 @@ "rentalNetwork": { "networkId": "Network-1", "url": "https://foo.bar" - } + }, + "currentFuelPercent": 0.5 } } } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql index 9a912781c56..6460a11d801 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql @@ -19,5 +19,6 @@ networkId url } + currentFuelPercent } } From b61c9f33295edc525dea881646414ac8899b3530 Mon Sep 17 00:00:00 2001 From: JustCris Date: Fri, 22 Nov 2024 16:34:03 +0100 Subject: [PATCH 083/195] add currentRangeMeters to VehicleRentalVehicle and gtfs graphql API --- .../apis/gtfs/datafetchers/RentalVehicleImpl.java | 5 +++++ .../apis/gtfs/generated/GraphQLDataFetchers.java | 2 ++ .../service/vehiclerental/model/VehicleRentalVehicle.java | 4 ++++ .../datasources/GbfsFreeVehicleStatusMapper.java | 2 +- .../org/opentripplanner/apis/gtfs/schema.graphqls | 4 +++- .../model/TestFreeFloatingRentalVehicleBuilder.java | 8 ++++++++ .../apis/gtfs/expectations/rental-vehicle.json | 3 ++- .../apis/gtfs/queries/rental-vehicle.graphql | 1 + 8 files changed, 26 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java index 5dec579e2e9..64cff3b44fb 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java @@ -21,6 +21,11 @@ public DataFetcher currentFuelPercent() { return environment -> getSource(environment).getCurrentFuelPercent(); } + @Override + public DataFetcher currentRangeMeters() { + return environment -> getSource(environment).getCurrentRangeMeters(); + } + @Override public DataFetcher id() { return environment -> diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index b2391accd64..b1d2f32ffda 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -899,6 +899,8 @@ public interface GraphQLRentalVehicle { public DataFetcher currentFuelPercent(); + public DataFetcher currentRangeMeters(); + public DataFetcher id(); public DataFetcher lat(); diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java index 2838ec43ea1..ee5316f0526 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java @@ -138,4 +138,8 @@ public VehicleRentalSystem getVehicleRentalSystem() { public Double getCurrentFuelPercent() { return currentFuelPercent; } + + public Double getCurrentRangeMeters() { + return currentRangeMeters; + } } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index 873bff1f8ff..f3c60307ef9 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -53,6 +53,7 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { ? Instant.ofEpochSecond((long) (double) vehicle.getLastReported()) : null; rentalVehicle.currentRangeMeters = vehicle.getCurrentRangeMeters(); + rentalVehicle.currentFuelPercent = vehicle.getCurrentFuelPercent(); rentalVehicle.pricingPlanId = vehicle.getPricingPlanId(); GBFSRentalUris rentalUris = vehicle.getRentalUris(); if (rentalUris != null) { @@ -61,7 +62,6 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { String webUri = rentalUris.getWeb(); rentalVehicle.rentalUris = new VehicleRentalStationUris(androidUri, iosUri, webUri); } - rentalVehicle.currentFuelPercent = vehicle.getCurrentFuelPercent(); return rentalVehicle; } else { 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 a57631df69c..aa42cd6e7fa 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1874,8 +1874,10 @@ type RealTimeEstimate { type RentalVehicle implements Node & PlaceInterface { "If true, vehicle is currently available for renting." allowPickupNow: Boolean - "Current fuel percentage of the free floating vehicle. Value expressed from 0 to 1" + "Fuel or battery power remaining in the vehicle. Expressed from 0 to 1." currentFuelPercent: Float + "Range in meters that the vehicle can travel with the vehicle's current charge or fuel." + currentRangeMeters: Float "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." id: ID! "Latitude of the vehicle (WGS 84)" diff --git a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java index c0787b73493..1a54b24bcf2 100644 --- a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java +++ b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java @@ -10,10 +10,12 @@ public class TestFreeFloatingRentalVehicleBuilder { public static final double DEFAULT_LATITUDE = 47.520; public static final double DEFAULT_LONGITUDE = 19.01; public static final double DEFAULT_CURRENT_FUEL_PERCENT = 0.5; + public static final double DEFAULT_CURRENT_RANGE_METERS = 5500; private double latitude = DEFAULT_LATITUDE; private double longitude = DEFAULT_LONGITUDE; private double currentFuelPercent = DEFAULT_CURRENT_FUEL_PERCENT; + private double currentRangeMeters = DEFAULT_CURRENT_RANGE_METERS; private VehicleRentalSystem system = null; private RentalVehicleType vehicleType = RentalVehicleType.getDefaultType(NETWORK_1); @@ -37,6 +39,11 @@ public TestFreeFloatingRentalVehicleBuilder withCurrentFuelPercent(double curren return this; } + public TestFreeFloatingRentalVehicleBuilder withCurrentRangeMeters(double currentRangeMeters) { + this.currentRangeMeters = currentRangeMeters; + return this; + } + public TestFreeFloatingRentalVehicleBuilder withSystem(String id, String url) { this.system = new VehicleRentalSystem( @@ -93,6 +100,7 @@ public VehicleRentalVehicle build() { vehicle.vehicleType = vehicleType; vehicle.system = system; vehicle.currentFuelPercent = currentFuelPercent; + vehicle.currentRangeMeters = currentRangeMeters; return vehicle; } } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json index 6036df5c12b..e8e532bedcc 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json @@ -16,7 +16,8 @@ "networkId": "Network-1", "url": "https://foo.bar" }, - "currentFuelPercent": 0.5 + "currentFuelPercent": 0.5, + "currentRangeMeters": 5500.0 } } } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql index 6460a11d801..5fd8da8c5fd 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql @@ -20,5 +20,6 @@ url } currentFuelPercent + currentRangeMeters } } From 1fa55b06f4cf11468fd39a45838e0e05fd68bb8d Mon Sep 17 00:00:00 2001 From: JustCris Date: Fri, 22 Nov 2024 17:18:29 +0100 Subject: [PATCH 084/195] fix comment typo --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 aa42cd6e7fa..3f4b8984f14 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1876,7 +1876,7 @@ type RentalVehicle implements Node & PlaceInterface { allowPickupNow: Boolean "Fuel or battery power remaining in the vehicle. Expressed from 0 to 1." currentFuelPercent: Float - "Range in meters that the vehicle can travel with the vehicle's current charge or fuel." + "Range in meters that the vehicle can travel with the current charge or fuel." currentRangeMeters: Float "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." id: ID! From 4686f7cf9752a95507f77faad7aa7113adf0c6da Mon Sep 17 00:00:00 2001 From: JustCris Date: Mon, 2 Dec 2024 16:21:40 +0100 Subject: [PATCH 085/195] use Distance class for currentRangeMeters --- .../ext/fares/impl/GtfsFaresV2ServiceTest.java | 2 +- .../org/opentripplanner/ext/fares/model/FareDistance.java | 2 ++ .../apis/gtfs/datafetchers/RentalVehicleImpl.java | 5 ++++- .../org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java | 2 +- .../service/vehiclerental/model/VehicleRentalVehicle.java | 5 +++-- .../org/opentripplanner/transit/model/basic}/Distance.java | 2 +- .../datasources/GbfsFreeVehicleStatusMapper.java | 5 ++++- .../opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java | 2 +- .../model/TestFreeFloatingRentalVehicleBuilder.java | 3 ++- 9 files changed, 19 insertions(+), 9 deletions(-) rename application/src/{ext/java/org/opentripplanner/ext/fares/model => main/java/org/opentripplanner/transit/model/basic}/Distance.java (96%) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/GtfsFaresV2ServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/GtfsFaresV2ServiceTest.java index 19b55489778..a7b2694e2e8 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/GtfsFaresV2ServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/GtfsFaresV2ServiceTest.java @@ -12,7 +12,7 @@ import java.util.Set; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.opentripplanner.ext.fares.model.Distance; +import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.ext.fares.model.FareDistance; import org.opentripplanner.ext.fares.model.FareDistance.LinearDistance; import org.opentripplanner.ext.fares.model.FareLegRule; diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/model/FareDistance.java b/application/src/ext/java/org/opentripplanner/ext/fares/model/FareDistance.java index 9c18e3947b3..bbbc5e64426 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/model/FareDistance.java +++ b/application/src/ext/java/org/opentripplanner/ext/fares/model/FareDistance.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.fares.model; +import org.opentripplanner.transit.model.basic.Distance; + /** Represents a distance metric used in distance-based fare computation*/ public sealed interface FareDistance { /** Represents the number of stops as a distance metric in fare computation */ diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java index 64cff3b44fb..4d1787219b4 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java @@ -23,7 +23,10 @@ public DataFetcher currentFuelPercent() { @Override public DataFetcher currentRangeMeters() { - return environment -> getSource(environment).getCurrentRangeMeters(); + return environment -> + getSource(environment).getCurrentRangeMeters() != null + ? getSource(environment).getCurrentRangeMeters().toMeters() + : null; } @Override diff --git a/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java b/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java index 2218be9cf30..b67b4944cae 100644 --- a/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java +++ b/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java @@ -4,7 +4,7 @@ import java.util.Collection; import java.util.Objects; -import org.opentripplanner.ext.fares.model.Distance; +import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.ext.fares.model.FareDistance; import org.opentripplanner.ext.fares.model.FareLegRule; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java index ee5316f0526..b8161ed8fe9 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java @@ -4,6 +4,7 @@ import java.util.Set; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.street.model.RentalFormFactor; +import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.framework.FeedScopedId; /** @@ -22,7 +23,7 @@ public class VehicleRentalVehicle implements VehicleRentalPlace { public boolean isReserved = false; public boolean isDisabled = false; public Instant lastReported; - public Double currentRangeMeters; + public Distance currentRangeMeters; public VehicleRentalStation station; public String pricingPlanId; public Double currentFuelPercent; @@ -139,7 +140,7 @@ public Double getCurrentFuelPercent() { return currentFuelPercent; } - public Double getCurrentRangeMeters() { + public Distance getCurrentRangeMeters() { return currentRangeMeters; } } diff --git a/application/src/ext/java/org/opentripplanner/ext/fares/model/Distance.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java similarity index 96% rename from application/src/ext/java/org/opentripplanner/ext/fares/model/Distance.java rename to application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java index f30712d4cad..ce2654b0fbb 100644 --- a/application/src/ext/java/org/opentripplanner/ext/fares/model/Distance.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java @@ -1,4 +1,4 @@ -package org.opentripplanner.ext.fares.model; +package org.opentripplanner.transit.model.basic; import org.opentripplanner.utils.tostring.ValueObjectToStringBuilder; diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index f3c60307ef9..975ce4cebd1 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -13,6 +13,7 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; +import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.framework.FeedScopedId; public class GbfsFreeVehicleStatusMapper { @@ -52,8 +53,10 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { vehicle.getLastReported() != null ? Instant.ofEpochSecond((long) (double) vehicle.getLastReported()) : null; - rentalVehicle.currentRangeMeters = vehicle.getCurrentRangeMeters(); rentalVehicle.currentFuelPercent = vehicle.getCurrentFuelPercent(); + Double currentRangeMeters = vehicle.getCurrentRangeMeters(); + rentalVehicle.currentRangeMeters = + currentRangeMeters != null ? Distance.ofMeters(currentRangeMeters) : null; rentalVehicle.pricingPlanId = vehicle.getPricingPlanId(); GBFSRentalUris rentalUris = vehicle.getRentalUris(); if (rentalUris != null) { diff --git a/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java b/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java index dbe833a7aaa..4fdf17effb9 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java @@ -12,7 +12,7 @@ import org.onebusaway.gtfs.model.FareLegRule; import org.onebusaway.gtfs.model.FareMedium; import org.onebusaway.gtfs.model.FareProduct; -import org.opentripplanner.ext.fares.model.Distance; +import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.ext.fares.model.FareDistance; import org.opentripplanner.ext.fares.model.FareDistance.LinearDistance; import org.opentripplanner.ext.fares.model.FareDistance.Stops; diff --git a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java index 1a54b24bcf2..28d1a62503c 100644 --- a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java +++ b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java @@ -2,6 +2,7 @@ import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.street.model.RentalFormFactor; +import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.framework.FeedScopedId; public class TestFreeFloatingRentalVehicleBuilder { @@ -100,7 +101,7 @@ public VehicleRentalVehicle build() { vehicle.vehicleType = vehicleType; vehicle.system = system; vehicle.currentFuelPercent = currentFuelPercent; - vehicle.currentRangeMeters = currentRangeMeters; + vehicle.currentRangeMeters = Distance.ofMeters(currentRangeMeters); return vehicle; } } From e93c828b35f2b33bcd6b207d87978ceb69288667 Mon Sep 17 00:00:00 2001 From: JustCris Date: Mon, 2 Dec 2024 17:08:57 +0100 Subject: [PATCH 086/195] add greater and less than methods for Distance --- .../transit/model/basic/Distance.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java index ce2654b0fbb..86335cbdb64 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java @@ -1,5 +1,6 @@ package org.opentripplanner.transit.model.basic; +import java.util.Objects; import org.opentripplanner.utils.tostring.ValueObjectToStringBuilder; public class Distance { @@ -27,6 +28,20 @@ public double toMeters() { return this.meters; } + /** + * Is this distance greater than the one passed in + */ + public boolean greaterThan(Distance distance) { + return this.meters > distance.meters; + } + + /** + * Is this distance less than the one passed in + */ + public boolean lessThan(Distance distance) { + return this.meters < distance.meters; + } + @Override public boolean equals(Object other) { if (other instanceof Distance distance) { @@ -36,6 +51,11 @@ public boolean equals(Object other) { } } + @Override + public int hashCode() { + return Objects.hash(meters, 31); + } + @Override public String toString() { if (meters < METERS_PER_KM) { From 49c46829a71542f26e0ac58df8b3922c8e201519 Mon Sep 17 00:00:00 2001 From: JustCris Date: Mon, 2 Dec 2024 17:09:08 +0100 Subject: [PATCH 087/195] Distance class tests --- .../gtfs/mapping/FareLegRuleMapper.java | 2 +- .../gtfs/mapping/FareLegRuleMapperTest.java | 2 +- .../routing/core/DistanceTest.java | 47 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java diff --git a/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java b/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java index b67b4944cae..e24aa300406 100644 --- a/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java +++ b/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java @@ -4,10 +4,10 @@ import java.util.Collection; import java.util.Objects; -import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.ext.fares.model.FareDistance; import org.opentripplanner.ext.fares.model.FareLegRule; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.framework.FeedScopedId; public final class FareLegRuleMapper { diff --git a/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java b/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java index 4fdf17effb9..890da8264f4 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java @@ -12,11 +12,11 @@ import org.onebusaway.gtfs.model.FareLegRule; import org.onebusaway.gtfs.model.FareMedium; import org.onebusaway.gtfs.model.FareProduct; -import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.ext.fares.model.FareDistance; import org.opentripplanner.ext.fares.model.FareDistance.LinearDistance; import org.opentripplanner.ext.fares.model.FareDistance.Stops; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.transit.model.basic.Distance; class FareLegRuleMapperTest { diff --git a/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java b/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java new file mode 100644 index 00000000000..5ec561916d5 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java @@ -0,0 +1,47 @@ +package org.opentripplanner.routing.core; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model.basic.Distance; + +public class DistanceTest { + + private static final Distance oneThousandFiveHundredMeters = Distance.ofMeters(1500d); + private static final Distance onePointFiveKilometers = Distance.ofKilometers(1.5d); + private static final Distance twoKilometers = Distance.ofKilometers(2d); + private static final Distance oneHundredMeters = Distance.ofMeters(100d); + private static final Distance pointOneKilometer = Distance.ofKilometers(0.1d); + private static final Distance oneHundredPointFiveMeters = Distance.ofMeters(100.5d); + + @Test + void equals() { + assertEquals(oneThousandFiveHundredMeters, onePointFiveKilometers); + assertEquals(pointOneKilometer, oneHundredMeters); + assertNotEquals(oneHundredPointFiveMeters, oneHundredMeters); + assertNotEquals(twoKilometers, onePointFiveKilometers); + } + + @Test + void greaterThan() { + assertTrue(oneHundredPointFiveMeters.greaterThan(oneHundredMeters)); + assertTrue(twoKilometers.greaterThan(oneThousandFiveHundredMeters)); + assertFalse(oneThousandFiveHundredMeters.greaterThan(onePointFiveKilometers)); + } + + @Test + void lessThan() { + assertTrue(oneThousandFiveHundredMeters.lessThan(twoKilometers)); + assertTrue(pointOneKilometer.lessThan(oneHundredPointFiveMeters)); + assertFalse(oneHundredPointFiveMeters.lessThan(oneHundredMeters)); + } + + @Test + void equalHashCode() { + assertEquals(Distance.ofMeters(5d).hashCode(), Distance.ofMeters(5d).hashCode()); + assertNotEquals(Distance.ofMeters(5d).hashCode(), Double.valueOf(5d).hashCode()); + } +} From 54292e2f977735f6e52e4758285a8516750d2e3b Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 3 Dec 2024 12:52:13 +0100 Subject: [PATCH 088/195] represent currentRangeMeters with integer type --- .../gtfs/datafetchers/RentalVehicleImpl.java | 6 ++--- .../gtfs/generated/GraphQLDataFetchers.java | 2 +- .../model/VehicleRentalVehicle.java | 16 +++++++++++-- .../transit/model/basic/Distance.java | 4 ++-- .../GbfsFreeVehicleStatusMapper.java | 4 +--- .../opentripplanner/apis/gtfs/schema.graphqls | 2 +- .../apis/gtfs/GraphQLIntegrationTest.java | 14 ++++++++--- .../TestFreeFloatingRentalVehicleBuilder.java | 20 ++++++++++------ .../gtfs/expectations/rental-vehicle.json | 2 +- .../expectations/vehicle-rentals-bybbox.json | 24 ++++++++++++++++++- .../queries/vehicle-rentals-bybbox.graphql | 2 ++ 11 files changed, 71 insertions(+), 25 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java index 4d1787219b4..2ae08e5e236 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java @@ -22,11 +22,9 @@ public DataFetcher currentFuelPercent() { } @Override - public DataFetcher currentRangeMeters() { + public DataFetcher currentRangeMeters() { return environment -> - getSource(environment).getCurrentRangeMeters() != null - ? getSource(environment).getCurrentRangeMeters().toMeters() - : null; + getSource(environment).getCurrentRangeMeters(); } @Override diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index b1d2f32ffda..34226365f91 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -899,7 +899,7 @@ public interface GraphQLRentalVehicle { public DataFetcher currentFuelPercent(); - public DataFetcher currentRangeMeters(); + public DataFetcher currentRangeMeters(); public DataFetcher id(); diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java index b8161ed8fe9..00d5b2bb87a 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java @@ -2,6 +2,7 @@ import java.time.Instant; import java.util.Set; +import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.street.model.RentalFormFactor; import org.opentripplanner.transit.model.basic.Distance; @@ -140,7 +141,18 @@ public Double getCurrentFuelPercent() { return currentFuelPercent; } - public Distance getCurrentRangeMeters() { - return currentRangeMeters; + public Integer getCurrentRangeMeters() { + if (this.currentRangeMeters == null) { + return null; + } + return this.currentRangeMeters.toMeters(); + } + + public void setCurrentRangeMeters(@Nullable Double currentRangeMeters) { + if (currentRangeMeters != null) { + this.currentRangeMeters = Distance.ofMeters(currentRangeMeters); + } else { + this.currentRangeMeters = null; + } } } diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java index 86335cbdb64..d0d34ce600e 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java @@ -24,8 +24,8 @@ public static Distance ofKilometers(double value) { } /** Returns the distance in meters */ - public double toMeters() { - return this.meters; + public int toMeters() { + return (int) Math.round(this.meters); } /** diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index 975ce4cebd1..d926b955dd4 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -54,9 +54,7 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { ? Instant.ofEpochSecond((long) (double) vehicle.getLastReported()) : null; rentalVehicle.currentFuelPercent = vehicle.getCurrentFuelPercent(); - Double currentRangeMeters = vehicle.getCurrentRangeMeters(); - rentalVehicle.currentRangeMeters = - currentRangeMeters != null ? Distance.ofMeters(currentRangeMeters) : null; + rentalVehicle.setCurrentRangeMeters(vehicle.getCurrentRangeMeters()); rentalVehicle.pricingPlanId = vehicle.getPricingPlanId(); GBFSRentalUris rentalUris = vehicle.getRentalUris(); if (rentalUris != null) { 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 3f4b8984f14..ed7e960be00 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1877,7 +1877,7 @@ type RentalVehicle implements Node & PlaceInterface { "Fuel or battery power remaining in the vehicle. Expressed from 0 to 1." currentFuelPercent: Float "Range in meters that the vehicle can travel with the current charge or fuel." - currentRangeMeters: Float + currentRangeMeters: Int "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." id: ID! "Latitude of the vehicle (WGS 84)" 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..d10c56aee7f 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/GraphQLIntegrationTest.java @@ -133,10 +133,17 @@ class GraphQLIntegrationTest { .withSystem("Network-1", "https://foo.bar") .build(); - private static final VehicleRentalVehicle RENTAL_VEHICLE = new TestFreeFloatingRentalVehicleBuilder() + private static final VehicleRentalVehicle RENTAL_VEHICLE_1 = new TestFreeFloatingRentalVehicleBuilder() .withSystem("Network-1", "https://foo.bar") .build(); + private static final VehicleRentalVehicle RENTAL_VEHICLE_2 = new TestFreeFloatingRentalVehicleBuilder() + .withSystem("Network-2", "https://foo.bar.baz") + .withNetwork("Network-2") + .withCurrentRangeMeters(null) + .withCurrentFuelPercent(null) + .build(); + static final Graph GRAPH = new Graph(); static final Instant ALERT_START_TIME = OffsetDateTime @@ -344,7 +351,8 @@ public Set findRoutes(StopLocation stop) { DefaultVehicleRentalService defaultVehicleRentalService = new DefaultVehicleRentalService(); defaultVehicleRentalService.addVehicleRentalStation(VEHICLE_RENTAL_STATION); - defaultVehicleRentalService.addVehicleRentalStation(RENTAL_VEHICLE); + defaultVehicleRentalService.addVehicleRentalStation(RENTAL_VEHICLE_1); + defaultVehicleRentalService.addVehicleRentalStation(RENTAL_VEHICLE_2); context = new GraphQLRequestContext( @@ -511,7 +519,7 @@ public List findClosestPlaces( return List.of( new PlaceAtDistance(stop, 0), new PlaceAtDistance(VEHICLE_RENTAL_STATION, 30), - new PlaceAtDistance(RENTAL_VEHICLE, 50) + new PlaceAtDistance(RENTAL_VEHICLE_1, 50) ); } }; diff --git a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java index 28d1a62503c..a79229c45bc 100644 --- a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java +++ b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java @@ -11,13 +11,14 @@ public class TestFreeFloatingRentalVehicleBuilder { public static final double DEFAULT_LATITUDE = 47.520; public static final double DEFAULT_LONGITUDE = 19.01; public static final double DEFAULT_CURRENT_FUEL_PERCENT = 0.5; - public static final double DEFAULT_CURRENT_RANGE_METERS = 5500; + public static final double DEFAULT_CURRENT_RANGE_METERS = 5500.7; private double latitude = DEFAULT_LATITUDE; private double longitude = DEFAULT_LONGITUDE; - private double currentFuelPercent = DEFAULT_CURRENT_FUEL_PERCENT; - private double currentRangeMeters = DEFAULT_CURRENT_RANGE_METERS; + private Double currentFuelPercent = DEFAULT_CURRENT_FUEL_PERCENT; + private Double currentRangeMeters = DEFAULT_CURRENT_RANGE_METERS; private VehicleRentalSystem system = null; + private String network = NETWORK_1; private RentalVehicleType vehicleType = RentalVehicleType.getDefaultType(NETWORK_1); @@ -35,16 +36,21 @@ public TestFreeFloatingRentalVehicleBuilder withLongitude(double longitude) { return this; } - public TestFreeFloatingRentalVehicleBuilder withCurrentFuelPercent(double currentFuelPercent) { + public TestFreeFloatingRentalVehicleBuilder withCurrentFuelPercent(Double currentFuelPercent) { this.currentFuelPercent = currentFuelPercent; return this; } - public TestFreeFloatingRentalVehicleBuilder withCurrentRangeMeters(double currentRangeMeters) { + public TestFreeFloatingRentalVehicleBuilder withCurrentRangeMeters(Double currentRangeMeters) { this.currentRangeMeters = currentRangeMeters; return this; } + public TestFreeFloatingRentalVehicleBuilder withNetwork(String network) { + this.network = network; + return this; + } + public TestFreeFloatingRentalVehicleBuilder withSystem(String id, String url) { this.system = new VehicleRentalSystem( @@ -94,14 +100,14 @@ private TestFreeFloatingRentalVehicleBuilder buildVehicleType(RentalFormFactor r public VehicleRentalVehicle build() { var vehicle = new VehicleRentalVehicle(); var stationName = "free-floating-" + vehicleType.formFactor.name().toLowerCase(); - vehicle.id = new FeedScopedId(NETWORK_1, stationName); + vehicle.id = new FeedScopedId(this.network, stationName); vehicle.name = new NonLocalizedString(stationName); vehicle.latitude = latitude; vehicle.longitude = longitude; vehicle.vehicleType = vehicleType; vehicle.system = system; vehicle.currentFuelPercent = currentFuelPercent; - vehicle.currentRangeMeters = Distance.ofMeters(currentRangeMeters); + vehicle.setCurrentRangeMeters(currentRangeMeters); return vehicle; } } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json index e8e532bedcc..f4070a0feff 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json @@ -17,7 +17,7 @@ "url": "https://foo.bar" }, "currentFuelPercent": 0.5, - "currentRangeMeters": 5500.0 + "currentRangeMeters": 5501 } } } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rentals-bybbox.json b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rentals-bybbox.json index d28e62f8d93..03d2a36e948 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rentals-bybbox.json +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rentals-bybbox.json @@ -76,7 +76,29 @@ "rentalNetwork": { "networkId": "Network-1", "url": "https://foo.bar" - } + }, + "currentFuelPercent": 0.5, + "currentRangeMeters": 5501 + }, + { + "__typename": "RentalVehicle", + "vehicleId": "Network-2:free-floating-bicycle", + "name": "free-floating-bicycle", + "allowPickupNow": true, + "lon": 19.01, + "lat": 47.52, + "rentalUris": null, + "operative": true, + "vehicleType": { + "formFactor": "BICYCLE", + "propulsionType": "HUMAN" + }, + "rentalNetwork": { + "networkId": "Network-2", + "url": "https://foo.bar.baz" + }, + "currentFuelPercent": null, + "currentRangeMeters": null } ] } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rentals-bybbox.graphql b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rentals-bybbox.graphql index 26209f427f9..adbe51299d3 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rentals-bybbox.graphql +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rentals-bybbox.graphql @@ -26,6 +26,8 @@ networkId url } + currentFuelPercent + currentRangeMeters } ... on VehicleRentalStation { stationId From 851c8b50e874e88d8fbbeb869a03a1975fb2ec63 Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 3 Dec 2024 14:46:20 +0100 Subject: [PATCH 089/195] format RentalVehicleImpl file --- .../apis/gtfs/datafetchers/RentalVehicleImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java index 2ae08e5e236..2faf9d71280 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java @@ -23,8 +23,7 @@ public DataFetcher currentFuelPercent() { @Override public DataFetcher currentRangeMeters() { - return environment -> - getSource(environment).getCurrentRangeMeters(); + return environment -> getSource(environment).getCurrentRangeMeters(); } @Override From d26799fe613ad6e8c8536fc5e3fd43f1e3aeabfb Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 3 Dec 2024 16:53:06 +0100 Subject: [PATCH 090/195] proper naming for static variables --- .../routing/core/DistanceTest.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java b/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java index 5ec561916d5..e990e111215 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java @@ -10,33 +10,33 @@ public class DistanceTest { - private static final Distance oneThousandFiveHundredMeters = Distance.ofMeters(1500d); - private static final Distance onePointFiveKilometers = Distance.ofKilometers(1.5d); - private static final Distance twoKilometers = Distance.ofKilometers(2d); - private static final Distance oneHundredMeters = Distance.ofMeters(100d); - private static final Distance pointOneKilometer = Distance.ofKilometers(0.1d); - private static final Distance oneHundredPointFiveMeters = Distance.ofMeters(100.5d); + private static final Distance ONE_THOUSAND_FIVE_HUNDRED_METERS = Distance.ofMeters(1500d); + private static final Distance ONE_POINT_FIVE_KILOMETERS = Distance.ofKilometers(1.5d); + private static final Distance TWO_KILOMETERS = Distance.ofKilometers(2d); + private static final Distance ONE_HUNDRED_METERS = Distance.ofMeters(100d); + private static final Distance POINT_ONE_KILOMETER = Distance.ofKilometers(0.1d); + private static final Distance ONE_HUNDRED_POINT_FIVE_METERS = Distance.ofMeters(100.5d); @Test void equals() { - assertEquals(oneThousandFiveHundredMeters, onePointFiveKilometers); - assertEquals(pointOneKilometer, oneHundredMeters); - assertNotEquals(oneHundredPointFiveMeters, oneHundredMeters); - assertNotEquals(twoKilometers, onePointFiveKilometers); + assertEquals(ONE_THOUSAND_FIVE_HUNDRED_METERS, ONE_POINT_FIVE_KILOMETERS); + assertEquals(POINT_ONE_KILOMETER, ONE_HUNDRED_METERS); + assertNotEquals(ONE_HUNDRED_POINT_FIVE_METERS, ONE_HUNDRED_METERS); + assertNotEquals(TWO_KILOMETERS, ONE_POINT_FIVE_KILOMETERS); } @Test void greaterThan() { - assertTrue(oneHundredPointFiveMeters.greaterThan(oneHundredMeters)); - assertTrue(twoKilometers.greaterThan(oneThousandFiveHundredMeters)); - assertFalse(oneThousandFiveHundredMeters.greaterThan(onePointFiveKilometers)); + assertTrue(ONE_HUNDRED_POINT_FIVE_METERS.greaterThan(ONE_HUNDRED_METERS)); + assertTrue(TWO_KILOMETERS.greaterThan(ONE_THOUSAND_FIVE_HUNDRED_METERS)); + assertFalse(ONE_THOUSAND_FIVE_HUNDRED_METERS.greaterThan(ONE_POINT_FIVE_KILOMETERS)); } @Test void lessThan() { - assertTrue(oneThousandFiveHundredMeters.lessThan(twoKilometers)); - assertTrue(pointOneKilometer.lessThan(oneHundredPointFiveMeters)); - assertFalse(oneHundredPointFiveMeters.lessThan(oneHundredMeters)); + assertTrue(ONE_THOUSAND_FIVE_HUNDRED_METERS.lessThan(TWO_KILOMETERS)); + assertTrue(POINT_ONE_KILOMETER.lessThan(ONE_HUNDRED_POINT_FIVE_METERS)); + assertFalse(ONE_HUNDRED_POINT_FIVE_METERS.lessThan(ONE_HUNDRED_METERS)); } @Test From e0bd36aaa23751349eae68a0792fe8bf00e81e78 Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 4 Dec 2024 15:15:56 +0100 Subject: [PATCH 091/195] use Ratio scalar for currentFuelPercent --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ed7e960be00..981b99295c5 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1875,7 +1875,7 @@ type RentalVehicle implements Node & PlaceInterface { "If true, vehicle is currently available for renting." allowPickupNow: Boolean "Fuel or battery power remaining in the vehicle. Expressed from 0 to 1." - currentFuelPercent: Float + currentFuelPercent: Ratio "Range in meters that the vehicle can travel with the current charge or fuel." currentRangeMeters: Int "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." From e9a572daf787c50552ed51b0f82bf0a4983db339 Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 4 Dec 2024 15:37:22 +0100 Subject: [PATCH 092/195] rename currentRangeMeters to currentRange --- .../gtfs/datafetchers/RentalVehicleImpl.java | 2 +- .../transmodel/model/stop/RentalVehicleType.java | 4 +--- .../model/VehicleRentalVehicle.java | 16 ++++++++-------- .../datasources/GbfsFreeVehicleStatusMapper.java | 3 +-- .../TestFreeFloatingRentalVehicleBuilder.java | 3 +-- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java index 2faf9d71280..ed8d2ff20d0 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java @@ -23,7 +23,7 @@ public DataFetcher currentFuelPercent() { @Override public DataFetcher currentRangeMeters() { - return environment -> getSource(environment).getCurrentRangeMeters(); + return environment -> getSource(environment).getCurrentRange(); } @Override diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java index e44639e09e7..9b78624b7ec 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java @@ -70,9 +70,7 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("currentRangeMeters") .type(Scalars.GraphQLFloat) - .dataFetcher(environment -> - ((VehicleRentalVehicle) environment.getSource()).currentRangeMeters - ) + .dataFetcher(environment -> ((VehicleRentalVehicle) environment.getSource()).currentRange) .build() ) .build(); diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java index 00d5b2bb87a..23c07e31cf1 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java @@ -24,7 +24,7 @@ public class VehicleRentalVehicle implements VehicleRentalPlace { public boolean isReserved = false; public boolean isDisabled = false; public Instant lastReported; - public Distance currentRangeMeters; + public Distance currentRange; public VehicleRentalStation station; public String pricingPlanId; public Double currentFuelPercent; @@ -141,18 +141,18 @@ public Double getCurrentFuelPercent() { return currentFuelPercent; } - public Integer getCurrentRangeMeters() { - if (this.currentRangeMeters == null) { + public Integer getCurrentRange() { + if (this.currentRange == null) { return null; } - return this.currentRangeMeters.toMeters(); + return this.currentRange.toMeters(); } - public void setCurrentRangeMeters(@Nullable Double currentRangeMeters) { - if (currentRangeMeters != null) { - this.currentRangeMeters = Distance.ofMeters(currentRangeMeters); + public void setCurrentRange(@Nullable Double currentRange) { + if (currentRange != null) { + this.currentRange = Distance.ofMeters(currentRange); } else { - this.currentRangeMeters = null; + this.currentRange = null; } } } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index d926b955dd4..2617b905b6a 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -13,7 +13,6 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; -import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.framework.FeedScopedId; public class GbfsFreeVehicleStatusMapper { @@ -54,7 +53,7 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { ? Instant.ofEpochSecond((long) (double) vehicle.getLastReported()) : null; rentalVehicle.currentFuelPercent = vehicle.getCurrentFuelPercent(); - rentalVehicle.setCurrentRangeMeters(vehicle.getCurrentRangeMeters()); + rentalVehicle.setCurrentRange(vehicle.getCurrentRangeMeters()); rentalVehicle.pricingPlanId = vehicle.getPricingPlanId(); GBFSRentalUris rentalUris = vehicle.getRentalUris(); if (rentalUris != null) { diff --git a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java index a79229c45bc..16208d1f41e 100644 --- a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java +++ b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java @@ -2,7 +2,6 @@ import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.street.model.RentalFormFactor; -import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.framework.FeedScopedId; public class TestFreeFloatingRentalVehicleBuilder { @@ -107,7 +106,7 @@ public VehicleRentalVehicle build() { vehicle.vehicleType = vehicleType; vehicle.system = system; vehicle.currentFuelPercent = currentFuelPercent; - vehicle.setCurrentRangeMeters(currentRangeMeters); + vehicle.setCurrentRange(currentRangeMeters); return vehicle; } } From 498d547f4f413bdf98376f5b8f78259b2e571d13 Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 4 Dec 2024 15:52:08 +0100 Subject: [PATCH 093/195] move conversion of Distance to-from meters to the api and gbfs mapping --- .../apis/gtfs/datafetchers/RentalVehicleImpl.java | 5 ++++- .../vehiclerental/model/VehicleRentalVehicle.java | 15 ++------------- .../datasources/GbfsFreeVehicleStatusMapper.java | 6 +++++- .../TestFreeFloatingRentalVehicleBuilder.java | 4 +++- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java index ed8d2ff20d0..8f04a58b9c1 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java @@ -23,7 +23,10 @@ public DataFetcher currentFuelPercent() { @Override public DataFetcher currentRangeMeters() { - return environment -> getSource(environment).getCurrentRange(); + return environment -> + getSource(environment).getCurrentRange() != null + ? getSource(environment).getCurrentRange().toMeters() + : null; } @Override diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java index 23c07e31cf1..9c2f62e3785 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java @@ -141,18 +141,7 @@ public Double getCurrentFuelPercent() { return currentFuelPercent; } - public Integer getCurrentRange() { - if (this.currentRange == null) { - return null; - } - return this.currentRange.toMeters(); - } - - public void setCurrentRange(@Nullable Double currentRange) { - if (currentRange != null) { - this.currentRange = Distance.ofMeters(currentRange); - } else { - this.currentRange = null; - } + public Distance getCurrentRange() { + return this.currentRange; } } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index 2617b905b6a..14833704309 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -13,6 +13,7 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; +import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.framework.FeedScopedId; public class GbfsFreeVehicleStatusMapper { @@ -53,7 +54,10 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { ? Instant.ofEpochSecond((long) (double) vehicle.getLastReported()) : null; rentalVehicle.currentFuelPercent = vehicle.getCurrentFuelPercent(); - rentalVehicle.setCurrentRange(vehicle.getCurrentRangeMeters()); + rentalVehicle.currentRange = + vehicle.getCurrentRangeMeters() != null + ? Distance.ofMeters(vehicle.getCurrentRangeMeters()) + : null; rentalVehicle.pricingPlanId = vehicle.getPricingPlanId(); GBFSRentalUris rentalUris = vehicle.getRentalUris(); if (rentalUris != null) { diff --git a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java index 16208d1f41e..35e349f4fad 100644 --- a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java +++ b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java @@ -2,6 +2,7 @@ import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.street.model.RentalFormFactor; +import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.framework.FeedScopedId; public class TestFreeFloatingRentalVehicleBuilder { @@ -106,7 +107,8 @@ public VehicleRentalVehicle build() { vehicle.vehicleType = vehicleType; vehicle.system = system; vehicle.currentFuelPercent = currentFuelPercent; - vehicle.setCurrentRange(currentRangeMeters); + vehicle.currentRange = + currentRangeMeters != null ? Distance.ofMeters(currentRangeMeters) : null; return vehicle; } } From eda75c888d5846fa401f8d81d69c29d933db9c88 Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 4 Dec 2024 17:03:56 +0100 Subject: [PATCH 094/195] remove unused code Distance class --- .../transit/model/basic/Distance.java | 14 -------------- .../routing/core/DistanceTest.java | 18 +----------------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java index d0d34ce600e..05613f8e3b0 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java @@ -28,20 +28,6 @@ public int toMeters() { return (int) Math.round(this.meters); } - /** - * Is this distance greater than the one passed in - */ - public boolean greaterThan(Distance distance) { - return this.meters > distance.meters; - } - - /** - * Is this distance less than the one passed in - */ - public boolean lessThan(Distance distance) { - return this.meters < distance.meters; - } - @Override public boolean equals(Object other) { if (other instanceof Distance distance) { diff --git a/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java b/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java index e990e111215..16ee7e93565 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java @@ -1,9 +1,7 @@ package org.opentripplanner.routing.core; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import org.opentripplanner.transit.model.basic.Distance; @@ -26,21 +24,7 @@ void equals() { } @Test - void greaterThan() { - assertTrue(ONE_HUNDRED_POINT_FIVE_METERS.greaterThan(ONE_HUNDRED_METERS)); - assertTrue(TWO_KILOMETERS.greaterThan(ONE_THOUSAND_FIVE_HUNDRED_METERS)); - assertFalse(ONE_THOUSAND_FIVE_HUNDRED_METERS.greaterThan(ONE_POINT_FIVE_KILOMETERS)); - } - - @Test - void lessThan() { - assertTrue(ONE_THOUSAND_FIVE_HUNDRED_METERS.lessThan(TWO_KILOMETERS)); - assertTrue(POINT_ONE_KILOMETER.lessThan(ONE_HUNDRED_POINT_FIVE_METERS)); - assertFalse(ONE_HUNDRED_POINT_FIVE_METERS.lessThan(ONE_HUNDRED_METERS)); - } - - @Test - void equalHashCode() { + void equalsHashCode() { assertEquals(Distance.ofMeters(5d).hashCode(), Distance.ofMeters(5d).hashCode()); assertNotEquals(Distance.ofMeters(5d).hashCode(), Double.valueOf(5d).hashCode()); } From 3e7bf5bd979f20491fc9797f452b057208440e54 Mon Sep 17 00:00:00 2001 From: JustCris Date: Mon, 9 Dec 2024 11:34:11 +0100 Subject: [PATCH 095/195] group range and percent in fuel type --- .../gtfs/datafetchers/RentalVehicleImpl.java | 13 ++---- .../gtfs/generated/GraphQLDataFetchers.java | 12 ++++-- .../apis/gtfs/generated/graphql-codegen.yml | 1 + .../model/stop/RentalVehicleType.java | 4 +- .../model/RentalVehicleFuel.java | 43 +++++++++++++++++++ .../model/VehicleRentalVehicle.java | 13 ++---- .../GbfsFreeVehicleStatusMapper.java | 8 ++-- .../opentripplanner/apis/gtfs/schema.graphqls | 14 ++++-- .../TestFreeFloatingRentalVehicleBuilder.java | 32 +++++++------- .../gtfs/expectations/rental-vehicle.json | 6 ++- .../expectations/vehicle-rentals-bybbox.json | 12 ++++-- .../apis/gtfs/queries/rental-vehicle.graphql | 6 ++- .../queries/vehicle-rentals-bybbox.graphql | 6 ++- 13 files changed, 114 insertions(+), 56 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java index 8f04a58b9c1..5697aa15a5f 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleImpl.java @@ -4,6 +4,7 @@ import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; +import org.opentripplanner.service.vehiclerental.model.RentalVehicleFuel; import org.opentripplanner.service.vehiclerental.model.RentalVehicleType; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; @@ -17,16 +18,8 @@ public DataFetcher allowPickupNow() { } @Override - public DataFetcher currentFuelPercent() { - return environment -> getSource(environment).getCurrentFuelPercent(); - } - - @Override - public DataFetcher currentRangeMeters() { - return environment -> - getSource(environment).getCurrentRange() != null - ? getSource(environment).getCurrentRange().toMeters() - : null; + public DataFetcher fuel() { + return environment -> getSource(environment).getFuel(); } @Override diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 34226365f91..091e0be59f1 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -58,6 +58,7 @@ import org.opentripplanner.service.vehicleparking.model.VehicleParkingSpaces; import org.opentripplanner.service.vehicleparking.model.VehicleParkingState; import org.opentripplanner.service.vehiclerental.model.RentalVehicleEntityCounts; +import org.opentripplanner.service.vehiclerental.model.RentalVehicleFuel; import org.opentripplanner.service.vehiclerental.model.RentalVehicleType; import org.opentripplanner.service.vehiclerental.model.RentalVehicleTypeCount; import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; @@ -897,9 +898,7 @@ public interface GraphQLRentalPlace extends TypeResolver {} public interface GraphQLRentalVehicle { public DataFetcher allowPickupNow(); - public DataFetcher currentFuelPercent(); - - public DataFetcher currentRangeMeters(); + public DataFetcher fuel(); public DataFetcher id(); @@ -928,6 +927,13 @@ public interface GraphQLRentalVehicleEntityCounts { public DataFetcher total(); } + /** Rental vehicle fuel represent the current status of the battery or fuel of a rental vehicle */ + public interface GraphQLRentalVehicleFuel { + public DataFetcher percent(); + + public DataFetcher range(); + } + public interface GraphQLRentalVehicleType { public DataFetcher formFactor(); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index a9bb87a6ea5..3272efa894f 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -134,4 +134,5 @@ config: CallRealTime: org.opentripplanner.apis.gtfs.model.CallRealTime#CallRealTime RentalPlace: org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace#VehicleRentalPlace CallSchedule: org.opentripplanner.apis.gtfs.model.CallSchedule#CallSchedule + RentalVehicleFuel: org.opentripplanner.service.vehiclerental.model.RentalVehicleFuel#RentalVehicleFuel diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java index 9b78624b7ec..8694fe0d95d 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java @@ -70,7 +70,9 @@ public static GraphQLObjectType create( .newFieldDefinition() .name("currentRangeMeters") .type(Scalars.GraphQLFloat) - .dataFetcher(environment -> ((VehicleRentalVehicle) environment.getSource()).currentRange) + .dataFetcher(environment -> + ((VehicleRentalVehicle) environment.getSource()).getFuel().getRange() + ) .build() ) .build(); diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java new file mode 100644 index 00000000000..b38f1ce0913 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java @@ -0,0 +1,43 @@ +package org.opentripplanner.service.vehiclerental.model; + +import javax.annotation.Nullable; +import org.opentripplanner.transit.model.basic.Distance; + +/** + * Contains information about the current battery or fuel status. + * See the GBFS + * vehicle_status specification for more details. + */ +public class RentalVehicleFuel { + + /** + * Current fuel percentage, expressed from 0 to 1. + *

+ * May be {@code null}. + */ + @Nullable + public final Double percent; + + /** + * Distance that the vehicle can travel with the current charge or fuel. + *

+ * May be {@code null}. + */ + @Nullable + public final Distance range; + + public RentalVehicleFuel(@Nullable Double fuelPercent, @Nullable Distance range) { + this.percent = fuelPercent; + this.range = range; + } + + @Nullable + public Double getPercent() { + return percent; + } + + @Nullable + public Integer getRange() { + return this.range != null ? this.range.toMeters() : null; + } +} diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java index 9c2f62e3785..446711bad36 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/VehicleRentalVehicle.java @@ -2,10 +2,8 @@ import java.time.Instant; import java.util.Set; -import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.street.model.RentalFormFactor; -import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.framework.FeedScopedId; /** @@ -24,10 +22,9 @@ public class VehicleRentalVehicle implements VehicleRentalPlace { public boolean isReserved = false; public boolean isDisabled = false; public Instant lastReported; - public Distance currentRange; public VehicleRentalStation station; public String pricingPlanId; - public Double currentFuelPercent; + public RentalVehicleFuel fuel; @Override public FeedScopedId getId() { @@ -137,11 +134,7 @@ public VehicleRentalSystem getVehicleRentalSystem() { return system; } - public Double getCurrentFuelPercent() { - return currentFuelPercent; - } - - public Distance getCurrentRange() { - return this.currentRange; + public RentalVehicleFuel getFuel() { + return fuel; } } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index 14833704309..11efe00cd8a 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -9,6 +9,7 @@ import org.mobilitydata.gbfs.v2_3.free_bike_status.GBFSBike; import org.mobilitydata.gbfs.v2_3.free_bike_status.GBFSRentalUris; import org.opentripplanner.framework.i18n.NonLocalizedString; +import org.opentripplanner.service.vehiclerental.model.RentalVehicleFuel; import org.opentripplanner.service.vehiclerental.model.RentalVehicleType; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris; import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; @@ -53,11 +54,12 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { vehicle.getLastReported() != null ? Instant.ofEpochSecond((long) (double) vehicle.getLastReported()) : null; - rentalVehicle.currentFuelPercent = vehicle.getCurrentFuelPercent(); - rentalVehicle.currentRange = + RentalVehicleFuel fuel = new RentalVehicleFuel( + vehicle.getCurrentFuelPercent(), vehicle.getCurrentRangeMeters() != null ? Distance.ofMeters(vehicle.getCurrentRangeMeters()) - : null; + : null + ); rentalVehicle.pricingPlanId = vehicle.getPricingPlanId(); GBFSRentalUris rentalUris = vehicle.getRentalUris(); if (rentalUris != null) { 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 981b99295c5..8825f0fbf54 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1874,10 +1874,8 @@ type RealTimeEstimate { type RentalVehicle implements Node & PlaceInterface { "If true, vehicle is currently available for renting." allowPickupNow: Boolean - "Fuel or battery power remaining in the vehicle. Expressed from 0 to 1." - currentFuelPercent: Ratio - "Range in meters that the vehicle can travel with the current charge or fuel." - currentRangeMeters: Int + "Fuel or battery status of the rental vehicle" + fuel: RentalVehicleFuel "Global object ID provided by Relay. This value can be used to refetch this object using **node** query." id: ID! "Latitude of the vehicle (WGS 84)" @@ -1907,6 +1905,14 @@ type RentalVehicleEntityCounts { total: Int! } +"Rental vehicle fuel represent the current status of the battery or fuel of a rental vehicle" +type RentalVehicleFuel { + "Fuel or battery power remaining in the vehicle. Expressed from 0 to 1." + percent: Ratio + "Range in meters that the vehicle can travel with the current charge or fuel." + range: Int +} + type RentalVehicleType { "The vehicle's general form factor" formFactor: FormFactor diff --git a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java index 35e349f4fad..845e8faed68 100644 --- a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java +++ b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java @@ -85,18 +85,6 @@ public TestFreeFloatingRentalVehicleBuilder withVehicleCar() { return buildVehicleType(RentalFormFactor.CAR); } - private TestFreeFloatingRentalVehicleBuilder buildVehicleType(RentalFormFactor rentalFormFactor) { - this.vehicleType = - new RentalVehicleType( - new FeedScopedId(TestFreeFloatingRentalVehicleBuilder.NETWORK_1, rentalFormFactor.name()), - rentalFormFactor.name(), - rentalFormFactor, - RentalVehicleType.PropulsionType.ELECTRIC, - 100000d - ); - return this; - } - public VehicleRentalVehicle build() { var vehicle = new VehicleRentalVehicle(); var stationName = "free-floating-" + vehicleType.formFactor.name().toLowerCase(); @@ -106,9 +94,23 @@ public VehicleRentalVehicle build() { vehicle.longitude = longitude; vehicle.vehicleType = vehicleType; vehicle.system = system; - vehicle.currentFuelPercent = currentFuelPercent; - vehicle.currentRange = - currentRangeMeters != null ? Distance.ofMeters(currentRangeMeters) : null; + vehicle.fuel = + new RentalVehicleFuel( + currentFuelPercent, + currentRangeMeters != null ? Distance.ofMeters(currentRangeMeters) : null + ); return vehicle; } + + private TestFreeFloatingRentalVehicleBuilder buildVehicleType(RentalFormFactor rentalFormFactor) { + this.vehicleType = + new RentalVehicleType( + new FeedScopedId(TestFreeFloatingRentalVehicleBuilder.NETWORK_1, rentalFormFactor.name()), + rentalFormFactor.name(), + rentalFormFactor, + RentalVehicleType.PropulsionType.ELECTRIC, + 100000d + ); + return this; + } } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json index f4070a0feff..7d739570a9d 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/rental-vehicle.json @@ -16,8 +16,10 @@ "networkId": "Network-1", "url": "https://foo.bar" }, - "currentFuelPercent": 0.5, - "currentRangeMeters": 5501 + "fuel": { + "percent": 0.5, + "range": 5501 + } } } } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rentals-bybbox.json b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rentals-bybbox.json index 03d2a36e948..10b7924d745 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rentals-bybbox.json +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/expectations/vehicle-rentals-bybbox.json @@ -77,8 +77,10 @@ "networkId": "Network-1", "url": "https://foo.bar" }, - "currentFuelPercent": 0.5, - "currentRangeMeters": 5501 + "fuel": { + "percent": 0.5, + "range": 5501 + } }, { "__typename": "RentalVehicle", @@ -97,8 +99,10 @@ "networkId": "Network-2", "url": "https://foo.bar.baz" }, - "currentFuelPercent": null, - "currentRangeMeters": null + "fuel": { + "percent": null, + "range": null + } } ] } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql index 5fd8da8c5fd..8f32632abc0 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/rental-vehicle.graphql @@ -19,7 +19,9 @@ networkId url } - currentFuelPercent - currentRangeMeters + fuel { + percent + range + } } } diff --git a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rentals-bybbox.graphql b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rentals-bybbox.graphql index adbe51299d3..55c71954692 100644 --- a/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rentals-bybbox.graphql +++ b/application/src/test/resources/org/opentripplanner/apis/gtfs/queries/vehicle-rentals-bybbox.graphql @@ -26,8 +26,10 @@ networkId url } - currentFuelPercent - currentRangeMeters + fuel { + percent + range + } } ... on VehicleRentalStation { stationId From 7d15f000bd37364270f35126bfd94ec831087ee7 Mon Sep 17 00:00:00 2001 From: JustCris Date: Mon, 9 Dec 2024 14:51:10 +0100 Subject: [PATCH 096/195] log warn if fuelPercent is invalid --- .../GbfsFreeVehicleStatusMapper.java | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index 11efe00cd8a..8ab3740542f 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -2,12 +2,14 @@ import static java.util.Objects.requireNonNullElse; +import graphql.schema.CoercingParseValueException; import java.time.Instant; import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; import org.mobilitydata.gbfs.v2_3.free_bike_status.GBFSBike; import org.mobilitydata.gbfs.v2_3.free_bike_status.GBFSRentalUris; +import org.opentripplanner.apis.gtfs.GraphQLScalars; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.service.vehiclerental.model.RentalVehicleFuel; import org.opentripplanner.service.vehiclerental.model.RentalVehicleType; @@ -16,9 +18,13 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class GbfsFreeVehicleStatusMapper { + private static final Logger LOG = LoggerFactory.getLogger(GbfsFreeVehicleStatusMapper.class); + private final VehicleRentalSystem system; private final Map vehicleTypes; @@ -54,12 +60,28 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { vehicle.getLastReported() != null ? Instant.ofEpochSecond((long) (double) vehicle.getLastReported()) : null; - RentalVehicleFuel fuel = new RentalVehicleFuel( - vehicle.getCurrentFuelPercent(), - vehicle.getCurrentRangeMeters() != null - ? Distance.ofMeters(vehicle.getCurrentRangeMeters()) - : null - ); + Double fuelPercent = null; + if (vehicle.getCurrentFuelPercent() != null) { + try { + fuelPercent = + (Double) GraphQLScalars.RATIO_SCALAR + .getCoercing() + .parseValue(vehicle.getCurrentFuelPercent()); + } catch (CoercingParseValueException e) { + LOG.warn( + "Current fuel percent: {} - {}", + vehicle.getCurrentFuelPercent(), + e.getMessage() + ); + } + } + rentalVehicle.fuel = + new RentalVehicleFuel( + fuelPercent, + vehicle.getCurrentRangeMeters() != null + ? Distance.ofMeters(vehicle.getCurrentRangeMeters()) + : null + ); rentalVehicle.pricingPlanId = vehicle.getPricingPlanId(); GBFSRentalUris rentalUris = vehicle.getRentalUris(); if (rentalUris != null) { From 9b9b585854445b9c048bfdfd0db714c59b5d2251 Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 11 Dec 2024 18:16:46 +0100 Subject: [PATCH 097/195] check currentRangeMeters validity in free rental vehicle --- .../transit/model/basic/Distance.java | 16 ++++++++++++++-- .../GbfsFreeVehicleStatusMapper.java | 17 ++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java index 05613f8e3b0..d0df42bb253 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java @@ -9,17 +9,29 @@ public class Distance { private final double meters; /** Returns a Distance object representing the given number of meters */ - public Distance(double value) { - this.meters = value; + public Distance(double distanceInMeters) { + if (distanceInMeters < 0) { + throw new IllegalArgumentException("Distance cannot be negative"); + } + + this.meters = distanceInMeters; } /** Returns a Distance object representing the given number of meters */ public static Distance ofMeters(double value) { + if (value < 0) { + throw new IllegalArgumentException("Distance cannot be negative"); + } + return new Distance(value); } /** Returns a Distance object representing the given number of kilometers */ public static Distance ofKilometers(double value) { + if (value < 0) { + throw new IllegalArgumentException("Distance cannot be negative"); + } + return new Distance(value * METERS_PER_KM); } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index 8ab3740542f..d5620bff52f 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -75,12 +75,23 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { ); } } + Distance rangeMeters = null; + try { + rangeMeters = vehicle.getCurrentRangeMeters() != null + ? Distance.ofMeters(vehicle.getCurrentRangeMeters()) + : null; + } catch (IllegalArgumentException e) { + LOG.warn(e.getMessage()); + // if the propulsion type has an engine current_range_meters is required + if (vehicle.getVehicleTypeId() != null + && vehicleTypes.get(vehicle.getVehicleTypeId()).propulsionType != RentalVehicleType.PropulsionType.HUMAN) { + return null; + } + } rentalVehicle.fuel = new RentalVehicleFuel( fuelPercent, - vehicle.getCurrentRangeMeters() != null - ? Distance.ofMeters(vehicle.getCurrentRangeMeters()) - : null + rangeMeters ); rentalVehicle.pricingPlanId = vehicle.getPricingPlanId(); GBFSRentalUris rentalUris = vehicle.getRentalUris(); From c90bdd46cd1077e3acf64bee124fcf0df23ab85d Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 11 Dec 2024 19:19:46 +0100 Subject: [PATCH 098/195] Ratio class --- .../transit/model/basic/Distance.java | 2 +- .../transit/model/basic/Ratio.java | 34 +++++++++++++++ .../routing/core/DistanceTest.java | 2 +- .../routing/core/RatioTest.java | 41 +++++++++++++++++++ 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java create mode 100644 application/src/test/java/org/opentripplanner/routing/core/RatioTest.java diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java index d0df42bb253..0b8061190be 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java @@ -51,7 +51,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(meters, 31); + return Objects.hash(meters, "Distance"); } @Override diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java new file mode 100644 index 00000000000..57276753e9d --- /dev/null +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java @@ -0,0 +1,34 @@ +package org.opentripplanner.transit.model.basic; + +import java.util.Objects; + +public class Ratio { + public final Double ratio; + + public Ratio(Double ratio) { + if (ratio < 0d || ratio > 1d) { + throw new IllegalArgumentException("Ratio must be in range [0,1]"); + } + + this.ratio = ratio; + } + + @Override + public boolean equals(Object other) { + if (other instanceof Ratio ratio) { + return ratio.ratio == this.ratio; + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.ratio, "Ratio"); + } + + @Override + public String toString() { + return this.ratio.toString(); + } +} diff --git a/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java b/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java index 16ee7e93565..2543eae70cd 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java @@ -24,7 +24,7 @@ void equals() { } @Test - void equalsHashCode() { + void testHashCode() { assertEquals(Distance.ofMeters(5d).hashCode(), Distance.ofMeters(5d).hashCode()); assertNotEquals(Distance.ofMeters(5d).hashCode(), Double.valueOf(5d).hashCode()); } diff --git a/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java b/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java new file mode 100644 index 00000000000..c434c4d54e7 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java @@ -0,0 +1,41 @@ +package org.opentripplanner.routing.core; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model.basic.Ratio; + +public class RatioTest { + private static final Double HALF = 0.5d; + private static final Double ZERO = 0d; + private static final Double ONE = 1d; + private static final Double TOO_HIGH = 1.1d; + private static final Double TOO_LOW = -1.1d; + + @Test + void validRatios() { + assertDoesNotThrow(() -> new Ratio(HALF)); + assertDoesNotThrow(() -> new Ratio(ZERO)); + assertDoesNotThrow(() -> new Ratio(ONE)); + } + + @Test + void invalidRatios() { + assertThrows(IllegalArgumentException.class ,() -> new Ratio(TOO_HIGH)); + assertThrows(IllegalArgumentException.class ,() -> new Ratio(TOO_LOW)); + } + + @Test + void testHashCode() { + Ratio half = new Ratio(HALF); + + Ratio half2 = new Ratio(HALF); + assertEquals(half.hashCode(), half2.hashCode()); + + Double halfDouble = 2d; + assertNotEquals(half.hashCode(), halfDouble); + } +} From 346449b0c8b4035d795b11fcb26a8b8707947f41 Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 17 Dec 2024 12:33:47 +0100 Subject: [PATCH 099/195] Ratio class for fuel percent validation --- .../model/RentalVehicleFuel.java | 7 +-- .../transit/model/basic/Distance.java | 2 +- .../transit/model/basic/Ratio.java | 2 +- .../GbfsFreeVehicleStatusMapper.java | 52 +++++++++---------- .../routing/core/RatioTest.java | 6 +-- .../TestFreeFloatingRentalVehicleBuilder.java | 14 +++-- 6 files changed, 46 insertions(+), 37 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java index b38f1ce0913..2e24613f49d 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java @@ -2,6 +2,7 @@ import javax.annotation.Nullable; import org.opentripplanner.transit.model.basic.Distance; +import org.opentripplanner.transit.model.basic.Ratio; /** * Contains information about the current battery or fuel status. @@ -16,7 +17,7 @@ public class RentalVehicleFuel { * May be {@code null}. */ @Nullable - public final Double percent; + public final Ratio percent; /** * Distance that the vehicle can travel with the current charge or fuel. @@ -26,14 +27,14 @@ public class RentalVehicleFuel { @Nullable public final Distance range; - public RentalVehicleFuel(@Nullable Double fuelPercent, @Nullable Distance range) { + public RentalVehicleFuel(@Nullable Ratio fuelPercent, @Nullable Distance range) { this.percent = fuelPercent; this.range = range; } @Nullable public Double getPercent() { - return percent; + return this.percent != null ? this.percent.ratio : null; } @Nullable diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java index 0b8061190be..fef694ec023 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java @@ -18,7 +18,7 @@ public Distance(double distanceInMeters) { } /** Returns a Distance object representing the given number of meters */ - public static Distance ofMeters(double value) { + public static Distance ofMeters(double value) throws IllegalArgumentException { if (value < 0) { throw new IllegalArgumentException("Distance cannot be negative"); } diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java index 57276753e9d..c93ece2a477 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java @@ -5,7 +5,7 @@ public class Ratio { public final Double ratio; - public Ratio(Double ratio) { + public Ratio(Double ratio) throws IllegalArgumentException { if (ratio < 0d || ratio > 1d) { throw new IllegalArgumentException("Ratio must be in range [0,1]"); } diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index d5620bff52f..00e99030c45 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -17,6 +17,7 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalSystem; import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle; import org.opentripplanner.transit.model.basic.Distance; +import org.opentripplanner.transit.model.basic.Ratio; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,39 +61,38 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { vehicle.getLastReported() != null ? Instant.ofEpochSecond((long) (double) vehicle.getLastReported()) : null; - Double fuelPercent = null; - if (vehicle.getCurrentFuelPercent() != null) { - try { - fuelPercent = - (Double) GraphQLScalars.RATIO_SCALAR - .getCoercing() - .parseValue(vehicle.getCurrentFuelPercent()); - } catch (CoercingParseValueException e) { - LOG.warn( - "Current fuel percent: {} - {}", - vehicle.getCurrentFuelPercent(), - e.getMessage() - ); - } - } + Ratio fuelPercent = null; + try { + fuelPercent = new Ratio(vehicle.getCurrentFuelPercent()); + } catch (IllegalArgumentException e) { + LOG.warn( + "Current fuel percent value not valid: {} - {}", + vehicle.getCurrentFuelPercent(), + e.getMessage() + ); + } catch (NullPointerException e) {} Distance rangeMeters = null; try { - rangeMeters = vehicle.getCurrentRangeMeters() != null - ? Distance.ofMeters(vehicle.getCurrentRangeMeters()) - : null; + rangeMeters = + vehicle.getCurrentRangeMeters() != null + ? Distance.ofMeters(vehicle.getCurrentRangeMeters()) + : null; } catch (IllegalArgumentException e) { - LOG.warn(e.getMessage()); + LOG.warn( + "Current range meter value not valid: {} - {}", + vehicle.getCurrentRangeMeters(), + e.getMessage() + ); // if the propulsion type has an engine current_range_meters is required - if (vehicle.getVehicleTypeId() != null - && vehicleTypes.get(vehicle.getVehicleTypeId()).propulsionType != RentalVehicleType.PropulsionType.HUMAN) { + if ( + vehicle.getVehicleTypeId() != null && + vehicleTypes.get(vehicle.getVehicleTypeId()).propulsionType != + RentalVehicleType.PropulsionType.HUMAN + ) { return null; } } - rentalVehicle.fuel = - new RentalVehicleFuel( - fuelPercent, - rangeMeters - ); + rentalVehicle.fuel = new RentalVehicleFuel(fuelPercent, rangeMeters); rentalVehicle.pricingPlanId = vehicle.getPricingPlanId(); GBFSRentalUris rentalUris = vehicle.getRentalUris(); if (rentalUris != null) { diff --git a/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java b/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java index c434c4d54e7..f65b00e0e78 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java @@ -1,7 +1,7 @@ package org.opentripplanner.routing.core; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -24,8 +24,8 @@ void validRatios() { @Test void invalidRatios() { - assertThrows(IllegalArgumentException.class ,() -> new Ratio(TOO_HIGH)); - assertThrows(IllegalArgumentException.class ,() -> new Ratio(TOO_LOW)); + assertThrows(IllegalArgumentException.class, () -> new Ratio(TOO_HIGH)); + assertThrows(IllegalArgumentException.class, () -> new Ratio(TOO_LOW)); } @Test diff --git a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java index 845e8faed68..85dd57bfc1d 100644 --- a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java +++ b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java @@ -1,8 +1,10 @@ package org.opentripplanner.service.vehiclerental.model; +import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.street.model.RentalFormFactor; import org.opentripplanner.transit.model.basic.Distance; +import org.opentripplanner.transit.model.basic.Ratio; import org.opentripplanner.transit.model.framework.FeedScopedId; public class TestFreeFloatingRentalVehicleBuilder { @@ -15,7 +17,7 @@ public class TestFreeFloatingRentalVehicleBuilder { private double latitude = DEFAULT_LATITUDE; private double longitude = DEFAULT_LONGITUDE; - private Double currentFuelPercent = DEFAULT_CURRENT_FUEL_PERCENT; + private Ratio currentFuelPercent = new Ratio(DEFAULT_CURRENT_FUEL_PERCENT); private Double currentRangeMeters = DEFAULT_CURRENT_RANGE_METERS; private VehicleRentalSystem system = null; private String network = NETWORK_1; @@ -36,8 +38,14 @@ public TestFreeFloatingRentalVehicleBuilder withLongitude(double longitude) { return this; } - public TestFreeFloatingRentalVehicleBuilder withCurrentFuelPercent(Double currentFuelPercent) { - this.currentFuelPercent = currentFuelPercent; + public TestFreeFloatingRentalVehicleBuilder withCurrentFuelPercent( + @Nullable Double currentFuelPercent + ) { + if (currentFuelPercent == null) { + this.currentFuelPercent = null; + } else { + this.currentFuelPercent = new Ratio(currentFuelPercent); + } return this; } From 6edf98b0c0cf58159feb3bb7c8febaa2d47e8de3 Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 17 Dec 2024 14:03:53 +0100 Subject: [PATCH 100/195] Ratio class and test format --- .../main/java/org/opentripplanner/transit/model/basic/Ratio.java | 1 + .../test/java/org/opentripplanner/routing/core/RatioTest.java | 1 + 2 files changed, 2 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java index c93ece2a477..0c980be6eee 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java @@ -3,6 +3,7 @@ import java.util.Objects; public class Ratio { + public final Double ratio; public Ratio(Double ratio) throws IllegalArgumentException { diff --git a/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java b/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java index f65b00e0e78..4195fc5c5c0 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java @@ -9,6 +9,7 @@ import org.opentripplanner.transit.model.basic.Ratio; public class RatioTest { + private static final Double HALF = 0.5d; private static final Double ZERO = 0d; private static final Double ONE = 1d; From 0b1920d703492f930d785054e2d3aefc9c05f920 Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 17 Dec 2024 14:21:52 +0100 Subject: [PATCH 101/195] fix check when range is required --- .../datasources/GbfsFreeVehicleStatusMapper.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index 00e99030c45..44bcee84580 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -83,14 +83,12 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { vehicle.getCurrentRangeMeters(), e.getMessage() ); - // if the propulsion type has an engine current_range_meters is required - if ( - vehicle.getVehicleTypeId() != null && - vehicleTypes.get(vehicle.getVehicleTypeId()).propulsionType != - RentalVehicleType.PropulsionType.HUMAN - ) { - return null; - } + } + // if the propulsion type has an engine current_range_meters is required + if (vehicle.getVehicleTypeId() != null + && vehicleTypes.get(vehicle.getVehicleTypeId()).propulsionType != RentalVehicleType.PropulsionType.HUMAN + && rangeMeters == null) { + return null; } rentalVehicle.fuel = new RentalVehicleFuel(fuelPercent, rangeMeters); rentalVehicle.pricingPlanId = vehicle.getPricingPlanId(); From 54b0bc02fb477995c435e63b97284dd82adb2883 Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 17 Dec 2024 14:28:36 +0100 Subject: [PATCH 102/195] format GbfsFreeVehicleStatusMapper --- .../datasources/GbfsFreeVehicleStatusMapper.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index 44bcee84580..1265583df13 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -85,9 +85,12 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { ); } // if the propulsion type has an engine current_range_meters is required - if (vehicle.getVehicleTypeId() != null - && vehicleTypes.get(vehicle.getVehicleTypeId()).propulsionType != RentalVehicleType.PropulsionType.HUMAN - && rangeMeters == null) { + if ( + vehicle.getVehicleTypeId() != null && + vehicleTypes.get(vehicle.getVehicleTypeId()).propulsionType != + RentalVehicleType.PropulsionType.HUMAN && + rangeMeters == null + ) { return null; } rentalVehicle.fuel = new RentalVehicleFuel(fuelPercent, rangeMeters); From f3e0a7122764900b96d667a752194a6538cc6e3b Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 17 Dec 2024 14:44:49 +0100 Subject: [PATCH 103/195] add range to scooter in GbfsFreeVehicleStatusMapperTest --- .../datasources/GbfsFreeVehicleStatusMapper.java | 3 +-- .../datasources/GbfsFreeVehicleStatusMapperTest.java | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index 1265583df13..f2fd9315dd3 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -2,14 +2,12 @@ import static java.util.Objects.requireNonNullElse; -import graphql.schema.CoercingParseValueException; import java.time.Instant; import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; import org.mobilitydata.gbfs.v2_3.free_bike_status.GBFSBike; import org.mobilitydata.gbfs.v2_3.free_bike_status.GBFSRentalUris; -import org.opentripplanner.apis.gtfs.GraphQLScalars; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.service.vehiclerental.model.RentalVehicleFuel; import org.opentripplanner.service.vehiclerental.model.RentalVehicleType; @@ -87,6 +85,7 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { // if the propulsion type has an engine current_range_meters is required if ( vehicle.getVehicleTypeId() != null && + vehicleTypes.get(vehicle.getVehicleTypeId()) != null && vehicleTypes.get(vehicle.getVehicleTypeId()).propulsionType != RentalVehicleType.PropulsionType.HUMAN && rangeMeters == null diff --git a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapperTest.java b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapperTest.java index 02295ce09e9..79322d325fc 100644 --- a/application/src/test/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapperTest.java +++ b/application/src/test/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapperTest.java @@ -37,7 +37,7 @@ class GbfsFreeVehicleStatusMapperTest { new FeedScopedId("1", "scooter"), "Scooter", RentalFormFactor.SCOOTER, - null, + RentalVehicleType.PropulsionType.COMBUSTION, null ) ) @@ -62,7 +62,6 @@ void withDefaultType() { bike.setLon(1d); bike.setVehicleTypeId("bike"); var mapped = MAPPER.mapFreeVehicleStatus(bike); - assertEquals("Default vehicle type", mapped.name.toString()); } @@ -73,6 +72,7 @@ void withType() { bike.setLat(1d); bike.setLon(1d); bike.setVehicleTypeId("scooter"); + bike.setCurrentRangeMeters(2000d); var mapped = MAPPER.mapFreeVehicleStatus(bike); assertEquals("Scooter", mapped.name.toString()); From 26d8d7d097ce5f8f6362f11000487148aa79dda3 Mon Sep 17 00:00:00 2001 From: JustCris Date: Tue, 7 Jan 2025 15:03:11 +0100 Subject: [PATCH 104/195] general fixes Ratio.java and Distance.java --- .../vehiclerental/model/RentalVehicleFuel.java | 12 +----------- .../transit/model/basic/Distance.java | 12 ++---------- .../opentripplanner/transit/model/basic/Ratio.java | 10 +++++++--- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java index 2e24613f49d..3b0349be0b8 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java @@ -11,19 +11,9 @@ */ public class RentalVehicleFuel { - /** - * Current fuel percentage, expressed from 0 to 1. - *

- * May be {@code null}. - */ @Nullable public final Ratio percent; - /** - * Distance that the vehicle can travel with the current charge or fuel. - *

- * May be {@code null}. - */ @Nullable public final Distance range; @@ -34,7 +24,7 @@ public RentalVehicleFuel(@Nullable Ratio fuelPercent, @Nullable Distance range) @Nullable public Double getPercent() { - return this.percent != null ? this.percent.ratio : null; + return this.percent != null ? this.percent.asDouble() : null; } @Nullable diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java index fef694ec023..9188699abfb 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java @@ -9,7 +9,7 @@ public class Distance { private final double meters; /** Returns a Distance object representing the given number of meters */ - public Distance(double distanceInMeters) { + private Distance(double distanceInMeters) { if (distanceInMeters < 0) { throw new IllegalArgumentException("Distance cannot be negative"); } @@ -19,19 +19,11 @@ public Distance(double distanceInMeters) { /** Returns a Distance object representing the given number of meters */ public static Distance ofMeters(double value) throws IllegalArgumentException { - if (value < 0) { - throw new IllegalArgumentException("Distance cannot be negative"); - } - return new Distance(value); } /** Returns a Distance object representing the given number of kilometers */ public static Distance ofKilometers(double value) { - if (value < 0) { - throw new IllegalArgumentException("Distance cannot be negative"); - } - return new Distance(value * METERS_PER_KM); } @@ -51,7 +43,7 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(meters, "Distance"); + return Objects.hash(meters); } @Override diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java index 0c980be6eee..95c4260b078 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java @@ -4,9 +4,9 @@ public class Ratio { - public final Double ratio; + private final Double ratio; - public Ratio(Double ratio) throws IllegalArgumentException { + public Ratio(Double ratio) { if (ratio < 0d || ratio > 1d) { throw new IllegalArgumentException("Ratio must be in range [0,1]"); } @@ -25,11 +25,15 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(this.ratio, "Ratio"); + return Objects.hash(this.ratio); } @Override public String toString() { return this.ratio.toString(); } + + public Double asDouble() { + return ratio; + } } From 628bf95363a64a7d7d3ccd3e66a7b08d66afe794 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 7 Jan 2025 17:48:06 +0100 Subject: [PATCH 105/195] Rename 'code' to 'publicCode' --- .../apis/gtfs/datafetchers/EntranceImpl.java | 2 +- .../apis/gtfs/generated/GraphQLDataFetchers.java | 4 ++-- .../opentripplanner/apis/gtfs/generated/GraphQLTypes.java | 6 +++++- .../org/opentripplanner/apis/gtfs/schema.graphqls | 8 ++++---- .../opentripplanner/apis/gtfs/GraphQLIntegrationTest.java | 3 --- .../apis/gtfs/expectations/walk-steps.json | 2 +- .../opentripplanner/apis/gtfs/queries/walk-steps.graphql | 2 +- 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java index 9891d107479..f9faa9cc4d1 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/EntranceImpl.java @@ -9,7 +9,7 @@ public class EntranceImpl implements GraphQLDataFetchers.GraphQLEntrance { @Override - public DataFetcher code() { + public DataFetcher publicCode() { return environment -> { Entrance entrance = environment.getSource(); return entrance.getCode(); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index d9c9ceb67e8..26ace8fc66a 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -394,12 +394,12 @@ public interface GraphQLEmissions { /** Station entrance or exit, originating from OSM or GTFS data. */ public interface GraphQLEntrance { - public DataFetcher code(); - public DataFetcher entranceId(); public DataFetcher name(); + public DataFetcher publicCode(); + public DataFetcher wheelchairAccessible(); } diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index a969b5223b1..fc20625e18e 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -4327,7 +4327,11 @@ public enum GraphQLRealtimeState { 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. + */ public enum GraphQLRelativeDirection { CIRCLE_CLOCKWISE, CIRCLE_COUNTERCLOCKWISE, 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 c9d0f3d73af..ce808e546d1 100644 --- a/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/application/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -493,12 +493,12 @@ type Emissions { "Station entrance or exit, originating from OSM or GTFS data." type Entrance { - "Short text or a number that identifies the entrance or exit for passengers. For example, `A` or `B`." - code: String "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 } @@ -3557,14 +3557,14 @@ enum RelativeDirection { """ 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 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 bfd2e1cc224..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; @@ -139,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(); 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 0e089aac428..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 @@ -28,7 +28,7 @@ "absoluteDirection": null, "feature": { "__typename": "Entrance", - "code": "A", + "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 565e620fed3..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 @@ -23,7 +23,7 @@ feature { __typename ... on Entrance { - code + publicCode entranceId wheelchairAccessible } From 79cac16d00ba8f52b53198b6363114f7ac33d369 Mon Sep 17 00:00:00 2001 From: a-limyr Date: Wed, 8 Jan 2025 15:16:48 +0100 Subject: [PATCH 106/195] Nested arguments, list arguments, layout changes, reset button, exclude arguments logic, moved attribution control and added reset option for boolean arguments. --- client/package-lock.json | 297 ++++++++++-- client/package.json | 3 +- .../ItineraryList/ItineraryListContainer.tsx | 119 ++--- .../components/MapView/DebugLayerButton.tsx | 59 ++- client/src/components/MapView/MapView.tsx | 25 +- .../SearchBar/DepartureArrivalSelect.tsx | 3 +- .../SearchBar/InputFieldsSection.tsx | 8 +- .../src/components/SearchBar/LogoSection.tsx | 25 +- client/src/components/SearchBar/SearchBar.tsx | 68 ++- .../SearchInput/ArgumentTooltip.tsx | 36 ++ .../SearchInput/DefaultValueTooltip.tsx | 15 - client/src/components/SearchInput/Sidebar.tsx | 53 +++ .../components/SearchInput/TripArguments.ts | 14 +- .../SearchInput/TripQueryArguments.tsx | 440 +++++++++++++++--- .../SearchInput/ViewArgumentsRaw.tsx | 46 +- .../SearchInput/excluded-arguments.ts | 13 + .../components/SearchInput/nestedUtils.tsx | 124 ++++- client/src/screens/App.tsx | 26 +- client/src/static/img/help-info-solid.svg | 1 + client/src/static/img/info-circle.svg | 1 + client/src/static/img/input.svg | 8 + client/src/static/img/json.svg | 1 + client/src/static/img/lap-timer.svg | 8 + client/src/static/img/route.svg | 8 + client/src/style.css | 217 ++++++--- client/src/util/generate-arguments.cjs | 13 +- 26 files changed, 1179 insertions(+), 452 deletions(-) create mode 100644 client/src/components/SearchInput/ArgumentTooltip.tsx delete mode 100644 client/src/components/SearchInput/DefaultValueTooltip.tsx create mode 100644 client/src/components/SearchInput/Sidebar.tsx create mode 100644 client/src/components/SearchInput/excluded-arguments.ts create mode 100644 client/src/static/img/help-info-solid.svg create mode 100644 client/src/static/img/info-circle.svg create mode 100644 client/src/static/img/input.svg create mode 100644 client/src/static/img/json.svg create mode 100644 client/src/static/img/lap-timer.svg create mode 100644 client/src/static/img/route.svg diff --git a/client/package-lock.json b/client/package-lock.json index e1e00b4e99d..982667dc6b2 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,7 +17,8 @@ "react": "18.3.1", "react-bootstrap": "2.10.5", "react-dom": "18.3.1", - "react-map-gl": "7.1.7" + "react-map-gl": "7.1.7", + "react-select": "5.9.0" }, "devDependencies": { "@graphql-codegen/cli": "5.0.3", @@ -218,7 +219,6 @@ "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", - "dev": true, "license": "MIT", "dependencies": { "@babel/highlight": "^7.25.7", @@ -273,7 +273,6 @@ "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.25.7", @@ -377,7 +376,6 @@ "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.7", @@ -488,7 +486,6 @@ "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -498,7 +495,6 @@ "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -532,7 +528,6 @@ "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.25.7", @@ -548,7 +543,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^1.9.0" @@ -561,7 +555,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", @@ -576,7 +569,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" @@ -586,14 +578,12 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, "license": "MIT" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -603,7 +593,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -613,7 +602,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^3.0.0" @@ -626,7 +614,6 @@ "version": "7.25.8", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.25.8" @@ -1112,7 +1099,6 @@ "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.25.7", @@ -1127,7 +1113,6 @@ "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.25.7", @@ -1146,7 +1131,6 @@ "version": "7.25.8", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.7", @@ -1163,6 +1147,114 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -1660,6 +1752,28 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "dependencies": { + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", + "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" + }, "node_modules/@googlemaps/polyline-codec": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/@googlemaps/polyline-codec/-/polyline-codec-1.0.28.tgz", @@ -2595,7 +2709,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -2609,7 +2722,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -2618,7 +2730,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -2627,14 +2738,12 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -3559,6 +3668,11 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, "node_modules/@types/pbf": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", @@ -4411,6 +4525,43 @@ "node": ">= 0.4" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/babel-plugin-macros/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-plugin-syntax-trailing-function-commas": { "version": "7.0.0-beta.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", @@ -4659,7 +4810,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -5138,7 +5288,6 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5389,7 +5538,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -5614,7 +5762,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -6340,6 +6487,11 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -6453,7 +6605,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6665,7 +6816,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "engines": { "node": ">=4" } @@ -6888,7 +7038,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -6906,6 +7055,19 @@ "tslib": "^2.0.3" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -7004,7 +7166,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -7020,7 +7181,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -7160,8 +7320,7 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, "node_modules/is-async-function": { "version": "2.0.0", @@ -7223,7 +7382,6 @@ "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -7804,7 +7962,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -7822,8 +7979,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -7965,8 +8121,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/listr2": { "version": "4.0.5", @@ -8277,6 +8432,11 @@ "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", "license": "ISC" }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -8384,7 +8544,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/murmurhash-js": { @@ -8776,7 +8935,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -8802,7 +8960,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -8878,8 +9035,7 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-root": { "version": "0.1.1", @@ -8930,7 +9086,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -8968,7 +9123,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -9271,6 +9425,26 @@ "node": ">=0.10.0" } }, + "node_modules/react-select": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.9.0.tgz", + "integrity": "sha512-nwRKGanVHGjdccsnzhFte/PULziueZxGD8LL2WojON78Mvnq7LdAMEtu2frrwld1fr3geixg3iiMBIc/LLAZpw==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -9396,7 +9570,6 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -9855,6 +10028,14 @@ "node": ">=0.10.0" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -10136,6 +10317,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/supercluster": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", @@ -10160,7 +10346,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -10326,7 +10511,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, "engines": { "node": ">=4" } @@ -10715,6 +10899,19 @@ "dev": true, "license": "MIT" }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz", + "integrity": "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/client/package.json b/client/package.json index ee356837dd9..aa6edf5d8ca 100644 --- a/client/package.json +++ b/client/package.json @@ -28,7 +28,8 @@ "react": "18.3.1", "react-bootstrap": "2.10.5", "react-dom": "18.3.1", - "react-map-gl": "7.1.7" + "react-map-gl": "7.1.7", + "react-select": "5.9.0" }, "devDependencies": { "@graphql-codegen/cli": "5.0.3", diff --git a/client/src/components/ItineraryList/ItineraryListContainer.tsx b/client/src/components/ItineraryList/ItineraryListContainer.tsx index 36ab2186bc7..6380a5f6d30 100644 --- a/client/src/components/ItineraryList/ItineraryListContainer.tsx +++ b/client/src/components/ItineraryList/ItineraryListContainer.tsx @@ -1,4 +1,4 @@ -import { QueryType, TripQueryVariables } from '../../gql/graphql.ts'; +import { QueryType } from '../../gql/graphql.ts'; import { Accordion } from 'react-bootstrap'; import { useContainerWidth } from './useContainerWidth.ts'; import { ItineraryHeaderContent } from './ItineraryHeaderContent.tsx'; @@ -7,9 +7,6 @@ import { ItineraryDetails } from './ItineraryDetails.tsx'; import { ItineraryPaginationControl } from './ItineraryPaginationControl.tsx'; import { useContext } from 'react'; import { TimeZoneContext } from '../../hooks/TimeZoneContext.ts'; -import { useState } from 'react'; -import ViewArgumentsRaw from '../SearchInput/ViewArgumentsRaw.tsx'; -import TripQueryArguments from '../SearchInput/TripQueryArguments.tsx'; export function ItineraryListContainer({ tripQueryResult, @@ -17,89 +14,63 @@ export function ItineraryListContainer({ setSelectedTripPatternIndex, pageResults, loading, - tripQueryVariables, - setTripQueryVariables, }: { tripQueryResult: QueryType | null; selectedTripPatternIndex: number; setSelectedTripPatternIndex: (selectedTripPatterIndex: number) => void; pageResults: (cursor: string) => void; loading: boolean; - tripQueryVariables: TripQueryVariables; - setTripQueryVariables: (variables: TripQueryVariables) => void; }) { const [earliestStartTime, latestEndTime] = useEarliestAndLatestTimes(tripQueryResult); const { containerRef, containerWidth } = useContainerWidth(); const timeZone = useContext(TimeZoneContext); - // State for toggling between Accordion and new element - const [showArguments, setShowArguments] = useState(false); - return ( -

-
- - -
- - {showArguments ? ( -
-

- Trip arguments -

- - -
- ) : ( - setSelectedTripPatternIndex(parseInt(eventKey as string))} +
+ <> +
+ Itinerary results +
+
+ +
+ setSelectedTripPatternIndex(parseInt(eventKey as string))} + > + {tripQueryResult && + tripQueryResult.trip.tripPatterns.map((tripPattern, itineraryIndex) => ( + - {tripQueryResult && - tripQueryResult.trip.tripPatterns.map((tripPattern, itineraryIndex) => ( - - - - - - - - - ))} - - )} + + + + + + + + ))} + + -
- All times in {timeZone} -
-
+ {/* Time Zone Info */} +
+ All times in {timeZone} +
+
); } diff --git a/client/src/components/MapView/DebugLayerButton.tsx b/client/src/components/MapView/DebugLayerButton.tsx index 9990852fb4b..cd2aa862a00 100644 --- a/client/src/components/MapView/DebugLayerButton.tsx +++ b/client/src/components/MapView/DebugLayerButton.tsx @@ -3,41 +3,36 @@ import debugLayerIcon from '../../static/img/debug-layer.svg'; import { useState } from 'react'; type DebugLayerButtonProps = { - onToggle: (isExpanded: boolean) => void; + onToggle: (isExpanded: boolean) => void; }; export default function DebugLayerButton({ onToggle }: DebugLayerButtonProps) { - const [isExpanded, setIsExpanded] = useState(false); + const [isExpanded, setIsExpanded] = useState(false); - const handleClick = () => { - const nextState = !isExpanded; - setIsExpanded(nextState); - onToggle(nextState); // Notify parent of the state change - }; + const handleClick = () => { + const nextState = !isExpanded; + setIsExpanded(nextState); + onToggle(nextState); // Notify parent of the state change + }; - return ( - - ); + return ( + + ); } diff --git a/client/src/components/MapView/MapView.tsx b/client/src/components/MapView/MapView.tsx index f195857d962..910733cfb99 100644 --- a/client/src/components/MapView/MapView.tsx +++ b/client/src/components/MapView/MapView.tsx @@ -8,6 +8,7 @@ import { VectorTileSource, MapRef, } from 'react-map-gl/maplibre'; +import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; import { TripPattern, TripQuery, TripQueryVariables } from '../../gql/graphql.ts'; import { NavigationMarkers } from './NavigationMarkers.tsx'; @@ -66,10 +67,25 @@ export function MapView({ map.fitBounds(source.bounds, { maxDuration: 50, linear: true }); } }; + + const onLoad = (e: MapEvent) => { + const map = e.target; + map.addControl(new maplibregl.AttributionControl(), 'bottom-left'); + }; + + function handleMapLoad(e: MapEvent) { + // 1) Call your existing function + panToWorldEnvelopeIfRequired(e); + + // 2) Add the native MapLibre attribution control + onLoad(e); + } + const mapRef = useRef(null); // Create a ref for MapRef return (
@@ -98,11 +114,8 @@ export function MapView({ setTripQueryVariables={setTripQueryVariables} loading={loading} /> - + + {tripQueryResult?.trip.tripPatterns.length && ( )} diff --git a/client/src/components/SearchBar/DepartureArrivalSelect.tsx b/client/src/components/SearchBar/DepartureArrivalSelect.tsx index 861e795d74b..a94516dfc3b 100644 --- a/client/src/components/SearchBar/DepartureArrivalSelect.tsx +++ b/client/src/components/SearchBar/DepartureArrivalSelect.tsx @@ -18,12 +18,13 @@ export function DepartureArrivalSelect({ return ( - + Departure/Arrival (e.target.value === 'arrival' ? onChange(true) : onChange(false))} value={tripQueryVariables.arriveBy ? 'arrival' : 'departure'} + style={{ verticalAlign: 'bottom' }} > diff --git a/client/src/components/SearchBar/InputFieldsSection.tsx b/client/src/components/SearchBar/InputFieldsSection.tsx index 59d6fc99f27..ff54af0e16e 100644 --- a/client/src/components/SearchBar/InputFieldsSection.tsx +++ b/client/src/components/SearchBar/InputFieldsSection.tsx @@ -28,20 +28,20 @@ export function InputFieldsSection({ }: InputFieldsSectionProps) { return (
-
+
-
+
-
+
-
+
diff --git a/client/src/components/SearchBar/LogoSection.tsx b/client/src/components/SearchBar/LogoSection.tsx index 1acaa38a636..087263e8167 100644 --- a/client/src/components/SearchBar/LogoSection.tsx +++ b/client/src/components/SearchBar/LogoSection.tsx @@ -13,19 +13,18 @@ export function LogoSection({ serverInfo }: LogoSectionProps) { const target = useRef(null); return ( -
- setShowServerInfo((v) => !v)}> -
- - OTP Debug - {showServerInfo && } -
-
-
-
Version: {serverInfo?.version}
-
Time zone: {serverInfo?.internalTransitModelTimeZone}
-
+
+ setShowServerInfo((v) => !v)}> +
+ + OTP Debug + {showServerInfo && } +
+
+
+
Version: {serverInfo?.version}
+
Time zone: {serverInfo?.internalTransitModelTimeZone}
+
); } diff --git a/client/src/components/SearchBar/SearchBar.tsx b/client/src/components/SearchBar/SearchBar.tsx index 187115e6380..833f0970d55 100644 --- a/client/src/components/SearchBar/SearchBar.tsx +++ b/client/src/components/SearchBar/SearchBar.tsx @@ -31,55 +31,47 @@ export function SearchBar({ onRoute, tripQueryVariables, setTripQueryVariables, const target = useRef(null); return ( - - <> - -
-
+ <> +
+
setShowServerInfo((v) => !v)}>
- OTP Debug - {showServerInfo && } + OTP Debug + {showServerInfo && }
-
- - - - - - - - - - - +
+ + + + + + + + + + + + tripQueryVariables={tripQueryVariables} + setTripQueryVariables={setTripQueryVariables} + /> + tripQueryVariables={tripQueryVariables} + setTripQueryVariables={setTripQueryVariables} + />
@@ -88,6 +80,6 @@ export function SearchBar({ onRoute, tripQueryVariables, setTripQueryVariables,
- + ); } diff --git a/client/src/components/SearchInput/ArgumentTooltip.tsx b/client/src/components/SearchInput/ArgumentTooltip.tsx new file mode 100644 index 00000000000..4d5d07ebfdf --- /dev/null +++ b/client/src/components/SearchInput/ArgumentTooltip.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import infoIcon from '../../static/img/help-info-solid.svg'; +import inputIcon from '../../static/img/input.svg'; +import durationIcon from '../../static/img/lap-timer.svg'; + +interface ArgumentTooltipProps { + defaultValue: any; + type?: string; +} + +const ArgumentTooltip: React.FC = ({ defaultValue, type }) => { + return ( + + {defaultValue !== undefined && defaultValue !== null && ( + + {'Info'} + + )} + {type !== undefined && type !== null && type === 'DoubleFunction' && ( + + {'Info'} + + )} + {type !== undefined && type !== null && type === 'Duration' && ( + + {'Info'} + + )} + + ); +}; + +export default ArgumentTooltip; diff --git a/client/src/components/SearchInput/DefaultValueTooltip.tsx b/client/src/components/SearchInput/DefaultValueTooltip.tsx deleted file mode 100644 index a1eef4b0c65..00000000000 --- a/client/src/components/SearchInput/DefaultValueTooltip.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - -interface DefaultValueTooltipProps { - defaultValue: any; -} - -const DefaultValueTooltip: React.FC = ({ defaultValue }) => { - return ( - - ! - - ); -}; - -export default DefaultValueTooltip; diff --git a/client/src/components/SearchInput/Sidebar.tsx b/client/src/components/SearchInput/Sidebar.tsx new file mode 100644 index 00000000000..ce4badb6d50 --- /dev/null +++ b/client/src/components/SearchInput/Sidebar.tsx @@ -0,0 +1,53 @@ +import React, { useState, ReactNode } from 'react'; +import tripIcon from '../../static/img/route.svg'; +import filterIcon from '../../static/img/filter.svg'; +import jsonIcon from '../../static/img/json.svg'; + +interface SidebarProps { + children: ReactNode | ReactNode[]; +} + +const Sidebar: React.FC = ({ children }) => { + const [activeIndex, setActiveIndex] = useState(0); + + // Function to return the appropriate image based on the index + const getIconForIndex = (index: number) => { + switch (index) { + case 0: + return Itineray list; + case 1: + return Filters; + case 2: + return Filters; + default: + return null; + } + }; + + // Ensure children is always an array and filter out invalid children (null, undefined) + const childArray = React.Children.toArray(children).filter((child) => React.isValidElement(child)); + + return ( +
+ {/* Sidebar Navigation Buttons */} +
+ {childArray.map((_, index) => ( +
setActiveIndex(index)} + > + {getIconForIndex(index)} +
+ ))} +
+ + {/* Content Area */} +
+ {childArray.map((child, index) => (index === activeIndex ?
{child}
: null))} +
+
+ ); +}; + +export default Sidebar; diff --git a/client/src/components/SearchInput/TripArguments.ts b/client/src/components/SearchInput/TripArguments.ts index 600fc9fd821..d911032bd72 100644 --- a/client/src/components/SearchInput/TripArguments.ts +++ b/client/src/components/SearchInput/TripArguments.ts @@ -1,14 +1,14 @@ export interface TripArguments { - trip: { - arguments: { - [key: string]: Argument; - }; + trip: { + arguments: { + [key: string]: Argument; }; + }; } export interface Argument { - type: TypeDescriptor; - defaultValue?: any; + type: TypeDescriptor; + defaultValue?: any; } export type TypeDescriptor = ScalarType | NestedObject; @@ -16,5 +16,5 @@ export type TypeDescriptor = ScalarType | NestedObject; export type ScalarType = 'ID' | 'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime' | 'Duration'; export interface NestedObject { - [key: string]: Argument | string[]; // Allows for nested objects or enum arrays + [key: string]: Argument | string[]; // Allows for nested objects or enum arrays } diff --git a/client/src/components/SearchInput/TripQueryArguments.tsx b/client/src/components/SearchInput/TripQueryArguments.tsx index 40888f537e7..98122ba7a5f 100644 --- a/client/src/components/SearchInput/TripQueryArguments.tsx +++ b/client/src/components/SearchInput/TripQueryArguments.tsx @@ -2,7 +2,8 @@ import React, { useEffect, useState } from 'react'; import tripArgumentsData from '../../gql/query-arguments.json'; import { TripQueryVariables } from '../../gql/graphql'; import { getNestedValue, setNestedValue } from './nestedUtils'; -import DefaultValueTooltip from './DefaultValueTooltip.tsx'; +import ArgumentTooltip from './ArgumentTooltip.tsx'; +import { excludedArguments } from './excluded-arguments.ts'; interface TripQueryArgumentsProps { tripQueryVariables: TripQueryVariables; @@ -15,15 +16,31 @@ function formatArgumentName(input: string): string { } const parts = input.split('.'); const formatted = parts[parts.length - 1].replace(/([A-Z])/g, ' $1').trim(); - return formatted.replace(/\b\w/g, (char) => char.toUpperCase())+' '; + return formatted.replace(/\b\w/g, (char) => char.toUpperCase()) + ' '; } +type ArgumentConfig = { + path: string; + type: string; + defaultValue: any; + enumValues?: string[]; + isComplex?: boolean; + isList?: boolean; +}; const TripQueryArguments: React.FC = ({ tripQueryVariables, setTripQueryVariables }) => { const [argumentsList, setArgumentsList] = useState< - { path: string; type: string; subtype?: string; defaultValue: any; enumValues?: string[]; isComplex?: boolean }[] + { + path: string; + type: string; + subtype?: string; + defaultValue: any; + enumValues?: string[]; + isComplex?: boolean; + isList?: boolean; + }[] >([]); const [expandedArguments, setExpandedArguments] = useState<{ [key: string]: boolean }>({}); - const [searchText, setSearchText] = useState(''); + const [searchText] = useState(''); useEffect(() => { const tripArgs = tripArgumentsData.trip.arguments; @@ -34,7 +51,15 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab const extractAllArgs = ( args: { [key: string]: any }, parentPath: string[] = [], - ): { path: string; type: string; name?: string; defaultValue: any; enumValues?: string[]; isComplex?: boolean }[] => { + ): { + path: string; + type: string; + name?: string; + defaultValue: any; + enumValues?: string[]; + isComplex?: boolean; + isList?: boolean; + }[] => { let allArgs: { path: string; type: string; @@ -42,13 +67,13 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab defaultValue: any; enumValues?: string[]; isComplex?: boolean; + isList?: boolean; }[] = []; Object.entries(args).forEach(([argName, argData]) => { const currentPath = [...parentPath, argName].join('.'); allArgs = allArgs.concat(processArgument(argName, argData, currentPath, parentPath)); }); - return allArgs; }; @@ -57,7 +82,15 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab argData: any, currentPath: string, parentPath: string[], - ): { path: string; type: string; name?: string; defaultValue: any; enumValues?: string[]; isComplex?: boolean }[] => { + ): { + path: string; + type: string; + name?: string; + defaultValue: any; + enumValues?: string[]; + isComplex?: boolean; + isList?: boolean; + }[] => { let allArgs: { path: string; type: string; @@ -65,18 +98,50 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab defaultValue: any; enumValues?: string[]; isComplex?: boolean; + isList?: boolean; }[] = []; if (typeof argData === 'object' && argData.type) { if (argData.type.type === 'Enum') { const enumValues = ['Not selected', ...argData.type.values]; const defaultValue = argData.defaultValue !== undefined ? argData.defaultValue : 'Not selected'; - allArgs.push({ path: currentPath, type: 'Enum', defaultValue, enumValues }); - } else if (argData.type.type === 'InputObject' && argData.type.fields) { - allArgs.push({ path: currentPath, type: 'Group', defaultValue: null, isComplex: true }); + allArgs.push({ + path: currentPath, + type: 'Enum', + defaultValue, + enumValues, + isList: argData.isList, + }); + } else if (argData.type.type === 'InputObject' && argData.isList) { + // This is a list of InputObjects + allArgs.push({ + path: currentPath, + type: 'Group', // We'll still call this 'Group' + defaultValue: argData.defaultValue, + isComplex: true, + isList: true, + }); + + // NEW: Also extract subfields with a wildcard + // e.g. for `accessEgressPenalty`, we'll get `accessEgressPenalty.*.costFactor`, etc. + allArgs = allArgs.concat(extractAllArgs(argData.type.fields, [...parentPath, `${argName}.*`])); + } else if (argData.type.type === 'InputObject') { + // Single InputObject + allArgs.push({ + path: currentPath, + type: 'Group', + defaultValue: null, + isComplex: true, + isList: false, + }); allArgs = allArgs.concat(extractAllArgs(argData.type.fields, [...parentPath, argName])); } else if (argData.type.type === 'Scalar') { - allArgs.push({ path: currentPath, type: argData.type.subtype, defaultValue: argData.defaultValue }); + allArgs.push({ + path: currentPath, + type: argData.type.subtype, + defaultValue: argData.defaultValue, + isList: argData.isList, + }); } } else if (typeof argData === 'object' && argData.fields) { allArgs.push({ path: currentPath, type: 'Group', defaultValue: null, isComplex: true }); @@ -88,41 +153,100 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab return allArgs; }; + const normalizePathForList = (path: string): string => { + // Replace numeric segments with `*` + return path.replace(/\.\d+/g, '.*'); + }; + const handleInputChange = (path: string, value: any) => { - if (typeof value === 'number' && isNaN(value)) { - value = null; + const normalizedPath = normalizePathForList(path); // Normalize the path to match `argumentsList` + const argumentConfig = argumentsList.find((arg) => arg.path === normalizedPath); + + if (!argumentConfig) { + console.error(`No matching argumentConfig found for path: ${path}`); + return; } - let updatedTripQueryVariables = setNestedValue(tripQueryVariables, path, value); - if ( - value === undefined || - value === null || - value === argumentsList.find((arg) => arg.path === path)?.defaultValue - ) { - updatedTripQueryVariables = setNestedValue(updatedTripQueryVariables, path, undefined); - updatedTripQueryVariables = cleanUpParentIfEmpty(updatedTripQueryVariables, path); + + // Handle `ID` types with `isList=true` + if (['String', 'DoubleFunction', 'ID', 'Duration'].includes(argumentConfig.type) && argumentConfig.isList) { + if (typeof value === 'string') { + // Convert comma-separated string into an array + const idsArray = value.split(',').map((id) => id.trim()); // Remove whitespace + + // Update the `tripQueryVariables` with the array + let updatedTripQueryVariables = setNestedValue(tripQueryVariables, path, idsArray); + + // Clean up parent structure if necessary + updatedTripQueryVariables = cleanUpParentIfEmpty(updatedTripQueryVariables, path); + setTripQueryVariables(updatedTripQueryVariables); + return; + } } + + // Default handling for other cases + let updatedTripQueryVariables = setNestedValue(tripQueryVariables, path, value); + + updatedTripQueryVariables = cleanUpParentIfEmpty(updatedTripQueryVariables, path); setTripQueryVariables(updatedTripQueryVariables); }; const cleanUpParentIfEmpty = (variables: any, path: string): any => { + // Handle the case where `path` is top-level (no dots) + if (!path.includes('.')) { + const topValue = getNestedValue(variables, path); + + // If it’s an empty array, remove it entirely from `variables` + if (Array.isArray(topValue) && topValue.length === 0) { + const { [path]: _, ...rest } = variables; + return rest; + } + + // If it's an object and all keys are undefined/null or empty, remove it + if (topValue && typeof topValue === 'object') { + const allKeysEmpty = Object.keys(topValue).every((key) => { + const childVal = topValue[key]; + return childVal === undefined || childVal === null || (Array.isArray(childVal) && childVal.length === 0); + }); + if (allKeysEmpty) { + const { [path]: _, ...rest } = variables; + return rest; + } + } + + return variables; // Otherwise leave it as is + } + const pathParts = path.split('.'); for (let i = pathParts.length - 1; i > 0; i--) { const parentPath = pathParts.slice(0, i).join('.'); const parentValue = getNestedValue(variables, parentPath); - if (parentValue && typeof parentValue === 'object') { - const allKeysUndefinedOrDefault = Object.keys(parentValue).every((key) => { + if (parentValue == null) { + // Already null or undefined, nothing to do + continue; + } + + if (Array.isArray(parentValue)) { + // If the parent array is now empty, remove it + if (parentValue.length === 0) { + variables = setNestedValue(variables, parentPath, undefined); + } + } else if (typeof parentValue === 'object') { + // If all child values are null/undefined or empty, remove the parent + const allKeysEmpty = Object.keys(parentValue).every((key) => { const childPath = `${parentPath}.${key}`; const childValue = getNestedValue(variables, childPath); - const defaultValue = argumentsList.find((arg) => arg.path === childPath)?.defaultValue; - return childValue === undefined || childValue === null || childValue === defaultValue; + return ( + childValue === undefined || childValue === null || (Array.isArray(childValue) && childValue.length === 0) + ); }); - if (allKeysUndefinedOrDefault) { + if (allKeysEmpty) { variables = setNestedValue(variables, parentPath, undefined); } } } + return variables; }; @@ -134,48 +258,182 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab }); }; - const filteredArgumentsList = argumentsList.filter(({ path }) => - formatArgumentName(path).toLowerCase().includes(searchText.toLowerCase()), - ); + const filteredArgumentsList = argumentsList + .filter(({ path }) => formatArgumentName(path).toLowerCase().includes(searchText.toLowerCase())) + .filter(({ path }) => !excludedArguments.has(path)); + + const renderListOfInputObjects = (listPath: string, allArgs: ArgumentConfig[], level: number) => { + const arrayVal = getNestedValue(tripQueryVariables, listPath) || []; + + // Dynamically determine the button label based on the type name + const argumentsData = tripArgumentsData.trip.arguments as Record< + string, + { + type: { + type: string; + name?: string; + fields?: Record; + }; + defaultValue?: any; + isList?: boolean; + } + >; + + const parentArg = argumentsList.find((arg) => arg.path === listPath); + let typeName = 'Item'; // Default fallback + + if (parentArg?.type === 'Group' && argumentsData[listPath]?.type) { + const typeDetails = argumentsData[listPath].type; + if (typeDetails.type === 'InputObject' && typeDetails.name) { + typeName = typeDetails.name; // Use the type name directly + } + } + + return ( +
+ {arrayVal.map((_item: any, index: number) => { + const itemPath = `${listPath}.${index}`; + + const itemNestedArgs = allArgs + .filter((arg) => arg.path.startsWith(`${listPath}.*.`) && arg.path !== `${listPath}.*`) + .map((arg) => ({ + ...arg, + path: arg.path.replace(`${listPath}.*`, itemPath), + })); + + const immediateNestedArgs = itemNestedArgs.filter( + (arg) => arg.path.split('.').length === itemPath.split('.').length + 1, + ); + + const isExpandedItem = expandedArguments[itemPath]; + + return ( +
+ toggleExpand(itemPath)}> + {isExpandedItem ? '▼ ' : '▶ '} [#{index + 1}] + + + + {isExpandedItem && ( +
+ {renderArgumentInputs(immediateNestedArgs, level + 1, itemNestedArgs)} +
+ )} +
+ ); + })} + + {/* Add button with a dynamic name */} + +
+ ); + }; + + const handleAddItem = (listPath: string) => { + const currentValue = getNestedValue(tripQueryVariables, listPath) || []; + // Insert an empty object or a default shape + const newValue = [...currentValue, {}]; + const updatedTripQueryVariables = setNestedValue(tripQueryVariables, listPath, newValue); + setTripQueryVariables(updatedTripQueryVariables); + }; + + const handleRemoveItem = (listPath: string, index: number) => { + const currentValue = getNestedValue(tripQueryVariables, listPath) || []; + const newValue = currentValue.filter((_: any, i: number) => i !== index); + let updatedTripQueryVariables = setNestedValue(tripQueryVariables, listPath, newValue); + + updatedTripQueryVariables = cleanUpParentIfEmpty(updatedTripQueryVariables, listPath); + setTripQueryVariables(updatedTripQueryVariables); + }; + + const handleRemoveArgument = (path: string) => { + let updatedTripQueryVariables = setNestedValue(tripQueryVariables, path, undefined); + // Then let your cleanup function remove it if it’s an empty object/array + updatedTripQueryVariables = cleanUpParentIfEmpty(updatedTripQueryVariables, path); + setTripQueryVariables(updatedTripQueryVariables); + }; const renderArgumentInputs = ( - args: { path: string; type: string; defaultValue: any; enumValues?: string[]; isComplex?: boolean }[], + args: { + path: string; + type: string; + defaultValue: any; + enumValues?: string[]; + isComplex?: boolean; + isList?: boolean; + }[], level: number, + allArgs: { + path: string; + type: string; + defaultValue: any; + enumValues?: string[]; + isComplex?: boolean; + isList?: boolean; + }[], ) => { - return args.map(({ path, type, defaultValue, enumValues, isComplex }) => { + return args.map(({ path, type, defaultValue, enumValues, isComplex, isList }) => { const isExpanded = expandedArguments[path]; - const nestedArgs = argumentsList.filter((arg) => arg.path.startsWith(`${path}.`) && arg.path !== path); + const currentDepth = path.split('.').length; + const nestedArgs = allArgs.filter((arg) => { + const argDepth = arg.path.split('.').length; + return arg.path.startsWith(`${path}.`) && arg.path !== path && argDepth === currentDepth + 1; + }); const nestedLevel = level + 1; return (
{isComplex ? (
- toggleExpand(path)} - > - {formatArgumentName(path)} {isExpanded ? '▼' : '▶'} + toggleExpand(path)}> + {isExpanded ? '▼ ' : '▶ '} {formatArgumentName(path)} - {isExpanded && renderArgumentInputs(nestedArgs, nestedLevel)} + + {isExpanded && isList ? ( +
{renderListOfInputObjects(path, allArgs, nestedLevel)}
+ ) : isExpanded ? ( + /* original single-object rendering */ + renderArgumentInputs(nestedArgs, nestedLevel, allArgs) + ) : null}
) : (
-
- {filteredArgumentsList.length === 0 ? ( -

No arguments found.

- ) : ( -
- {renderArgumentInputs( - filteredArgumentsList.filter((arg) => arg.path.split('.').length === 1), - 0, - )} +
+
+ Filters +
- )} -
+ {filteredArgumentsList.length === 0 ? ( +

No arguments found.

+ ) : ( +
+ {renderArgumentInputs( + filteredArgumentsList.filter((arg) => arg.path.split('.').length === 1), + 0, + filteredArgumentsList, + )} +
+ )} +
); }; diff --git a/client/src/components/SearchInput/ViewArgumentsRaw.tsx b/client/src/components/SearchInput/ViewArgumentsRaw.tsx index 3b6788b3b5f..f38bcbde7d0 100644 --- a/client/src/components/SearchInput/ViewArgumentsRaw.tsx +++ b/client/src/components/SearchInput/ViewArgumentsRaw.tsx @@ -1,45 +1,23 @@ -import React, { useState } from 'react'; -import { Button, Modal } from 'react-bootstrap'; -import codeIcon from '../../static/img/code.svg'; +import React from 'react'; interface ViewArgumentsRawProps { - tripQueryVariables: any; + tripQueryVariables: any; } const ViewArgumentsRaw: React.FC = ({ tripQueryVariables }) => { - const [isDialogOpen, setIsDialogOpen] = useState(false); - const openDialog = () => { - setIsDialogOpen(true); - }; + return ( +
+
+ Arguments raw +
- const closeDialog = () => { - setIsDialogOpen(false); - }; +
+            {JSON.stringify(tripQueryVariables, null, 2)}
+          
- return ( - <> - - - - - Trip Query Variables - - -
-              {JSON.stringify(tripQueryVariables, null, 2)}
-            
-
- - - -
- - ); +
+ ); }; export default ViewArgumentsRaw; diff --git a/client/src/components/SearchInput/excluded-arguments.ts b/client/src/components/SearchInput/excluded-arguments.ts new file mode 100644 index 00000000000..7dc75bf0b08 --- /dev/null +++ b/client/src/components/SearchInput/excluded-arguments.ts @@ -0,0 +1,13 @@ +export const excludedArguments = new Set([ + + 'numTripPatterns', + 'arriveBy', + 'from', + 'to', + 'dateTime', + 'searchWindow', + 'modes.accessMode', + 'modes.directMode', + 'modes.egressMode', + // Add every full path you want to exclude - top level paths will remove all children! +]); \ No newline at end of file diff --git a/client/src/components/SearchInput/nestedUtils.tsx b/client/src/components/SearchInput/nestedUtils.tsx index 94b9e9149b4..5b6a01ed644 100644 --- a/client/src/components/SearchInput/nestedUtils.tsx +++ b/client/src/components/SearchInput/nestedUtils.tsx @@ -1,32 +1,116 @@ -// src/utils/nestedUtils.ts - /** * Retrieves a nested value from an object based on a dot-separated path. - * @param obj - The object to traverse. - * @param path - The dot-separated path string. + * Supports wildcard (`*`) in paths to match any index in arrays. + * + * @param obj - The object/array to traverse. + * @param path - The dot-separated path string (e.g. "myList.*.fieldName"). * @returns The value at the specified path or undefined if not found. */ export const getNestedValue = (obj: any, path: string): any => { - return path.split('.').reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), obj); + const keys = path.split('.'); + + return keys.reduce((acc, key) => { + if (acc == null) return undefined; + + if (key === '*') { + // If wildcard, return all matching values from the array + if (Array.isArray(acc)) { + return acc.map((item) => getNestedValue(item, keys.slice(1).join('.'))).filter((val) => val !== undefined); + } + // Wildcard on non-array is invalid + return undefined; + } + + return acc[key]; + }, obj); }; /** - * Sets a nested value in an object based on a dot-separated path. - * Returns a new object with the updated value, ensuring immutability. - * @param obj - The original object. - * @param path - The dot-separated path string. - * @param value - The value to set. - * @returns A new object with the updated value. + * Sets a nested value in an object (or array) based on a dot-separated path, + * returning a new top-level object/array to ensure immutability. + * Supports wildcard (`*`) in paths for updating all items in an array. + * + * @param obj - The original object/array. + * @param path - The dot-separated path string (e.g. "myList.*.fieldName"). + * @param value - The value to set at that path. + * @returns A new object (or array) with the updated value. */ export const setNestedValue = (obj: any, path: string, value: any): any => { - const keys = path.split('.'); - const newObj = { ...obj }; - let current = newObj; - for (let i = 0; i < keys.length - 1; i++) { - const key = keys[i]; - current[key] = { ...current[key] }; - current = current[key]; + const keys = path.split('.'); + + /** + * Recursively traverse `obj` based on the path segments. + */ + function cloneAndSet(current: any, index: number): any { + const key = keys[index]; + const isNumeric = !isNaN(Number(key)); + + // Handle wildcard (`*`) updates + if (key === '*') { + if (!Array.isArray(current)) { + console.error(`Wildcard '*' used on non-array at path: ${keys.slice(0, index).join('.')}`); + return current; + } + // Update all items in the array + return current.map((item) => cloneAndSet(item, index + 1)); } - current[keys[keys.length - 1]] = value; - return newObj; + + // Base case: if we're at the final segment, just return `value`. + if (index === keys.length - 1) { + if (Array.isArray(current) && isNumeric) { + const newArray = [...current]; + newArray[Number(key)] = value; + return newArray; + } + if (!Array.isArray(current) && !isNumeric) { + return { ...current, [key]: value }; + } + if (isNumeric) { + const arr: any[] = Array.isArray(current) ? [...current] : []; + arr[Number(key)] = value; + return arr; + } else { + return { ...(Array.isArray(current) ? {} : current), [key]: value }; + } + } + + // Recursively update the next level + const nextKey = keys[index + 1]; + const nextIsNumeric = !isNaN(Number(nextKey)); + + if (Array.isArray(current) && isNumeric) { + const newArray = [...current]; + const childVal = current[Number(key)]; + newArray[Number(key)] = cloneAndSet( + childVal !== undefined ? childVal : nextIsNumeric ? [] : {}, + index + 1, + ); + return newArray; + } else if (!Array.isArray(current) && !isNumeric) { + const newObj = { ...current }; + const childVal = current[key]; + newObj[key] = cloneAndSet( + childVal !== undefined ? childVal : nextIsNumeric ? [] : {}, + index + 1, + ); + return newObj; + } else { + if (isNumeric) { + const arr: any[] = []; + arr[Number(key)] = cloneAndSet(nextIsNumeric ? [] : {}, index + 1); + return arr; + } else { + return { + [key]: cloneAndSet(nextIsNumeric ? [] : {}, index + 1), + }; + } + } + } + + if (obj == null) { + const firstKeyIsNumeric = !isNaN(Number(keys[0])); + obj = firstKeyIsNumeric ? [] : {}; + } + + return cloneAndSet(obj, 0); }; diff --git a/client/src/screens/App.tsx b/client/src/screens/App.tsx index 9e85aace7b2..83465b1ccd9 100644 --- a/client/src/screens/App.tsx +++ b/client/src/screens/App.tsx @@ -7,6 +7,9 @@ import { useTripQueryVariables } from '../hooks/useTripQueryVariables.ts'; import { TimeZoneContext } from '../hooks/TimeZoneContext.ts'; import { LogoSection } from '../components/SearchBar/LogoSection.tsx'; import { InputFieldsSection } from '../components/SearchBar/InputFieldsSection.tsx'; +import TripQueryArguments from '../components/SearchInput/TripQueryArguments.tsx'; +import Sidebar from '../components/SearchInput/Sidebar.tsx'; +import ViewArgumentsRaw from "../components/SearchInput/ViewArgumentsRaw.tsx"; export function App() { const serverInfo = useServerInfo(); @@ -31,15 +34,20 @@ export function App() { >
- + + + + +
\ No newline at end of file diff --git a/client/src/static/img/info-circle.svg b/client/src/static/img/info-circle.svg new file mode 100644 index 00000000000..0689c0044ec --- /dev/null +++ b/client/src/static/img/info-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/static/img/input.svg b/client/src/static/img/input.svg new file mode 100644 index 00000000000..4ed4605b2c6 --- /dev/null +++ b/client/src/static/img/input.svg @@ -0,0 +1,8 @@ + + + diff --git a/client/src/static/img/json.svg b/client/src/static/img/json.svg new file mode 100644 index 00000000000..a92f3eec55b --- /dev/null +++ b/client/src/static/img/json.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/static/img/lap-timer.svg b/client/src/static/img/lap-timer.svg new file mode 100644 index 00000000000..1de0b3be6ce --- /dev/null +++ b/client/src/static/img/lap-timer.svg @@ -0,0 +1,8 @@ + + + diff --git a/client/src/static/img/route.svg b/client/src/static/img/route.svg new file mode 100644 index 00000000000..6699f08361b --- /dev/null +++ b/client/src/static/img/route.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/client/src/style.css b/client/src/style.css index e9fd49a92b5..3c6929a25ff 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -12,30 +12,8 @@ align-items: center; } -.logo-section { - grid-column: 1 / 2; - grid-row: 1 / 2; -} - -.input-section { - grid-column: 2 / 3; - grid-row: 1 / 2; -} - -.trip-section { - grid-column: 1 / 2; - grid-row: 2 / 3; -} - -.map-section { - grid-column: 2 / 3; - grid-row: 2 / 3; -} - .navbar-brand { color: #4078bc; - margin-top: 20px; - margin-right: 14px; font-size: 2rem; } @@ -103,12 +81,17 @@ margin: 30px 0 auto 0; } +.input-family { + display: flex; + align-items: center; + gap: 2px; +} + .swap-from-to img { width: 15px; } .logo-container { - width: 265px; display: flex; flex-direction: column; } @@ -120,13 +103,19 @@ text-align: left; } -.itinerary-list-container { +.logo-image { + margin-right: 2px; +} + +.left-pane-container { font-size: 12px; width: 100%; overflow-y: auto; + min-width: 300px; } -.itinerary-list-container .time-zone-info { + +.left-pane-container .time-zone-info { margin: 10px 20px; font-size: 12px; text-align: right; @@ -260,27 +249,9 @@ box-shadow: -2px 0 5px rgba(0, 0, 0, 0.2); } -.left-menu-container { - position: absolute; - top: 0; - left: 0; /* Align to the left side */ - width: 0; - height: 100%; - background-color: #f4f4f4; - overflow-x: hidden; - transition: 0.3s; /* Smooth transition for width */ - padding-top: 60px; - box-shadow: none; -} - -.left-menu-container.open { - width: 250px; /* Expand the sidebar when open */ - box-shadow: 2px 0 5px rgba(0, 0, 0, 0.2); /* Shadow on the right of the sidebar */ -} - .sidebar-button.right { position: absolute; - right: 0px; /* Default position when sidebar is closed */ + right: 0; /* Default position when sidebar is closed */ background: #ccc; color: white; border: none; @@ -296,24 +267,6 @@ right: 270px; /* Shifted position when sidebar is open */ } -.sidebar-button.left { - position: absolute; - left: 0px; /* Default position when sidebar is closed */ - background: #ccc; - color: white; - border: none; - border-radius: 4px; - padding: 10px; - cursor: pointer; - transition: - right 0.3s, - background-color 0.2s; /* Smooth transitions */ -} - -.sidebar-button.left.open { - left: 270px; /* Shifted position when sidebar is open */ -} - .sidebar-button.active { background: #fff; } @@ -342,19 +295,139 @@ input[type='number'] { cursor: pointer; } +.pagination-controls { + margin-top: 10px; + margin-bottom: 5px; +} + .default-tooltip-icon { - font-size: 14px; - color: #888; - border: 1px solid #ccc; - border-radius: 50%; - width: 18px; - height: 18px; + width: 10px; + height: 10px; +} +.argument-label { + padding-right: 2px; +} + +/* Sidebar Container */ +.sidebar-container { + display: flex; + width: 100%; + height: 100%; + border-top: black 1px solid; +} + +/* Sidebar Navigation */ +.sidebar { display: flex; + flex-direction: column; align-items: center; - justify-content: center; - background-color: #f9f9f9; + width: 40px; + background-color: #f7f7f7; + border-right: 1px solid #ccc; +} + +/* Sidebar Buttons */ +.sidebar-button { + cursor: pointer; + padding: 5px; + text-align: center; + border-radius: 8px; + margin: 5px 0; + background-color: transparent; + transition: background-color 0.3s ease; +} + +.sidebar-button:hover { + background-color: #e0e0e0; +} + +.sidebar-button.active { + background-color: #ddd; + font-weight: bold; +} + +/* Content Area */ +.sidebar-content { + flex: 1; + overflow-y: auto; + margin: 5px; +} + +.panel-header { + font-size: 24px; + text-align: center; + position: relative; + margin-bottom: 10px; +} + +.argument-list { + font-size: 12px; + line-height: 1; +} + +.argument-list button { + font-size: 13px; + padding: 5px 10px; + margin: 5px 0; + background-color: #007BFF; + color: white; + border: none; + border-radius: 3px; + cursor: pointer; +} + +.argument-list button:hover { + background-color: #0056b3; /* Darker on hover */ +} + + +.argument-list input[type="text"], +.argument-list input[type="number"], +.argument-list input[type="datetime-local"], +select { + font-size: 12px; + padding: 0; + margin: 0; + border: none; + border-bottom: 1px solid #ccc; /* Bottom border only */ + background: none; + box-sizing: border-box; +} + +.argument-list input[type="text"], +.argument-list input[type="number"] { + width: 50px; +} +.argument-list input[type="datetime-local"] { + width: 140px; +} + +.remove-argument { + margin-left: 2px; + color: red; + cursor: pointer; +} + +.reset-button { + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + /* The transform ensures the button is vertically centered + if your header has a fixed height or if text is multiline. */ +} + +.panel-header button { + font-size: 13px; + padding: 5px 10px; + margin: 5px 0; + background-color: #007BFF; + color: white; + border: none; + border-radius: 3px; + cursor: pointer; } -.default-tooltip-icon:hover { - background-color: #e6e6e6; +.panel-header button:hover { + background-color: #0056b3; /* Darker on hover */ } diff --git a/client/src/util/generate-arguments.cjs b/client/src/util/generate-arguments.cjs index ad66daebb8a..680fb05b92f 100644 --- a/client/src/util/generate-arguments.cjs +++ b/client/src/util/generate-arguments.cjs @@ -1,5 +1,3 @@ -const fs = require('fs'); -const path = require('path'); const { isScalarType, isInputObjectType, @@ -55,12 +53,14 @@ function resolveType(type, schema = new Set()) { } const fieldType = field.type; + const isList = isListType(fieldType); // Detect if the field is a list const fieldDefaultValue = field.defaultValue !== undefined ? field.defaultValue : null; // Include defaultValue consistently, setting it to null if not defined fieldTypes[fieldName] = { type: resolveType(fieldType, schema), - defaultValue: fieldDefaultValue + defaultValue: fieldDefaultValue, + isList, // Explicitly indicate if it's a list }; }); return { type: 'InputObject', name: namedType.name, fields: fieldTypes }; @@ -74,8 +74,8 @@ function resolveType(type, schema = new Set()) { /** * Plugin to generate a JSON file with all arguments from a specified query, * excluding deprecated arguments based on deprecationReason, - * and including their types and default values, - * breaking down complex types into primitives. + * and including their types, default values, + * and whether they support multiple selection. */ const generateTripArgsJsonPlugin = async (schema) => { try { @@ -105,16 +105,19 @@ const generateTripArgsJsonPlugin = async (schema) => { const argName = arg.name; const argType = resolveType(arg.type, schema); const argDefaultValue = arg.defaultValue !== undefined ? arg.defaultValue : null; + const isList = isListType(arg.type); // Detect if the argument is a list // Consistent representation for enum types if (argDefaultValue !== null) { argsJson[argName] = { type: argType, defaultValue: argDefaultValue, + isList, // Explicitly indicate if it's a list }; } else { argsJson[argName] = { type: argType, + isList, // Explicitly indicate if it's a list }; } }); From 47880552a8e3280e6a658e153a18d0939ec2dcb2 Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 8 Jan 2025 15:30:33 +0100 Subject: [PATCH 107/195] RentalvehicleFuel properties docs comments --- .../service/vehiclerental/model/RentalVehicleFuel.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java index 3b0349be0b8..720356f323c 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java @@ -11,9 +11,15 @@ */ public class RentalVehicleFuel { + /** + * Current fuel percentage, expressed from 0 to 1. + */ @Nullable public final Ratio percent; + /** + * Current fuel percentage, expressed from 0 to 1. + */ @Nullable public final Distance range; From ff8411a850d31efcc48cc7ecd9073a874b525248 Mon Sep 17 00:00:00 2001 From: JustCris Date: Thu, 9 Jan 2025 11:50:15 +0100 Subject: [PATCH 108/195] javadoc comment for Ratio class --- .../java/org/opentripplanner/transit/model/basic/Ratio.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java index 95c4260b078..1fa1ed7e7b8 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java @@ -2,6 +2,11 @@ import java.util.Objects; +/** + * Represents a ratio within the range [0, 1]. + * The class ensures that the ratio value, represented as a double, + * falls withing the specified range. + */ public class Ratio { private final Double ratio; From a994a5110c43a86866536701874ca40117635234 Mon Sep 17 00:00:00 2001 From: a-limyr Date: Thu, 9 Jan 2025 14:13:22 +0100 Subject: [PATCH 109/195] Nested arguments, list arguments, layout changes, reset button, exclude arguments logic, moved attribution control and added reset option for boolean arguments. --- client/package-lock.json | 131 +++++++++++++++--- .../SearchBar/InputFieldsSection.tsx | 12 +- 2 files changed, 121 insertions(+), 22 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 28ebc7f9840..d8616aae8e9 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -212,7 +212,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", @@ -265,7 +264,6 @@ "version": "7.26.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", - "dev": true, "dependencies": { "@babel/parser": "^7.26.3", "@babel/types": "^7.26.3", @@ -343,7 +341,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" @@ -424,7 +421,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -433,7 +429,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -464,7 +459,6 @@ "version": "7.26.3", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", - "dev": true, "dependencies": { "@babel/types": "^7.26.3" }, @@ -952,7 +946,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.25.9", "@babel/parser": "^7.25.9", @@ -966,7 +959,6 @@ "version": "7.26.4", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.3", @@ -984,7 +976,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "engines": { "node": ">=4" } @@ -993,7 +984,6 @@ "version": "7.26.3", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" @@ -1008,6 +998,114 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@envelop/core": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@envelop/core/-/core-5.0.2.tgz", @@ -2795,8 +2893,7 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -5340,7 +5437,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -7306,7 +7402,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "dependencies": { "hasown": "^2.0.2" }, @@ -7875,7 +7970,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -8402,8 +8496,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/murmurhash-js": { "version": "1.0.0", @@ -8994,8 +9087,7 @@ "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -9444,7 +9536,6 @@ "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", diff --git a/client/src/components/SearchBar/InputFieldsSection.tsx b/client/src/components/SearchBar/InputFieldsSection.tsx index ff54af0e16e..e1819dcaae2 100644 --- a/client/src/components/SearchBar/InputFieldsSection.tsx +++ b/client/src/components/SearchBar/InputFieldsSection.tsx @@ -29,9 +29,17 @@ export function InputFieldsSection({ return (
- + - +
From c25fab8c83618513606887cf9a95feee6fbc34af Mon Sep 17 00:00:00 2001 From: a-limyr Date: Thu, 9 Jan 2025 14:22:08 +0100 Subject: [PATCH 110/195] Fixed some styling --- client/src/components/MapView/LayerControl.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/client/src/components/MapView/LayerControl.tsx b/client/src/components/MapView/LayerControl.tsx index aedab6b4d09..42d18d4e1f6 100644 --- a/client/src/components/MapView/LayerControl.tsx +++ b/client/src/components/MapView/LayerControl.tsx @@ -122,16 +122,11 @@ const LayerControl: React.FC = ({ return (
From 67781697cf9e42ec080936d0047e0f88056d4297 Mon Sep 17 00:00:00 2001 From: JustCris Date: Thu, 9 Jan 2025 16:14:21 +0100 Subject: [PATCH 111/195] check getCurrentFuelPercent null value and drop NPE --- .../datasources/GbfsFreeVehicleStatusMapper.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index f2fd9315dd3..eb5610646fe 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -61,14 +61,16 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { : null; Ratio fuelPercent = null; try { - fuelPercent = new Ratio(vehicle.getCurrentFuelPercent()); + if (vehicle.getCurrentFuelPercent() != null) { + fuelPercent = new Ratio(vehicle.getCurrentFuelPercent()); + } } catch (IllegalArgumentException e) { LOG.warn( "Current fuel percent value not valid: {} - {}", vehicle.getCurrentFuelPercent(), e.getMessage() ); - } catch (NullPointerException e) {} + } Distance rangeMeters = null; try { rangeMeters = From 62e671f0f8a7758fbb5351d14a38a9b669cf25ba Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 9 Jan 2025 20:33:20 +0100 Subject: [PATCH 112/195] Example on factory method with validation error handler passed in as an argument. --- .../transit/model/basic/Ratio.java | 38 +++++++++++++++++-- .../GbfsFreeVehicleStatusMapper.java | 25 ++++++------ .../routing/core/RatioTest.java | 14 +++---- 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java index 1fa1ed7e7b8..7fb5d4cb1dc 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java @@ -1,6 +1,9 @@ package org.opentripplanner.transit.model.basic; import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import javax.annotation.Nullable; /** * Represents a ratio within the range [0, 1]. @@ -11,12 +14,39 @@ public class Ratio { private final Double ratio; - public Ratio(Double ratio) { - if (ratio < 0d || ratio > 1d) { - throw new IllegalArgumentException("Ratio must be in range [0,1]"); + private Ratio(double ratio) { + this.ratio = ratio; + } + + /** + * This method is similar to {@link #of(double, Consumer)}, but throws an + * {@link IllegalArgumentException} if the ratio is not valid. + */ + public static Ratio of(double ratio) { + return of( + ratio, + errMsg -> { + throw new IllegalArgumentException(errMsg); + } + ) + .orElseThrow(); + } + + public static Optional of(double ratio, Consumer validationErrorHandler) { + if (ratio >= 0d && ratio <= 1d) { + return Optional.of(new Ratio(ratio)); } + else { + validationErrorHandler.accept("Ratio must be in range [0,1], but was: " + ratio); + return Optional.empty(); + } + } - this.ratio = ratio; + public static Optional ofBoxed(@Nullable Double ratio, Consumer validationErrorHandler) { + if (ratio == null) { + return Optional.empty(); + } + return of(ratio, validationErrorHandler); } @Override diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index eb5610646fe..3390b50e17b 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -17,12 +17,14 @@ import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.basic.Ratio; import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.utils.logging.Throttle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class GbfsFreeVehicleStatusMapper { private static final Logger LOG = LoggerFactory.getLogger(GbfsFreeVehicleStatusMapper.class); + private static final Throttle FUEL_PERCENT_LOG_THROTTLE = Throttle.ofOneMinute(); private final VehicleRentalSystem system; @@ -59,18 +61,17 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { vehicle.getLastReported() != null ? Instant.ofEpochSecond((long) (double) vehicle.getLastReported()) : null; - Ratio fuelPercent = null; - try { - if (vehicle.getCurrentFuelPercent() != null) { - fuelPercent = new Ratio(vehicle.getCurrentFuelPercent()); - } - } catch (IllegalArgumentException e) { - LOG.warn( - "Current fuel percent value not valid: {} - {}", + + var fuelRatio = Ratio + .ofBoxed( vehicle.getCurrentFuelPercent(), - e.getMessage() - ); - } + validationErrorMessage -> + FUEL_PERCENT_LOG_THROTTLE.throttle(() -> + LOG.warn("'currentFuelPercent' is not valid. Details: " + validationErrorMessage) + ) + ) + .orElse(null); + Distance rangeMeters = null; try { rangeMeters = @@ -94,7 +95,7 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { ) { return null; } - rentalVehicle.fuel = new RentalVehicleFuel(fuelPercent, rangeMeters); + rentalVehicle.fuel = new RentalVehicleFuel(fuelRatio, rangeMeters); rentalVehicle.pricingPlanId = vehicle.getPricingPlanId(); GBFSRentalUris rentalUris = vehicle.getRentalUris(); if (rentalUris != null) { diff --git a/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java b/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java index 4195fc5c5c0..74a8fe6060a 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/RatioTest.java @@ -18,22 +18,22 @@ public class RatioTest { @Test void validRatios() { - assertDoesNotThrow(() -> new Ratio(HALF)); - assertDoesNotThrow(() -> new Ratio(ZERO)); - assertDoesNotThrow(() -> new Ratio(ONE)); + assertDoesNotThrow(() -> Ratio.of(HALF)); + assertDoesNotThrow(() -> Ratio.of(ZERO)); + assertDoesNotThrow(() -> Ratio.of(ONE)); } @Test void invalidRatios() { - assertThrows(IllegalArgumentException.class, () -> new Ratio(TOO_HIGH)); - assertThrows(IllegalArgumentException.class, () -> new Ratio(TOO_LOW)); + assertThrows(IllegalArgumentException.class, () -> Ratio.of(TOO_HIGH)); + assertThrows(IllegalArgumentException.class, () -> Ratio.of(TOO_LOW)); } @Test void testHashCode() { - Ratio half = new Ratio(HALF); + Ratio half = Ratio.of(HALF); - Ratio half2 = new Ratio(HALF); + Ratio half2 = Ratio.of(HALF); assertEquals(half.hashCode(), half2.hashCode()); Double halfDouble = 2d; From f7d6b36995916a864eb563a8e0c28c4f8ecaa4de Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Thu, 9 Jan 2025 20:44:21 +0100 Subject: [PATCH 113/195] Cleanup Ratio implementation --- .../transit/model/basic/Ratio.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java index 7fb5d4cb1dc..0ca16391475 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Ratio.java @@ -1,9 +1,9 @@ package org.opentripplanner.transit.model.basic; -import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import javax.annotation.Nullable; +import org.opentripplanner.utils.lang.DoubleUtils; /** * Represents a ratio within the range [0, 1]. @@ -12,10 +12,10 @@ */ public class Ratio { - private final Double ratio; + private final double ratio; private Ratio(double ratio) { - this.ratio = ratio; + this.ratio = DoubleUtils.roundTo3Decimals(ratio); } /** @@ -35,14 +35,16 @@ public static Ratio of(double ratio) { public static Optional of(double ratio, Consumer validationErrorHandler) { if (ratio >= 0d && ratio <= 1d) { return Optional.of(new Ratio(ratio)); - } - else { + } else { validationErrorHandler.accept("Ratio must be in range [0,1], but was: " + ratio); return Optional.empty(); } } - public static Optional ofBoxed(@Nullable Double ratio, Consumer validationErrorHandler) { + public static Optional ofBoxed( + @Nullable Double ratio, + Consumer validationErrorHandler + ) { if (ratio == null) { return Optional.empty(); } @@ -50,25 +52,25 @@ public static Optional ofBoxed(@Nullable Double ratio, Consumer v } @Override - public boolean equals(Object other) { - if (other instanceof Ratio ratio) { - return ratio.ratio == this.ratio; - } else { + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { return false; } + var other = (Ratio) o; + return Double.compare(ratio, other.ratio) == 0; } @Override public int hashCode() { - return Objects.hash(this.ratio); + return Double.hashCode(ratio); } @Override public String toString() { - return this.ratio.toString(); + return Double.toString(ratio); } - public Double asDouble() { + public double asDouble() { return ratio; } } From 4248ffe1644ae50d1970d26310b5593acf7fe508 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 10 Jan 2025 15:38:12 +0100 Subject: [PATCH 114/195] Fix mapping in Transmodel API --- .../mapping/RelativeDirectionMapper.java | 33 +++++++++++++++++++ .../model/plan/PathGuidanceType.java | 5 ++- .../apis/transmodel/model/EnumTypesTest.java | 21 ++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 application/src/main/java/org/opentripplanner/apis/transmodel/mapping/RelativeDirectionMapper.java diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/RelativeDirectionMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/RelativeDirectionMapper.java new file mode 100644 index 00000000000..3228cb914df --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/RelativeDirectionMapper.java @@ -0,0 +1,33 @@ +package org.opentripplanner.apis.transmodel.mapping; + +import org.opentripplanner.model.plan.RelativeDirection; + +/** + * This mapper makes sure that only those values are returned which have a mapping in the Transmodel API, + * as we don't really want to return all of them. + */ +public class RelativeDirectionMapper { + + public static RelativeDirection map(RelativeDirection relativeDirection) { + return switch (relativeDirection) { + case DEPART, + SLIGHTLY_LEFT, + HARD_LEFT, + LEFT, + CONTINUE, + SLIGHTLY_RIGHT, + RIGHT, + HARD_RIGHT, + CIRCLE_CLOCKWISE, + CIRCLE_COUNTERCLOCKWISE, + ELEVATOR, + UTURN_LEFT, + UTURN_RIGHT -> relativeDirection; + // for these the Transmodel API doesn't have a mapping. should it? + case ENTER_STATION, + EXIT_STATION, + ENTER_OR_EXIT_STATION, + FOLLOW_SIGNS -> RelativeDirection.CONTINUE; + }; + } +} diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java index 86c8359c026..8840e8fedb8 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/PathGuidanceType.java @@ -5,6 +5,7 @@ import graphql.schema.GraphQLList; import graphql.schema.GraphQLNonNull; import graphql.schema.GraphQLObjectType; +import org.opentripplanner.apis.transmodel.mapping.RelativeDirectionMapper; import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.model.plan.WalkStep; @@ -31,7 +32,9 @@ public static GraphQLObjectType create(GraphQLObjectType elevationStepType) { .name("relativeDirection") .description("The relative direction of this step.") .type(EnumTypes.RELATIVE_DIRECTION) - .dataFetcher(environment -> ((WalkStep) environment.getSource()).getRelativeDirection()) + .dataFetcher(environment -> + RelativeDirectionMapper.map(((WalkStep) environment.getSource()).getRelativeDirection()) + ) .build() ) .field( 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); From bba0903ac2bc1252c7ab204a39e1e80cc3a8a57a Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 13 Jan 2025 22:48:42 +0100 Subject: [PATCH 115/195] Apply suggestions from code review Co-authored-by: Leonard Ehrenfried --- .../injectdoc/ApiDocumentationProfile.java | 2 +- .../injectdoc/CustomDocumentation.java | 2 +- .../injectdoc/InjectCustomDocumentation.java | 2 +- .../InjectCustomDocumentationTest.java | 2 +- doc/user/RouterConfiguration.md | 82 +++++++++---------- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java index 1ed63a9bd96..93452589ace 100644 --- a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java +++ b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/ApiDocumentationProfile.java @@ -11,7 +11,7 @@ public enum ApiDocumentationProfile implements DocumentedEnum textMap; /** - * Pacakge local to be unit-testable + * Package local to be unit-testable */ CustomDocumentation(Map textMap) { this.textMap = textMap; diff --git a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java index f2793a4e6c3..edc14986c22 100644 --- a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java +++ b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java @@ -21,7 +21,7 @@ import java.util.function.BiFunction; /** - * This is GraphQL visitor witch inject custom documentation on types and fields. + * This is GraphQL visitor which injects custom documentation on types and fields. */ public class InjectCustomDocumentation extends GraphQLTypeVisitorStub 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 index 4231d7079e5..44318d613a4 100644 --- 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 @@ -21,7 +21,7 @@ import org.opentripplanner._support.text.TextAssertions; /** - * This test read in a schema file, inject documentation and convert the + * 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. diff --git a/doc/user/RouterConfiguration.md b/doc/user/RouterConfiguration.md index 10065eab6db..cda4a81143f 100644 --- a/doc/user/RouterConfiguration.md +++ b/doc/user/RouterConfiguration.md @@ -31,46 +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 | -|    [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 support 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 | +| 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 | @@ -118,7 +118,7 @@ domain, these are set in the routing request. 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 support this feature. +Currently, ONLY the Transmodel API supports this feature. - `default` Default documentation is used. From 3dd1efeeb09dbcf6f01ccf635d9eec686c4ea245 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 13 Jan 2025 23:09:57 +0100 Subject: [PATCH 116/195] refactor: Cleanup names in InjectCustomDocumentation as requested in review. --- .../graphql/injectdoc/InjectCustomDocumentation.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java index edc14986c22..4e98f202d90 100644 --- a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java +++ b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/InjectCustomDocumentation.java @@ -158,15 +158,15 @@ private TraversalControl fieldDoc( var fieldName = field.getName(); var typeName = parent.getName(); - Optional f1 = customDocumentation + Optional withDescription = customDocumentation .fieldDescription(typeName, fieldName, field.getDescription()) .map(doc -> setDescription.apply(field, doc)); - Optional f2 = customDocumentation + Optional withDeprecated = customDocumentation .fieldDeprecatedReason(typeName, fieldName, originalDeprecatedReason) - .map(doc -> setDeprecatedReason.apply(f1.orElse(field), doc)); + .map(doc -> setDeprecatedReason.apply(withDescription.orElse(field), doc)); - f2.or(() -> f1).ifPresent(f -> changeNode(context, f)); + withDeprecated.or(() -> withDescription).ifPresent(f -> changeNode(context, f)); return CONTINUE; } From 106ab1339473b3fede773e38c01c1c6301ae4709 Mon Sep 17 00:00:00 2001 From: Thomas Gran Date: Mon, 13 Jan 2025 23:23:19 +0100 Subject: [PATCH 117/195] refactor: Fix char encoding for reading property file. --- .../apis/support/graphql/injectdoc/CustomDocumentation.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentation.java b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentation.java index b688a546a56..4afe0cf6952 100644 --- a/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentation.java +++ b/application/src/main/java/org/opentripplanner/apis/support/graphql/injectdoc/CustomDocumentation.java @@ -1,6 +1,8 @@ package org.opentripplanner.apis.support.graphql.injectdoc; import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -142,7 +144,7 @@ private static Map loadCustomDocumentationFromPropertiesFile( throw new OtpAppException("Resource not found: %s", resource); } var props = new Properties(); - props.load(input); + props.load(new InputStreamReader(input, StandardCharsets.UTF_8)); Map map = new HashMap<>(); for (String key : props.stringPropertyNames()) { From 57e03b42d73f8463fc958e25df4aad8675ea6a29 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 14 Jan 2025 10:38:48 +0200 Subject: [PATCH 118/195] Change default values from Duration.ZERO to null. --- .../graph_builder/module/DirectTransferGenerator.java | 4 ++-- .../graph_builder/module/TransferParameters.java | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index c9b6c04bfcc..8e0682beb43 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -327,7 +327,7 @@ private TransferConfiguration parseTransferParameters(NearbyStopFinder nearbySto defaultTransferRequests.add(transferProfile); // Set mode-specific maxTransferDuration, if it is set in the build config. Duration maxTransferDuration = transferParameters.maxTransferDuration(); - if (maxTransferDuration != Duration.ZERO) { + if (maxTransferDuration != null) { defaultNearbyStopFinderForMode.put(mode, createNearbyStopFinder(maxTransferDuration)); } else { defaultNearbyStopFinderForMode.put(mode, nearbyStopFinder); @@ -335,7 +335,7 @@ private TransferConfiguration parseTransferParameters(NearbyStopFinder nearbySto } // Create transfers between carsAllowedStops for the specific mode if carsAllowedStopMaxTransferDuration is set in the build config. Duration carsAllowedStopMaxTransferDuration = transferParameters.carsAllowedStopMaxTransferDuration(); - if (carsAllowedStopMaxTransferDuration != Duration.ZERO) { + if (carsAllowedStopMaxTransferDuration != null) { carsAllowedStopTransferRequests.add(transferProfile); carsAllowedStopNearbyStopFinderForMode.put( mode, diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java b/application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java index 89553eb1e39..0d7b31b4a81 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/TransferParameters.java @@ -3,13 +3,16 @@ import java.time.Duration; import org.opentripplanner.utils.tostring.ToStringBuilder; +/** + * Mode-specific parameters for transfers. + */ public record TransferParameters( Duration maxTransferDuration, Duration carsAllowedStopMaxTransferDuration, boolean disableDefaultTransfers ) { - public static final Duration DEFAULT_MAX_TRANSFER_DURATION = Duration.ZERO; - public static final Duration DEFAULT_CARS_ALLOWED_STOP_MAX_TRANSFER_DURATION = Duration.ZERO; + public static final Duration DEFAULT_MAX_TRANSFER_DURATION = null; + public static final Duration DEFAULT_CARS_ALLOWED_STOP_MAX_TRANSFER_DURATION = null; public static final boolean DEFAULT_DISABLE_DEFAULT_TRANSFERS = false; TransferParameters(Builder builder) { From 7de4a39f66877d7951bc66a0216100f4bcb93a74 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 14 Jan 2025 12:18:12 +0200 Subject: [PATCH 119/195] Add documentation. --- .../buildconfig/TransferParametersMapper.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java index 8a65ccdf712..e9cce1a367d 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferParametersMapper.java @@ -26,7 +26,20 @@ public static TransferParameters map(NodeAdapter c) { """ This parameter configures additional transfers to be calculated for the specified mode only between stops that have trips with cars. The transfers are calculated for the mode in a range based on the given duration. -By default, these transfers are not calculated for the specified mode. +By default, these transfers are not calculated unless specified for a mode with this field. + +Calculating transfers only between stops that have trips with cars can be useful with car ferries, for example. +Using transit with cars can only occur between certain stops. +These kinds of stops require support for loading cars into ferries, for example. +The default transfers are calculated based on a configurable range (configurable by using the `maxTransferDuration` field) +which limits transfers from stops to only be calculated to other stops that are in range. +When compared to walking, using a car can cover larger distances within the same duration specified in the `maxTransferDuration` field. +This can lead to large amounts of transfers calculated between stops that do not require car transfers between them. +This in turn can lead to a large increase in memory for the stored graph, depending on the data used in the graph. + +For cars, using this parameter in conjunction with `disableDefaultTransfers` allows calculating transfers only between relevant stops. +For bikes, using this parameter can enable transfers between ferry stops that would normally not be in range. +In Finland this is useful for bike routes that use ferries near the Turku archipelago, for example. """ ) .since(V2_7) @@ -36,6 +49,16 @@ public static TransferParameters map(NodeAdapter c) { c .of("disableDefaultTransfers") .summary("This disables default transfer calculations.") + .description( + """ +The default transfers are calculated based on a configurable range (configurable by using the `maxTransferDuration` field) +which limits transfers from stops to only be calculated to other stops that are in range. +This parameter disables these transfers. +A motivation to disable default transfers could be related to using the `carsAllowedStopMaxTransferDuration` field which only +calculates transfers between stops that have trips with cars. +For example, when using the `carsAllowedStopMaxTransferDuration` field with cars, the default transfers can be redundant. +""" + ) .since(V2_7) .asBoolean(TransferParameters.DEFAULT_DISABLE_DEFAULT_TRANSFERS) ); From 8e8797475a49abd886b86048184b8094e804b096 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 14 Jan 2025 12:24:16 +0200 Subject: [PATCH 120/195] Rename transferParameters to transferParametersForMode. --- .../graph_builder/module/DirectTransferGenerator.java | 4 ++-- .../org/opentripplanner/standalone/config/BuildConfig.java | 2 +- .../standalone/config/buildconfig/TransferConfig.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index 8e0682beb43..a0c9476bff2 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -301,7 +301,7 @@ private TransferConfiguration parseTransferParameters(NearbyStopFinder nearbySto /* These are used for calculating transfers only between carsAllowedStops. */ HashMap carsAllowedStopNearbyStopFinderForMode = new HashMap<>(); - // Check that the mode specified in transferParameters can also be found in transferRequests. + // Check that the mode specified in transferParametersForMode can also be found in transferRequests. for (StreetMode mode : transferParametersForMode.keySet()) { if ( !transferRequests @@ -309,7 +309,7 @@ private TransferConfiguration parseTransferParameters(NearbyStopFinder nearbySto .anyMatch(transferProfile -> transferProfile.journey().transfer().mode() == mode) ) { throw new IllegalArgumentException( - String.format("Mode %s is used in transferParameters but not in transferRequests", mode) + String.format("Mode %s is used in transferParametersForMode but not in transferRequests", mode) ); } } diff --git a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index f9efbb8faa4..af8b1db6b9d 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -293,7 +293,7 @@ When set to true (it is false by default), the elevation module will include the "Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph." ) .asDuration(Duration.ofMinutes(30)); - transferParametersForMode = TransferConfig.map(root, "transferParameters"); + transferParametersForMode = TransferConfig.map(root, "transferParametersForMode"); maxStopToShapeSnapDistance = root .of("maxStopToShapeSnapDistance") diff --git a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java index 39683e16c18..5549e009c48 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/buildconfig/TransferConfig.java @@ -25,7 +25,7 @@ public static Map map(NodeAdapter root, String p ```JSON // build-config.json { - "transferParameters": { + "transferParametersForMode": { "CAR": { "disableDefaultTransfers": true, "carsAllowedStopMaxTransferDuration": "3h" From d0fbae9a9b2537e0724c38cf55ca570f2bcfbdd9 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 14 Jan 2025 12:31:35 +0200 Subject: [PATCH 121/195] Change documentation for maxTransferDuration. --- .../graph_builder/module/DirectTransferGenerator.java | 5 ++++- .../opentripplanner/standalone/config/BuildConfig.java | 2 +- doc/user/BuildConfiguration.md | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index a0c9476bff2..bcede5798c4 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -309,7 +309,10 @@ private TransferConfiguration parseTransferParameters(NearbyStopFinder nearbySto .anyMatch(transferProfile -> transferProfile.journey().transfer().mode() == mode) ) { throw new IllegalArgumentException( - String.format("Mode %s is used in transferParametersForMode but not in transferRequests", mode) + String.format( + "Mode %s is used in transferParametersForMode but not in transferRequests", + mode + ) ); } } diff --git a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java index af8b1db6b9d..f8533bd75ca 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/BuildConfig.java @@ -290,7 +290,7 @@ When set to true (it is false by default), the elevation module will include the .of("maxTransferDuration") .since(V2_1) .summary( - "Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph." + "Transfers up to this duration with a mode-specific speed value will be pre-calculated and included in the Graph." ) .asDuration(Duration.ofMinutes(30)); transferParametersForMode = TransferConfig.map(root, "transferParametersForMode"); diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index 50c1dbab21a..0c7beedaff4 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -32,7 +32,7 @@ Sections follow that describe particular settings in more depth. | [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | | maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | | [maxStopToShapeSnapDistance](#maxStopToShapeSnapDistance) | `double` | Maximum distance between route shapes and their stops. | *Optional* | `150.0` | 2.1 | -| maxTransferDuration | `duration` | Transfers up to this duration with the default walk speed value will be pre-calculated and included in the Graph. | *Optional* | `"PT30M"` | 2.1 | +| maxTransferDuration | `duration` | Transfers up to this duration with a mode-specific speed value will be pre-calculated and included in the Graph. | *Optional* | `"PT30M"` | 2.1 | | [multiThreadElevationCalculations](#multiThreadElevationCalculations) | `boolean` | Configuring multi-threading during elevation calculations. | *Optional* | `false` | 2.0 | | [osmCacheDataInMem](#osmCacheDataInMem) | `boolean` | If OSM data should be cached in memory during processing. | *Optional* | `false` | 2.0 | | [osmNaming](#osmNaming) | `enum` | A custom OSM namer to use. | *Optional* | `"default"` | 1.5 | @@ -90,7 +90,7 @@ Sections follow that describe particular settings in more depth. | osmDefaults | `object` | Default properties for OpenStreetMap feeds. | *Optional* | | 2.2 | |    [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 | -| [transferParameters](#transferParameters) | `enum map of object` | Configures mode-specific properties for transfer calculations. | *Optional* | | 2.7 | +| [transferParametersForMode](#transferParametersForMode) | `enum map of object` | Configures mode-specific properties for transfer calculations. | *Optional* | | 2.7 | | [transferRequests](RouteRequest.md) | `object[]` | Routing requests to use for pre-calculating stop-to-stop transfers. | *Optional* | | 2.1 | | [transitFeeds](#transitFeeds) | `object[]` | Scan for transit data files | *Optional* | | 2.2 | |    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | @@ -953,7 +953,7 @@ The named set of mapping rules applied when parsing OSM tags. Overrides the valu The named set of mapping rules applied when parsing OSM tags. -

transferParameters

+

transferParametersForMode

**Since version:** `2.7` ∙ **Type:** `enum map of object` ∙ **Cardinality:** `Optional` **Path:** / @@ -969,7 +969,7 @@ To configure mode-specific parameters, the modes should also be used in the `tra ```JSON // build-config.json { - "transferParameters": { + "transferParametersForMode": { "CAR": { "disableDefaultTransfers": true, "carsAllowedStopMaxTransferDuration": "3h" From f38506128893efeb35431e055b5436b67f0d8841 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 14 Jan 2025 12:45:02 +0200 Subject: [PATCH 122/195] Add documentation to standalone build-config.json and NodeAdapterHelper. --- .../doc/framework/NodeAdapterHelper.java | 1 + .../standalone/config/build-config.json | 10 + doc/user/BuildConfiguration.md | 294 ++++++++++++------ 3 files changed, 207 insertions(+), 98 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/generate/doc/framework/NodeAdapterHelper.java b/application/src/test/java/org/opentripplanner/generate/doc/framework/NodeAdapterHelper.java index 7df4429da15..a0eb9180c97 100644 --- a/application/src/test/java/org/opentripplanner/generate/doc/framework/NodeAdapterHelper.java +++ b/application/src/test/java/org/opentripplanner/generate/doc/framework/NodeAdapterHelper.java @@ -15,6 +15,7 @@ public class NodeAdapterHelper { new AnchorAbbreviation("od.", "osmDefaults."), new AnchorAbbreviation("lfp.", "localFileNamePatterns."), new AnchorAbbreviation("u.", "updaters."), + new AnchorAbbreviation("tpfm.", "transferParametersForMode."), new AnchorAbbreviation("0.", "[0]."), new AnchorAbbreviation("1.", "[1].") ); diff --git a/application/src/test/resources/standalone/config/build-config.json b/application/src/test/resources/standalone/config/build-config.json index 11ea4a36b2e..9a32b3bd892 100644 --- a/application/src/test/resources/standalone/config/build-config.json +++ b/application/src/test/resources/standalone/config/build-config.json @@ -82,5 +82,15 @@ "emissions": { "carAvgCo2PerKm": 170, "carAvgOccupancy": 1.3 + }, + "transferParametersForMode": { + "CAR": { + "disableDefaultTransfers": true, + "carsAllowedStopMaxTransferDuration": "3h" + }, + "BIKE": { + "maxTransferDuration": "30m", + "carsAllowedStopMaxTransferDuration": "3h" + } } } diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md index 0c7beedaff4..58e2e00a65e 100644 --- a/doc/user/BuildConfiguration.md +++ b/doc/user/BuildConfiguration.md @@ -17,104 +17,112 @@ Sections follow that describe particular settings in more depth. -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|--------------------------------------------------------------------------|:--------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|-----------------------------------|:-----:| -| [areaVisibility](#areaVisibility) | `boolean` | Perform visibility calculations. | *Optional* | `false` | 1.5 | -| [buildReportDir](#buildReportDir) | `uri` | URI to the directory where the graph build report should be written to. | *Optional* | | 2.0 | -| [configVersion](#configVersion) | `string` | Deployment version of the *build-config.json*. | *Optional* | | 2.1 | -| [dataImportReport](#dataImportReport) | `boolean` | Generate nice HTML report of Graph errors/warnings | *Optional* | `false` | 2.0 | -| [distanceBetweenElevationSamples](#distanceBetweenElevationSamples) | `double` | The distance between elevation samples in meters. | *Optional* | `10.0` | 2.0 | -| embedRouterConfig | `boolean` | Embed the Router config in the graph, which allows it to be sent to a server fully configured over the wire. | *Optional* | `true` | 2.0 | -| [graph](#graph) | `uri` | URI to the graph object file for reading and writing. | *Optional* | | 2.0 | -| [gsCredentials](#gsCredentials) | `string` | Local file system path to Google Cloud Platform service accounts credentials file. | *Optional* | | 2.0 | -| [includeEllipsoidToGeoidDifference](#includeEllipsoidToGeoidDifference) | `boolean` | Include the Ellipsoid to Geoid difference in the calculations of every point along every StreetWithElevationEdge. | *Optional* | `false` | 2.0 | -| maxAreaNodes | `integer` | Visibility calculations for an area will not be done if there are more nodes than this limit. | *Optional* | `150` | 2.1 | -| [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | -| maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | -| [maxStopToShapeSnapDistance](#maxStopToShapeSnapDistance) | `double` | Maximum distance between route shapes and their stops. | *Optional* | `150.0` | 2.1 | -| maxTransferDuration | `duration` | Transfers up to this duration with a mode-specific speed value will be pre-calculated and included in the Graph. | *Optional* | `"PT30M"` | 2.1 | -| [multiThreadElevationCalculations](#multiThreadElevationCalculations) | `boolean` | Configuring multi-threading during elevation calculations. | *Optional* | `false` | 2.0 | -| [osmCacheDataInMem](#osmCacheDataInMem) | `boolean` | If OSM data should be cached in memory during processing. | *Optional* | `false` | 2.0 | -| [osmNaming](#osmNaming) | `enum` | A custom OSM namer to use. | *Optional* | `"default"` | 1.5 | -| platformEntriesLinking | `boolean` | Link unconnected entries to public transport platforms. | *Optional* | `false` | 2.0 | -| [readCachedElevations](#readCachedElevations) | `boolean` | Whether to read cached elevation data. | *Optional* | `true` | 2.0 | -| staticBikeParkAndRide | `boolean` | Whether we should create bike P+R stations from OSM data. | *Optional* | `false` | 1.5 | -| staticParkAndRide | `boolean` | Whether we should create car P+R stations from OSM data. | *Optional* | `true` | 1.5 | -| stopConsolidationFile | `uri` | Name of the CSV-formatted file in the build directory which contains the configuration for stop consolidation. | *Optional* | | 2.5 | -| [streetGraph](#streetGraph) | `uri` | URI to the street graph object file for reading and writing. | *Optional* | | 2.0 | -| [subwayAccessTime](#subwayAccessTime) | `double` | Minutes necessary to reach stops served by trips on routes of route_type=1 (subway) from the street. | *Optional* | `2.0` | 1.5 | -| [transitModelTimeZone](#transitModelTimeZone) | `time-zone` | Time zone for the graph. | *Optional* | | 2.2 | -| [transitServiceEnd](#transitServiceEnd) | `duration` | Limit the import of transit services to the given end date. | *Optional* | `"P3Y"` | 2.0 | -| [transitServiceStart](#transitServiceStart) | `duration` | Limit the import of transit services to the given START date. | *Optional* | `"-P1Y"` | 2.0 | -| [writeCachedElevations](#writeCachedElevations) | `boolean` | Reusing elevation data from previous builds | *Optional* | `false` | 2.0 | -| [boardingLocationTags](#boardingLocationTags) | `string[]` | What OSM tags should be looked on for the source of matching stops to platforms and stops. | *Optional* | | 2.2 | -| [dataOverlay](sandbox/DataOverlay.md) | `object` | Config for the DataOverlay Sandbox module | *Optional* | | 2.2 | -| [dem](#dem) | `object[]` | Specify parameters for DEM extracts. | *Optional* | | 2.2 | -|       [elevationUnitMultiplier](#dem_0_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. Overrides the value specified in `demDefaults`. | *Optional* | `1.0` | 2.3 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -| demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | -|    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | -| [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | -| [emissions](sandbox/Emissions.md) | `object` | Emissions configuration. | *Optional* | | 2.5 | -| [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | -| gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | -|    blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. | *Optional* | `true` | 2.3 | -|    [discardMinTransferTimes](#gd_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. | *Optional* | `false` | 2.3 | -|    maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. | *Optional* | `200` | 2.3 | -|    removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. | *Optional* | `true` | 2.3 | -|    [stationTransferPreference](#gd_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. | *Optional* | `"allowed"` | 2.3 | -| islandPruning | `object` | Settings for fixing street graph connectivity errors | *Optional* | | 2.3 | -|    [adaptivePruningDistance](#islandPruning_adaptivePruningDistance) | `integer` | Search distance for analyzing islands in pruning. | *Optional* | `250` | 2.3 | -|    [adaptivePruningFactor](#islandPruning_adaptivePruningFactor) | `double` | Defines how much pruning thresholds grow maximally by distance. | *Optional* | `50.0` | 2.3 | -|    [islandWithStopsMaxSize](#islandPruning_islandWithStopsMaxSize) | `integer` | When a graph island with stops in it should be pruned. | *Optional* | `2` | 2.3 | -|    [islandWithoutStopsMaxSize](#islandPruning_islandWithoutStopsMaxSize) | `integer` | When a graph island without stops should be pruned. | *Optional* | `10` | 2.3 | -| [localFileNamePatterns](#localFileNamePatterns) | `object` | Patterns for matching OTP file types in the base directory | *Optional* | | 2.0 | -|    [dem](#lfp_dem) | `regexp` | Pattern for matching elevation DEM files. | *Optional* | `"(?i)\.tiff?$"` | 2.0 | -|    [gtfs](#lfp_gtfs) | `regexp` | Patterns for matching GTFS zip-files or directories. | *Optional* | `"(?i)gtfs"` | 2.0 | -|    [netex](#lfp_netex) | `regexp` | Patterns for matching NeTEx zip files or directories. | *Optional* | `"(?i)netex"` | 2.0 | -|    [osm](#lfp_osm) | `regexp` | Pattern for matching Open Street Map input files. | *Optional* | `"(?i)(\.pbf¦\.osm¦\.osm\.xml)$"` | 2.0 | -| netexDefaults | `object` | The netexDefaults section allows you to specify default properties for NeTEx files. | *Optional* | | 2.2 | -|    feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Optional* | `"NETEX"` | 2.2 | -|    [groupFilePattern](#nd_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | -|    ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | -|    [ignoreFilePattern](#nd_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | -|    ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | -|    noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | -|    [sharedFilePattern](#nd_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | -|    [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 | -|       [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 | -|    [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 | -| [transferParametersForMode](#transferParametersForMode) | `enum map of object` | Configures mode-specific properties for transfer calculations. | *Optional* | | 2.7 | -| [transferRequests](RouteRequest.md) | `object[]` | Routing requests to use for pre-calculating stop-to-stop transfers. | *Optional* | | 2.1 | -| [transitFeeds](#transitFeeds) | `object[]` | Scan for transit data files | *Optional* | | 2.2 | -|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | -|       type = "gtfs" | `enum` | The feed input format. | *Required* | | 2.2 | -|       blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | -|       [discardMinTransferTimes](#tf_0_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. Overrides the value specified in `gtfsDefaults`. | *Optional* | `false` | 2.3 | -|       feedId | `string` | The unique ID for this feed. This overrides any feed ID defined within the feed itself. | *Optional* | | 2.2 | -|       maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. Overrides the value specified in `gtfsDefaults`. | *Optional* | `200` | 2.3 | -|       removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -|       [stationTransferPreference](#tf_0_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. Overrides the value specified in `gtfsDefaults`. | *Optional* | `"allowed"` | 2.3 | -|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | -|       type = "netex" | `enum` | The feed input format. | *Required* | | 2.2 | -|       feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Required* | | 2.2 | -|       [groupFilePattern](#tf_1_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | -|       ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | -|       [ignoreFilePattern](#tf_1_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | -|       ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | -|       noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | -|       [sharedFilePattern](#tf_1_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | -|       [sharedGroupFilePattern](#tf_1_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | -|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | -|       [ferryIdsNotAllowedForBicycle](#tf_1_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | -| [transitRouteToStationCentroid](#transitRouteToStationCentroid) | `feed-scoped-id[]` | List stations that should route to centroid. | *Optional* | | 2.7 | +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|-------------------------------------------------------------------------------------------|:--------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|-----------------------------------|:-----:| +| [areaVisibility](#areaVisibility) | `boolean` | Perform visibility calculations. | *Optional* | `false` | 1.5 | +| [buildReportDir](#buildReportDir) | `uri` | URI to the directory where the graph build report should be written to. | *Optional* | | 2.0 | +| [configVersion](#configVersion) | `string` | Deployment version of the *build-config.json*. | *Optional* | | 2.1 | +| [dataImportReport](#dataImportReport) | `boolean` | Generate nice HTML report of Graph errors/warnings | *Optional* | `false` | 2.0 | +| [distanceBetweenElevationSamples](#distanceBetweenElevationSamples) | `double` | The distance between elevation samples in meters. | *Optional* | `10.0` | 2.0 | +| embedRouterConfig | `boolean` | Embed the Router config in the graph, which allows it to be sent to a server fully configured over the wire. | *Optional* | `true` | 2.0 | +| [graph](#graph) | `uri` | URI to the graph object file for reading and writing. | *Optional* | | 2.0 | +| [gsCredentials](#gsCredentials) | `string` | Local file system path to Google Cloud Platform service accounts credentials file. | *Optional* | | 2.0 | +| [includeEllipsoidToGeoidDifference](#includeEllipsoidToGeoidDifference) | `boolean` | Include the Ellipsoid to Geoid difference in the calculations of every point along every StreetWithElevationEdge. | *Optional* | `false` | 2.0 | +| maxAreaNodes | `integer` | Visibility calculations for an area will not be done if there are more nodes than this limit. | *Optional* | `150` | 2.1 | +| [maxDataImportIssuesPerFile](#maxDataImportIssuesPerFile) | `integer` | When to split the import report. | *Optional* | `1000` | 2.0 | +| maxElevationPropagationMeters | `integer` | The maximum distance to propagate elevation to vertices which have no elevation. | *Optional* | `2000` | 1.5 | +| [maxStopToShapeSnapDistance](#maxStopToShapeSnapDistance) | `double` | Maximum distance between route shapes and their stops. | *Optional* | `150.0` | 2.1 | +| maxTransferDuration | `duration` | Transfers up to this duration with a mode-specific speed value will be pre-calculated and included in the Graph. | *Optional* | `"PT30M"` | 2.1 | +| [multiThreadElevationCalculations](#multiThreadElevationCalculations) | `boolean` | Configuring multi-threading during elevation calculations. | *Optional* | `false` | 2.0 | +| [osmCacheDataInMem](#osmCacheDataInMem) | `boolean` | If OSM data should be cached in memory during processing. | *Optional* | `false` | 2.0 | +| [osmNaming](#osmNaming) | `enum` | A custom OSM namer to use. | *Optional* | `"default"` | 1.5 | +| platformEntriesLinking | `boolean` | Link unconnected entries to public transport platforms. | *Optional* | `false` | 2.0 | +| [readCachedElevations](#readCachedElevations) | `boolean` | Whether to read cached elevation data. | *Optional* | `true` | 2.0 | +| staticBikeParkAndRide | `boolean` | Whether we should create bike P+R stations from OSM data. | *Optional* | `false` | 1.5 | +| staticParkAndRide | `boolean` | Whether we should create car P+R stations from OSM data. | *Optional* | `true` | 1.5 | +| stopConsolidationFile | `uri` | Name of the CSV-formatted file in the build directory which contains the configuration for stop consolidation. | *Optional* | | 2.5 | +| [streetGraph](#streetGraph) | `uri` | URI to the street graph object file for reading and writing. | *Optional* | | 2.0 | +| [subwayAccessTime](#subwayAccessTime) | `double` | Minutes necessary to reach stops served by trips on routes of route_type=1 (subway) from the street. | *Optional* | `2.0` | 1.5 | +| [transitModelTimeZone](#transitModelTimeZone) | `time-zone` | Time zone for the graph. | *Optional* | | 2.2 | +| [transitServiceEnd](#transitServiceEnd) | `duration` | Limit the import of transit services to the given end date. | *Optional* | `"P3Y"` | 2.0 | +| [transitServiceStart](#transitServiceStart) | `duration` | Limit the import of transit services to the given START date. | *Optional* | `"-P1Y"` | 2.0 | +| [writeCachedElevations](#writeCachedElevations) | `boolean` | Reusing elevation data from previous builds | *Optional* | `false` | 2.0 | +| [boardingLocationTags](#boardingLocationTags) | `string[]` | What OSM tags should be looked on for the source of matching stops to platforms and stops. | *Optional* | | 2.2 | +| [dataOverlay](sandbox/DataOverlay.md) | `object` | Config for the DataOverlay Sandbox module | *Optional* | | 2.2 | +| [dem](#dem) | `object[]` | Specify parameters for DEM extracts. | *Optional* | | 2.2 | +|       [elevationUnitMultiplier](#dem_0_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. Overrides the value specified in `demDefaults`. | *Optional* | `1.0` | 2.3 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +| demDefaults | `object` | Default properties for DEM extracts. | *Optional* | | 2.3 | +|    [elevationUnitMultiplier](#demDefaults_elevationUnitMultiplier) | `double` | Specify a multiplier to convert elevation units from source to meters. | *Optional* | `1.0` | 2.3 | +| [elevationBucket](#elevationBucket) | `object` | Used to download NED elevation tiles from the given AWS S3 bucket. | *Optional* | | na | +| [emissions](sandbox/Emissions.md) | `object` | Emissions configuration. | *Optional* | | 2.5 | +| [fares](sandbox/Fares.md) | `object` | Fare configuration. | *Optional* | | 2.0 | +| gtfsDefaults | `object` | The gtfsDefaults section allows you to specify default properties for GTFS files. | *Optional* | | 2.3 | +|    blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. | *Optional* | `true` | 2.3 | +|    [discardMinTransferTimes](#gd_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. | *Optional* | `false` | 2.3 | +|    maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. | *Optional* | `200` | 2.3 | +|    removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. | *Optional* | `true` | 2.3 | +|    [stationTransferPreference](#gd_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. | *Optional* | `"allowed"` | 2.3 | +| islandPruning | `object` | Settings for fixing street graph connectivity errors | *Optional* | | 2.3 | +|    [adaptivePruningDistance](#islandPruning_adaptivePruningDistance) | `integer` | Search distance for analyzing islands in pruning. | *Optional* | `250` | 2.3 | +|    [adaptivePruningFactor](#islandPruning_adaptivePruningFactor) | `double` | Defines how much pruning thresholds grow maximally by distance. | *Optional* | `50.0` | 2.3 | +|    [islandWithStopsMaxSize](#islandPruning_islandWithStopsMaxSize) | `integer` | When a graph island with stops in it should be pruned. | *Optional* | `2` | 2.3 | +|    [islandWithoutStopsMaxSize](#islandPruning_islandWithoutStopsMaxSize) | `integer` | When a graph island without stops should be pruned. | *Optional* | `10` | 2.3 | +| [localFileNamePatterns](#localFileNamePatterns) | `object` | Patterns for matching OTP file types in the base directory | *Optional* | | 2.0 | +|    [dem](#lfp_dem) | `regexp` | Pattern for matching elevation DEM files. | *Optional* | `"(?i)\.tiff?$"` | 2.0 | +|    [gtfs](#lfp_gtfs) | `regexp` | Patterns for matching GTFS zip-files or directories. | *Optional* | `"(?i)gtfs"` | 2.0 | +|    [netex](#lfp_netex) | `regexp` | Patterns for matching NeTEx zip files or directories. | *Optional* | `"(?i)netex"` | 2.0 | +|    [osm](#lfp_osm) | `regexp` | Pattern for matching Open Street Map input files. | *Optional* | `"(?i)(\.pbf¦\.osm¦\.osm\.xml)$"` | 2.0 | +| netexDefaults | `object` | The netexDefaults section allows you to specify default properties for NeTEx files. | *Optional* | | 2.2 | +|    feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Optional* | `"NETEX"` | 2.2 | +|    [groupFilePattern](#nd_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | +|    ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | +|    [ignoreFilePattern](#nd_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | +|    ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | +|    noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | +|    [sharedFilePattern](#nd_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | +|    [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 | +|       [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 | +|    [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 | +| [transferParametersForMode](#transferParametersForMode) | `enum map of object` | Configures mode-specific properties for transfer calculations. | *Optional* | | 2.7 | +|    BIKE | `object` | NA | *Optional* | | 2.7 | +|       [carsAllowedStopMaxTransferDuration](#tpfm_BIKE_carsAllowedStopMaxTransferDuration) | `duration` | This is used for specifying a `maxTransferDuration` value to use with transfers between stops which are visited by trips that allow cars. | *Optional* | | 2.7 | +|       [disableDefaultTransfers](#tpfm_BIKE_disableDefaultTransfers) | `boolean` | This disables default transfer calculations. | *Optional* | `false` | 2.7 | +|       maxTransferDuration | `duration` | This overwrites the default `maxTransferDuration` for the given mode. | *Optional* | | 2.7 | +|    CAR | `object` | NA | *Optional* | | 2.7 | +|       [carsAllowedStopMaxTransferDuration](#tpfm_CAR_carsAllowedStopMaxTransferDuration) | `duration` | This is used for specifying a `maxTransferDuration` value to use with transfers between stops which are visited by trips that allow cars. | *Optional* | | 2.7 | +|       [disableDefaultTransfers](#tpfm_CAR_disableDefaultTransfers) | `boolean` | This disables default transfer calculations. | *Optional* | `false` | 2.7 | +|       maxTransferDuration | `duration` | This overwrites the default `maxTransferDuration` for the given mode. | *Optional* | | 2.7 | +| [transferRequests](RouteRequest.md) | `object[]` | Routing requests to use for pre-calculating stop-to-stop transfers. | *Optional* | | 2.1 | +| [transitFeeds](#transitFeeds) | `object[]` | Scan for transit data files | *Optional* | | 2.2 | +|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | +|       type = "gtfs" | `enum` | The feed input format. | *Required* | | 2.2 | +|       blockBasedInterlining | `boolean` | Whether to create stay-seated transfers in between two trips with the same block id. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | +|       [discardMinTransferTimes](#tf_0_discardMinTransferTimes) | `boolean` | Should minimum transfer times in GTFS files be discarded. Overrides the value specified in `gtfsDefaults`. | *Optional* | `false` | 2.3 | +|       feedId | `string` | The unique ID for this feed. This overrides any feed ID defined within the feed itself. | *Optional* | | 2.2 | +|       maxInterlineDistance | `integer` | Maximal distance between stops in meters that will connect consecutive trips that are made with same vehicle. Overrides the value specified in `gtfsDefaults`. | *Optional* | `200` | 2.3 | +|       removeRepeatedStops | `boolean` | Should consecutive identical stops be merged into one stop time entry. Overrides the value specified in `gtfsDefaults`. | *Optional* | `true` | 2.3 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +|       [stationTransferPreference](#tf_0_stationTransferPreference) | `enum` | Should there be some preference or aversion for transfers at stops that are part of a station. Overrides the value specified in `gtfsDefaults`. | *Optional* | `"allowed"` | 2.3 | +|    { object } | `object` | Nested object in array. The object type is determined by the parameters. | *Optional* | | 2.2 | +|       type = "netex" | `enum` | The feed input format. | *Required* | | 2.2 | +|       feedId | `string` | This field is used to identify the specific NeTEx feed. It is used instead of the feed_id field in GTFS file feed_info.txt. | *Required* | | 2.2 | +|       [groupFilePattern](#tf_1_groupFilePattern) | `regexp` | Pattern for matching group NeTEx files. | *Optional* | `"(\w{3})-.*\.xml"` | 2.0 | +|       ignoreFareFrame | `boolean` | Ignore contents of the FareFrame | *Optional* | `false` | 2.3 | +|       [ignoreFilePattern](#tf_1_ignoreFilePattern) | `regexp` | Pattern for matching ignored files in a NeTEx bundle. | *Optional* | `"$^"` | 2.0 | +|       ignoreParking | `boolean` | Ignore Parking elements. | *Optional* | `true` | 2.6 | +|       noTransfersOnIsolatedStops | `boolean` | Whether we should allow transfers to and from StopPlaces marked with LimitedUse.ISOLATED | *Optional* | `false` | 2.2 | +|       [sharedFilePattern](#tf_1_sharedFilePattern) | `regexp` | Pattern for matching shared NeTEx files in a NeTEx bundle. | *Optional* | `"shared-data\.xml"` | 2.0 | +|       [sharedGroupFilePattern](#tf_1_sharedGroupFilePattern) | `regexp` | Pattern for matching shared group NeTEx files in a NeTEx bundle. | *Optional* | `"(\w{3})-.*-shared\.xml"` | 2.0 | +|       source | `uri` | The unique URI pointing to the data file. | *Required* | | 2.2 | +|       [ferryIdsNotAllowedForBicycle](#tf_1_ferryIdsNotAllowedForBicycle) | `string[]` | List ferries which do not allow bikes. | *Optional* | | 2.0 | +| [transitRouteToStationCentroid](#transitRouteToStationCentroid) | `feed-scoped-id[]` | List stations that should route to centroid. | *Optional* | | 2.7 | @@ -983,6 +991,86 @@ To configure mode-specific parameters, the modes should also be used in the `tra ``` +

carsAllowedStopMaxTransferDuration

+ +**Since version:** `2.7` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` +**Path:** /transferParametersForMode/BIKE + +This is used for specifying a `maxTransferDuration` value to use with transfers between stops which are visited by trips that allow cars. + +This parameter configures additional transfers to be calculated for the specified mode only between stops that have trips with cars. +The transfers are calculated for the mode in a range based on the given duration. +By default, these transfers are not calculated unless specified for a mode with this field. + +Calculating transfers only between stops that have trips with cars can be useful with car ferries, for example. +Using transit with cars can only occur between certain stops. +These kinds of stops require support for loading cars into ferries, for example. +The default transfers are calculated based on a configurable range (configurable by using the `maxTransferDuration` field) +which limits transfers from stops to only be calculated to other stops that are in range. +When compared to walking, using a car can cover larger distances within the same duration specified in the `maxTransferDuration` field. +This can lead to large amounts of transfers calculated between stops that do not require car transfers between them. +This in turn can lead to a large increase in memory for the stored graph, depending on the data used in the graph. + +For cars, using this parameter in conjunction with `disableDefaultTransfers` allows calculating transfers only between relevant stops. +For bikes, using this parameter can enable transfers between ferry stops that would normally not be in range. +In Finland this is useful for bike routes that use ferries near the Turku archipelago, for example. + + +

disableDefaultTransfers

+ +**Since version:** `2.7` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `false` +**Path:** /transferParametersForMode/BIKE + +This disables default transfer calculations. + +The default transfers are calculated based on a configurable range (configurable by using the `maxTransferDuration` field) +which limits transfers from stops to only be calculated to other stops that are in range. +This parameter disables these transfers. +A motivation to disable default transfers could be related to using the `carsAllowedStopMaxTransferDuration` field which only +calculates transfers between stops that have trips with cars. +For example, when using the `carsAllowedStopMaxTransferDuration` field with cars, the default transfers can be redundant. + + +

carsAllowedStopMaxTransferDuration

+ +**Since version:** `2.7` ∙ **Type:** `duration` ∙ **Cardinality:** `Optional` +**Path:** /transferParametersForMode/CAR + +This is used for specifying a `maxTransferDuration` value to use with transfers between stops which are visited by trips that allow cars. + +This parameter configures additional transfers to be calculated for the specified mode only between stops that have trips with cars. +The transfers are calculated for the mode in a range based on the given duration. +By default, these transfers are not calculated unless specified for a mode with this field. + +Calculating transfers only between stops that have trips with cars can be useful with car ferries, for example. +Using transit with cars can only occur between certain stops. +These kinds of stops require support for loading cars into ferries, for example. +The default transfers are calculated based on a configurable range (configurable by using the `maxTransferDuration` field) +which limits transfers from stops to only be calculated to other stops that are in range. +When compared to walking, using a car can cover larger distances within the same duration specified in the `maxTransferDuration` field. +This can lead to large amounts of transfers calculated between stops that do not require car transfers between them. +This in turn can lead to a large increase in memory for the stored graph, depending on the data used in the graph. + +For cars, using this parameter in conjunction with `disableDefaultTransfers` allows calculating transfers only between relevant stops. +For bikes, using this parameter can enable transfers between ferry stops that would normally not be in range. +In Finland this is useful for bike routes that use ferries near the Turku archipelago, for example. + + +

disableDefaultTransfers

+ +**Since version:** `2.7` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `false` +**Path:** /transferParametersForMode/CAR + +This disables default transfer calculations. + +The default transfers are calculated based on a configurable range (configurable by using the `maxTransferDuration` field) +which limits transfers from stops to only be calculated to other stops that are in range. +This parameter disables these transfers. +A motivation to disable default transfers could be related to using the `carsAllowedStopMaxTransferDuration` field which only +calculates transfers between stops that have trips with cars. +For example, when using the `carsAllowedStopMaxTransferDuration` field with cars, the default transfers can be redundant. + +

transitFeeds

**Since version:** `2.2` ∙ **Type:** `object[]` ∙ **Cardinality:** `Optional` @@ -1215,6 +1303,16 @@ the centroid. "emissions" : { "carAvgCo2PerKm" : 170, "carAvgOccupancy" : 1.3 + }, + "transferParametersForMode" : { + "CAR" : { + "disableDefaultTransfers" : true, + "carsAllowedStopMaxTransferDuration" : "3h" + }, + "BIKE" : { + "maxTransferDuration" : "30m", + "carsAllowedStopMaxTransferDuration" : "3h" + } } } ``` From 6222f053a3b5c036a8b49ed1dee42d476570d97d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 4 Jan 2025 22:27:23 +0100 Subject: [PATCH 123/195] Add speed test for cache creation --- .../transit/speed_test/SpeedTest.java | 17 +++++++++++++++++ .../speed_test/model/timer/SpeedTestTimer.java | 3 +++ 2 files changed, 20 insertions(+) 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..3228a2f8fa8 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 @@ -13,11 +13,13 @@ import java.util.List; import java.util.Map; import java.util.function.Predicate; +import java.util.stream.IntStream; import org.opentripplanner.TestServerContext; import org.opentripplanner.datastore.OtpDataStore; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.response.RoutingResponse; import org.opentripplanner.routing.framework.DebugTimingAggregator; import org.opentripplanner.routing.graph.Graph; @@ -192,6 +194,9 @@ public void runTest() { } updateTimersWithGlobalCounters(); + + timeTransferCacheComputation(); + printProfileStatistics(); saveTestCasesToResultFile(); System.err.println("\nSpeedTest done! " + projectInfo().getVersionString()); @@ -335,6 +340,18 @@ private void saveTestCasesToResultFile() { } } + private void timeTransferCacheComputation() { + IntStream + .of(1, 2, 3, 4, 5, 6, 7) + .forEach(reluctance -> { + RouteRequest routeRequest = new RouteRequest(); + routeRequest.withPreferences(b -> b.withWalk(c -> c.withReluctance(reluctance))); + timer.transferCacheTimer.record(() -> + timetableRepository.getTransitLayer().initTransferCacheForRequest(routeRequest) + ); + }); + } + /** * Add "static" transit statistics and JVM memory usages to the "timers" logging. */ 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..59a83c9ea2c 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,9 @@ public class SpeedTestTimer { List.of(loggerRegistry) ); private final MeterRegistry uploadRegistry = MeterRegistrySetup.getRegistry().orElse(null); + + public final Timer transferCacheTimer = registry.timer("transfer_cache_computation"); + private boolean groupResultByTestCaseCategory = false; public static int nanosToMillisecond(long nanos) { From d3e67b050b6eb1faf57e86e0686fcd977c295709 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 4 Jan 2025 22:28:04 +0100 Subject: [PATCH 124/195] Run speed test on branch --- .github/workflows/performance-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/performance-test.yml b/.github/workflows/performance-test.yml index 5e55e158a6f..e15f13fc796 100644 --- a/.github/workflows/performance-test.yml +++ b/.github/workflows/performance-test.yml @@ -4,6 +4,7 @@ on: push: branches: - dev-2.x + - transfer-cache-speed-test jobs: perf-test: From e3044e63364f69f1208864e77204c00db2078c93 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 5 Jan 2025 09:24:47 +0100 Subject: [PATCH 125/195] Improve recording --- .../opentripplanner/transit/speed_test/SpeedTest.java | 5 +++-- .../transit/speed_test/model/timer/SpeedTestTimer.java | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) 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 3228a2f8fa8..2415b940dde 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 @@ -346,8 +346,9 @@ private void timeTransferCacheComputation() { .forEach(reluctance -> { RouteRequest routeRequest = new RouteRequest(); routeRequest.withPreferences(b -> b.withWalk(c -> c.withReluctance(reluctance))); - timer.transferCacheTimer.record(() -> - timetableRepository.getTransitLayer().initTransferCacheForRequest(routeRequest) + 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 59a83c9ea2c..d5ff56b9d53 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 @@ -139,6 +139,15 @@ public void globalCount(String meterName, long count) { } } + 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, From e429a2d868d4153b030ea94ec078725ed3665013 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 5 Jan 2025 09:33:37 +0100 Subject: [PATCH 126/195] Reverse order of measurements --- .../opentripplanner/transit/speed_test/SpeedTest.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 2415b940dde..f986322bfb4 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 @@ -193,9 +193,11 @@ public void runTest() { } } + measureTransferCacheComputation(); + updateTimersWithGlobalCounters(); - timeTransferCacheComputation(); + timer.finishUp(); printProfileStatistics(); saveTestCasesToResultFile(); @@ -340,7 +342,11 @@ private void saveTestCasesToResultFile() { } } - private void timeTransferCacheComputation() { + /** + * Measure how long it takes to compute the transfer cache. + */ + private void measureTransferCacheComputation() { + System.out.println("Measuring transfer cache computation"); IntStream .of(1, 2, 3, 4, 5, 6, 7) .forEach(reluctance -> { @@ -370,7 +376,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(); } /** From e1167a92867ad101368f3412dbc626133ec53601 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 5 Jan 2025 10:31:46 +0100 Subject: [PATCH 127/195] Remove log --- .../java/org/opentripplanner/transit/speed_test/SpeedTest.java | 1 - 1 file changed, 1 deletion(-) 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 f986322bfb4..9b6b5b1ed68 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 @@ -346,7 +346,6 @@ private void saveTestCasesToResultFile() { * Measure how long it takes to compute the transfer cache. */ private void measureTransferCacheComputation() { - System.out.println("Measuring transfer cache computation"); IntStream .of(1, 2, 3, 4, 5, 6, 7) .forEach(reluctance -> { From 9232f2de6d28024b5bf07a797bdb84c3d1f2100d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 5 Jan 2025 10:33:16 +0100 Subject: [PATCH 128/195] Remove unused field --- .../transit/speed_test/model/timer/SpeedTestTimer.java | 2 -- 1 file changed, 2 deletions(-) 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 d5ff56b9d53..3f9313fae69 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 @@ -40,8 +40,6 @@ public class SpeedTestTimer { ); private final MeterRegistry uploadRegistry = MeterRegistrySetup.getRegistry().orElse(null); - public final Timer transferCacheTimer = registry.timer("transfer_cache_computation"); - private boolean groupResultByTestCaseCategory = false; public static int nanosToMillisecond(long nanos) { From 6f02877db551c0cbbc1f1ec400fa27c77d4bca46 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 5 Jan 2025 22:29:29 +0100 Subject: [PATCH 129/195] Small clean up in timer code --- .../transit/speed_test/model/timer/SpeedTestTimer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 3f9313fae69..847cbad2a14 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 @@ -185,7 +185,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); @@ -218,8 +218,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; } From cc4f438cbf232008c5cc0bb2f23c8bd7806b6157 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 5 Jan 2025 22:40:04 +0100 Subject: [PATCH 130/195] Remove branch name --- .github/workflows/performance-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/performance-test.yml b/.github/workflows/performance-test.yml index e15f13fc796..5e55e158a6f 100644 --- a/.github/workflows/performance-test.yml +++ b/.github/workflows/performance-test.yml @@ -4,7 +4,6 @@ on: push: branches: - dev-2.x - - transfer-cache-speed-test jobs: perf-test: From 041d1bbedc9d7741e8b754762c8052da05ba3549 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 6 Jan 2025 11:03:01 +0100 Subject: [PATCH 131/195] Add Javadoc --- .../transit/speed_test/model/timer/SpeedTestTimer.java | 3 +++ 1 file changed, 3 insertions(+) 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 847cbad2a14..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 @@ -137,6 +137,9 @@ 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); From 3ebaabb02f427362fd56026ebf6d29b02d8d2c15 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:29:44 +0000 Subject: [PATCH 132/195] chore(deps): update dependency eslint-config-prettier to v10 --- client/package-lock.json | 11 ++++++----- client/package.json | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index bb145274bd4..ab4ee820fff 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -32,7 +32,7 @@ "@vitejs/plugin-react": "4.3.4", "@vitest/coverage-v8": "2.1.8", "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", @@ -5992,12 +5992,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" diff --git a/client/package.json b/client/package.json index 78180de6b6a..9870204fd6d 100644 --- a/client/package.json +++ b/client/package.json @@ -41,7 +41,7 @@ "@vitejs/plugin-react": "4.3.4", "@vitest/coverage-v8": "2.1.8", "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", From 25ffb7342bb7990a5eea096062ca02157a419778 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Jan 2025 12:55:00 +0100 Subject: [PATCH 133/195] Extract separate test for transfer cache --- .../transit/speed_test/LoadModel.java | 7 ++ .../transit/speed_test/SetupHelper.java | 38 ++++++++++ .../transit/speed_test/SpeedTest.java | 25 ------- .../transit/speed_test/TransferCacheTest.java | 71 +++++++++++++++++++ 4 files changed, 116 insertions(+), 25 deletions(-) create mode 100644 application/src/test/java/org/opentripplanner/transit/speed_test/LoadModel.java create mode 100644 application/src/test/java/org/opentripplanner/transit/speed_test/SetupHelper.java create mode 100644 application/src/test/java/org/opentripplanner/transit/speed_test/TransferCacheTest.java 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..b94cecc8a3e --- /dev/null +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/SetupHelper.java @@ -0,0 +1,38 @@ +package org.opentripplanner.transit.speed_test; + +import java.io.File; +import java.net.URI; +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, 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); + } + + 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 9b6b5b1ed68..11400c07be0 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 @@ -13,13 +13,11 @@ import java.util.List; import java.util.Map; import java.util.function.Predicate; -import java.util.stream.IntStream; import org.opentripplanner.TestServerContext; import org.opentripplanner.datastore.OtpDataStore; import org.opentripplanner.framework.application.OtpAppException; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.raptor.configure.RaptorConfig; -import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.response.RoutingResponse; import org.opentripplanner.routing.framework.DebugTimingAggregator; import org.opentripplanner.routing.graph.Graph; @@ -29,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; @@ -193,8 +190,6 @@ public void runTest() { } } - measureTransferCacheComputation(); - updateTimersWithGlobalCounters(); timer.finishUp(); @@ -342,22 +337,6 @@ private void saveTestCasesToResultFile() { } } - /** - * Measure how long it takes to compute the transfer cache. - */ - private void measureTransferCacheComputation() { - IntStream - .of(1, 2, 3, 4, 5, 6, 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) - ); - }); - } - /** * Add "static" transit statistics and JVM memory usages to the "timers" logging. */ @@ -390,8 +369,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..499d16691d4 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/TransferCacheTest.java @@ -0,0 +1,71 @@ +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(); + + assertTestDateHasData(timetableRepository, config, buildConfig); + + // Creating transitLayerForRaptor should be integrated into the TimetableRepository, but for now + // we do it manually here + creatTransitLayerForRaptor(timetableRepository, config.transitRoutingParams); + + measureTransferCacheComputation(timer, timetableRepository); + + timer.setUp(false); + timer.finishUp(); + } catch (OtpAppException ae) { + System.err.println(ae.getMessage()); + System.exit(1); + } 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) + ); + }); + } +} From 8dde5b0423e5e4c1751e55b01981007f5ca2d521 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Jan 2025 13:12:28 +0100 Subject: [PATCH 134/195] Update CI config --- .github/workflows/performance-test.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/performance-test.yml b/.github/workflows/performance-test.yml index 5e55e158a6f..d4376c242c5 100644 --- a/.github/workflows/performance-test.yml +++ b/.github/workflows/performance-test.yml @@ -91,7 +91,7 @@ jobs: cp otp-shaded/target/otp-shaded-*-SNAPSHOT.jar otp.jar java -Xmx32G -jar otp.jar --build --save test/performance/${{ matrix.location }}/ - - name: Run speed test + - name: Run RAPTOR speed test if: matrix.profile == 'core' || github.ref == 'refs/heads/dev-2.x' env: PERFORMANCE_INFLUX_DB_PASSWORD: ${{ secrets.PERFORMANCE_INFLUX_DB_PASSWORD }} @@ -113,3 +113,12 @@ jobs: with: name: ${{ matrix.location }}-flight-recorder path: application/${{ matrix.location }}-speed-test.jfr + + - name: Run transfer cache speed test + if: matrix.profile == 'core' || github.ref == 'refs/heads/dev-2.x' + env: + PERFORMANCE_INFLUX_DB_PASSWORD: ${{ secrets.PERFORMANCE_INFLUX_DB_PASSWORD }} + SPEEDTEST_LOCATION: ${{ matrix.location }} + MAVEN_OPTS: "-Xmx50g -Dmaven.repo.local=/home/lenni/.m2/repository/" + run: | + mvn --projects application exec:java -Dexec.mainClass="org.opentripplanner.transit.speed_test.TransferCacheTest" -Dexec.classpathScope=test -Dexec.args="--dir=test/performance/${{ matrix.location }}" From 3a8faa0fc7702f7c591bbb2e7446c0f446d95533 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Jan 2025 13:19:00 +0100 Subject: [PATCH 135/195] Run test on branch --- .github/workflows/performance-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/performance-test.yml b/.github/workflows/performance-test.yml index d4376c242c5..ddff431872a 100644 --- a/.github/workflows/performance-test.yml +++ b/.github/workflows/performance-test.yml @@ -4,6 +4,7 @@ on: push: branches: - dev-2.x + - transfer-cache-speed-test jobs: perf-test: From 1bcdf1178ff6399adc2e271de47f5f1f57d58db5 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Jan 2025 13:55:43 +0100 Subject: [PATCH 136/195] Create transit layer first --- .../transit/speed_test/TransferCacheTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index 499d16691d4..84b45c0e73a 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/TransferCacheTest.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/TransferCacheTest.java @@ -28,14 +28,14 @@ public static void main(String[] args) { var timetableRepository = model.timetableRepository(); var buildConfig = model.buildConfig(); - var timer = new SpeedTestTimer(); - - assertTestDateHasData(timetableRepository, config, buildConfig); - // Creating transitLayerForRaptor should be integrated into the TimetableRepository, but for now // we do it manually here creatTransitLayerForRaptor(timetableRepository, config.transitRoutingParams); + assertTestDateHasData(timetableRepository, config, buildConfig); + + var timer = new SpeedTestTimer(); + measureTransferCacheComputation(timer, timetableRepository); timer.setUp(false); From 5c8509646af0158d247cd6068baa40700b49dbe4 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 14 Jan 2025 16:06:45 +0200 Subject: [PATCH 137/195] Add mode-specific version of the maxStopCount parameter (maxStopCountForMode) to router-config.json. --- .../raptoradapter/router/TransitRouter.java | 16 +++++--- .../preference/AccessEgressPreferences.java | 40 +++++++++++++------ .../routerequest/RouteRequestConfig.java | 15 ++++++- doc/user/RouteRequest.md | 13 ++++++ 4 files changed, 65 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java index d332013d5ac..23293e6d200 100644 --- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java +++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java @@ -36,6 +36,7 @@ import org.opentripplanner.routing.algorithm.transferoptimization.configure.TransferOptimizationServiceConfigurator; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.preference.AccessEgressPreferences; import org.opentripplanner.routing.api.request.request.StreetRequest; import org.opentripplanner.routing.api.response.InputField; import org.opentripplanner.routing.api.response.RoutingError; @@ -239,6 +240,7 @@ private Collection fetchEgress() { private Collection fetchAccessEgresses(AccessEgressType type) { var streetRequest = type.isAccess() ? request.journey().access() : request.journey().egress(); + StreetMode mode = streetRequest.mode(); // Prepare access/egress lists RouteRequest accessRequest = request.clone(); @@ -252,13 +254,15 @@ private Collection fetchAccessEgresses(AccessEgre }); } - Duration durationLimit = accessRequest + AccessEgressPreferences accessEgressPreferences = accessRequest .preferences() .street() - .accessEgress() - .maxDuration() - .valueOf(streetRequest.mode()); - int stopCountLimit = accessRequest.preferences().street().accessEgress().maxStopCount(); + .accessEgress(); + + Duration durationLimit = accessEgressPreferences.maxDuration().valueOf(mode); + int stopCountLimit = accessEgressPreferences + .maxStopCountForMode() + .getOrDefault(mode, accessEgressPreferences.defaultMaxStopCount()); var nearbyStops = AccessEgressRouter.findAccessEgresses( accessRequest, @@ -275,7 +279,7 @@ private Collection fetchAccessEgresses(AccessEgre var results = new ArrayList<>(accessEgresses); // Special handling of flex accesses - if (OTPFeature.FlexRouting.isOn() && streetRequest.mode() == StreetMode.FLEXIBLE) { + if (OTPFeature.FlexRouting.isOn() && mode == StreetMode.FLEXIBLE) { var flexAccessList = FlexAccessEgressRouter.routeAccessEgress( accessRequest, temporaryVerticesContainer, diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java b/application/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java index 289e06e6e02..4a8c342275f 100644 --- a/application/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java +++ b/application/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java @@ -4,6 +4,7 @@ import java.io.Serializable; import java.time.Duration; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; @@ -27,18 +28,21 @@ public final class AccessEgressPreferences implements Serializable { private final TimeAndCostPenaltyForEnum penalty; private final DurationForEnum maxDuration; - private final int maxStopCount; + private final int defaultMaxStopCount; + private final Map maxStopCountForMode; private AccessEgressPreferences() { this.maxDuration = durationForStreetModeOf(ofMinutes(45)); this.penalty = DEFAULT_TIME_AND_COST; - this.maxStopCount = 500; + this.defaultMaxStopCount = 500; + this.maxStopCountForMode = Map.of(); } private AccessEgressPreferences(Builder builder) { this.maxDuration = builder.maxDuration; this.penalty = builder.penalty; - this.maxStopCount = builder.maxStopCount; + this.defaultMaxStopCount = builder.defaultMaxStopCount; + this.maxStopCountForMode = Collections.unmodifiableMap(builder.maxStopCountForMode); } public static Builder of() { @@ -57,8 +61,12 @@ public DurationForEnum maxDuration() { return maxDuration; } - public int maxStopCount() { - return maxStopCount; + public int defaultMaxStopCount() { + return defaultMaxStopCount; + } + + public Map maxStopCountForMode() { + return maxStopCountForMode; } @Override @@ -69,13 +77,14 @@ public boolean equals(Object o) { return ( penalty.equals(that.penalty) && maxDuration.equals(that.maxDuration) && - maxStopCount == that.maxStopCount + defaultMaxStopCount == that.defaultMaxStopCount && + maxStopCountForMode.equals(that.maxStopCountForMode) ); } @Override public int hashCode() { - return Objects.hash(penalty, maxDuration, maxStopCount); + return Objects.hash(penalty, maxDuration, defaultMaxStopCount, maxStopCountForMode); } @Override @@ -84,7 +93,8 @@ public String toString() { .of(AccessEgressPreferences.class) .addObj("penalty", penalty, DEFAULT.penalty) .addObj("maxDuration", maxDuration, DEFAULT.maxDuration) - .addObj("maxStopCount", maxStopCount, DEFAULT.maxStopCount) + .addObj("defaultMaxStopCount", defaultMaxStopCount, DEFAULT.defaultMaxStopCount) + .addObj("maxStopCountForMode", maxStopCountForMode, DEFAULT.maxStopCountForMode) .toString(); } @@ -93,13 +103,15 @@ public static class Builder { private final AccessEgressPreferences original; private TimeAndCostPenaltyForEnum penalty; private DurationForEnum maxDuration; - private int maxStopCount; + private Map maxStopCountForMode; + private int defaultMaxStopCount; public Builder(AccessEgressPreferences original) { this.original = original; this.maxDuration = original.maxDuration; this.penalty = original.penalty; - this.maxStopCount = original.maxStopCount; + this.defaultMaxStopCount = original.defaultMaxStopCount; + this.maxStopCountForMode = original.maxStopCountForMode; } public Builder withMaxDuration(Consumer> body) { @@ -112,8 +124,12 @@ public Builder withMaxDuration(Duration defaultValue, Map return withMaxDuration(b -> b.withDefault(defaultValue).withValues(values)); } - public Builder withMaxStopCount(int maxCount) { - this.maxStopCount = maxCount; + public Builder withMaxStopCount( + int defaultMaxStopCount, + Map maxStopCountForMode + ) { + this.defaultMaxStopCount = defaultMaxStopCount; + this.maxStopCountForMode = maxStopCountForMode; return this; } diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index 454ab29a68c..3ea1e83148c 100644 --- a/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/application/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -542,7 +542,20 @@ duration can be set per mode(`maxDurationForMode`), because some street modes se Safety limit to prevent access to and egress from too many stops. """ ) - .asInt(dftAccessEgress.maxStopCount()) + .asInt(dftAccessEgress.defaultMaxStopCount()), + cae + .of("maxStopCountForMode") + .since(V2_7) + .summary( + "Maximal number of stops collected in access/egress routing for the given mode" + ) + .description( + """ + Safety limit to prevent access to and egress from too many stops. + Mode-specific version of `maxStopCount`. + """ + ) + .asEnumMap(StreetMode.class, Integer.class) ); }) .withMaxDirectDuration( diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 332058b2a42..f223c644604 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -46,6 +46,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |    [maxDuration](#rd_accessEgress_maxDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 | |    [maxStopCount](#rd_accessEgress_maxStopCount) | `integer` | Maximal number of stops collected in access/egress routing | *Optional* | `500` | 2.4 | |    [maxDurationForMode](#rd_accessEgress_maxDurationForMode) | `enum map of duration` | Limit access/egress per street mode. | *Optional* | | 2.1 | +|    [maxStopCountForMode](#rd_accessEgress_maxStopCountForMode) | `enum map of integer` | Maximal number of stops collected in access/egress routing for the given mode | *Optional* | | 2.7 | |    [penalty](#rd_accessEgress_penalty) | `enum map of object` | Penalty for access/egress by street mode. | *Optional* | | 2.4 | |       FLEXIBLE | `object` | NA | *Optional* | | 2.4 | |          costFactor | `double` | A factor multiplied with the time-penalty to get the cost-penalty. | *Optional* | `0.0` | 2.4 | @@ -431,6 +432,18 @@ Override the settings in `maxDuration` for specific street modes. This is done because some street modes searches are much more resource intensive than others. +

maxStopCountForMode

+ +**Since version:** `2.7` ∙ **Type:** `enum map of integer` ∙ **Cardinality:** `Optional` +**Path:** /routingDefaults/accessEgress +**Enum keys:** `not-set` | `walk` | `bike` | `bike-to-park` | `bike-rental` | `scooter-rental` | `car` | `car-to-park` | `car-pickup` | `car-rental` | `car-hailing` | `flexible` + +Maximal number of stops collected in access/egress routing for the given mode + +Safety limit to prevent access to and egress from too many stops. +Mode-specific version of `maxStopCount`. + +

penalty

**Since version:** `2.4` ∙ **Type:** `enum map of object` ∙ **Cardinality:** `Optional` From 96a0fccd2d6d4b9531412e38f0befcd44f05b73c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Jan 2025 15:11:27 +0100 Subject: [PATCH 138/195] Initialize before adding any data --- .../transit/speed_test/TransferCacheTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 index 84b45c0e73a..1c41cf67044 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/TransferCacheTest.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/TransferCacheTest.java @@ -28,17 +28,17 @@ public static void main(String[] args) { 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); - var timer = new SpeedTestTimer(); - measureTransferCacheComputation(timer, timetableRepository); - timer.setUp(false); timer.finishUp(); } catch (OtpAppException ae) { System.err.println(ae.getMessage()); From 5c9774975c368d3b78987fa80c0bbe91cc8a30e5 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Jan 2025 16:54:46 +0100 Subject: [PATCH 139/195] Re-use setup logic --- .../transit/speed_test/SpeedTest.java | 25 ++----------------- 1 file changed, 2 insertions(+), 23 deletions(-) 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 11400c07be0..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 @@ -152,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(); @@ -269,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<>()); From 179592cce4e0c41568ea18e84db931f571ba4703 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 14 Jan 2025 17:34:27 +0100 Subject: [PATCH 140/195] Don't run on branch anymore --- .github/workflows/performance-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/performance-test.yml b/.github/workflows/performance-test.yml index ddff431872a..d4376c242c5 100644 --- a/.github/workflows/performance-test.yml +++ b/.github/workflows/performance-test.yml @@ -4,7 +4,6 @@ on: push: branches: - dev-2.x - - transfer-cache-speed-test jobs: perf-test: From cda87ff049fe240ffb509948dd108fb461b70bc6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 15 Jan 2025 10:12:16 +0100 Subject: [PATCH 141/195] Incorporate review feedback --- .../opentripplanner/transit/speed_test/SetupHelper.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 index b94cecc8a3e..86383d6d94e 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/SetupHelper.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/SetupHelper.java @@ -2,6 +2,7 @@ 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; @@ -15,7 +16,7 @@ */ class SetupHelper { - static LoadModel loadGraph(File baseDir, URI path) { + 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()); @@ -23,7 +24,9 @@ static LoadModel loadGraph(File baseDir, URI path) { Graph graph = serializedGraphObject.graph; if (graph == null) { - throw new IllegalStateException(); + throw new IllegalStateException( + "Could not find graph at %s".formatted(file.getAbsolutePath()) + ); } TimetableRepository timetableRepository = serializedGraphObject.timetableRepository; From df84af5634ff51131e8aa31896cde70e0d17fa08 Mon Sep 17 00:00:00 2001 From: a-limyr Date: Wed, 15 Jan 2025 12:58:10 +0100 Subject: [PATCH 142/195] Fixed some styling --- client/codegen-preprocess.ts | 5 +- client/codegen.ts | 1 - .../SearchInput/TripQueryArguments.tsx | 68 ++++--- .../SearchInput/TripSchemaContext.tsx | 79 ++++++++ .../src/components/SearchInput/useTripArgs.ts | 182 ++++++++++++++++++ client/src/screens/App.tsx | 14 +- client/src/style.css | 7 + client/src/util/generate-arguments.cjs | 15 +- client/src/util/getSchemaUrl.ts | 5 + 9 files changed, 330 insertions(+), 46 deletions(-) create mode 100644 client/src/components/SearchInput/TripSchemaContext.tsx create mode 100644 client/src/components/SearchInput/useTripArgs.ts create mode 100644 client/src/util/getSchemaUrl.ts diff --git a/client/codegen-preprocess.ts b/client/codegen-preprocess.ts index c0747c7e29d..d947bc7478c 100644 --- a/client/codegen-preprocess.ts +++ b/client/codegen-preprocess.ts @@ -1,9 +1,12 @@ import type { CodegenConfig } from '@graphql-codegen/cli'; + import * as path from 'node:path'; + + const config: CodegenConfig = { overwrite: true, - schema: '../application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql', + schema: 'https://otp2debug.dev.entur.org/otp/transmodel/v3/schema.graphql', documents: 'src/**/*.{ts,tsx}', generates: { 'src/static/query/tripQuery.tsx': { diff --git a/client/codegen.ts b/client/codegen.ts index 2a80c1de5f5..a8ad1e40c49 100644 --- a/client/codegen.ts +++ b/client/codegen.ts @@ -1,5 +1,4 @@ import type { CodegenConfig } from '@graphql-codegen/cli'; -import * as path from 'node:path'; const config: CodegenConfig = { overwrite: true, diff --git a/client/src/components/SearchInput/TripQueryArguments.tsx b/client/src/components/SearchInput/TripQueryArguments.tsx index 98122ba7a5f..3091b13836d 100644 --- a/client/src/components/SearchInput/TripQueryArguments.tsx +++ b/client/src/components/SearchInput/TripQueryArguments.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; -import tripArgumentsData from '../../gql/query-arguments.json'; +//import tripArgumentsData from '../../gql/query-arguments.json'; +import { useTripSchema } from './TripSchemaContext'; import { TripQueryVariables } from '../../gql/graphql'; import { getNestedValue, setNestedValue } from './nestedUtils'; import ArgumentTooltip from './ArgumentTooltip.tsx'; @@ -42,12 +43,23 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab const [expandedArguments, setExpandedArguments] = useState<{ [key: string]: boolean }>({}); const [searchText] = useState(''); + const { tripArgs, loading, error } = useTripSchema(); + useEffect(() => { + if (!tripArgs) return; // Don't run if the data isn't loaded yet + if (loading || error) return; // Optionally handle error/loading + + // Example: tripArgs has shape { trip: { arguments: {} } } + const extractedArgs = extractAllArgs(tripArgs.trip.arguments); + setArgumentsList(extractedArgs); + }, [tripArgs, loading, error]); + + /**useEffect(() => { const tripArgs = tripArgumentsData.trip.arguments; const extractedArgs = extractAllArgs(tripArgs); setArgumentsList(extractedArgs); }, []); - +**/ const extractAllArgs = ( args: { [key: string]: any }, parentPath: string[] = [], @@ -262,11 +274,11 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab .filter(({ path }) => formatArgumentName(path).toLowerCase().includes(searchText.toLowerCase())) .filter(({ path }) => !excludedArguments.has(path)); - const renderListOfInputObjects = (listPath: string, allArgs: ArgumentConfig[], level: number) => { + const renderListOfInputObjects = (listPath: string, allArgs: ArgumentConfig[], level: number, tripArgs: any) => { const arrayVal = getNestedValue(tripQueryVariables, listPath) || []; // Dynamically determine the button label based on the type name - const argumentsData = tripArgumentsData.trip.arguments as Record< + const argumentsData = tripArgs as Record< string, { type: { @@ -392,7 +404,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab {isExpanded && isList ? ( -
{renderListOfInputObjects(path, allArgs, nestedLevel)}
+
{renderListOfInputObjects(path, allArgs, nestedLevel, tripArgs)}
) : isExpanded ? ( /* original single-object rendering */ renderArgumentInputs(nestedArgs, nestedLevel, allArgs) @@ -416,13 +428,18 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab checked={currentValue ?? false} onChange={(e) => handleInputChange(path, e.target.checked)} /> - {isInUse && handleRemoveArgument(path)} className={"remove-argument"}>x} + {isInUse && ( + handleRemoveArgument(path)} className={'remove-argument'}> + x + + )} ); })()} {['String', 'DoubleFunction', 'ID', 'Duration'].includes(type) && isList && ( { @@ -430,7 +447,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab return Array.isArray(currentValue) ? currentValue.join(', ') : ''; // Join array into a comma-separated string })()} onChange={(e) => handleInputChange(path, e.target.value)} - placeholder="Enter comma-separated IDs" + placeholder="Comma-separated list" /> )} {['String', 'DoubleFunction', 'ID', 'Duration'].includes(type) && !isList && ( @@ -533,27 +550,26 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab setTripQueryVariables(newVars); }; - return ( -
-
- Filters - -
- {filteredArgumentsList.length === 0 ? ( -

No arguments found.

- ) : ( -
- {renderArgumentInputs( - filteredArgumentsList.filter((arg) => arg.path.split('.').length === 1), - 0, - filteredArgumentsList, - )} -
- )} +
+
+ Filters +
+ {filteredArgumentsList.length === 0 ? ( +

No arguments found.

+ ) : ( +
+ {renderArgumentInputs( + filteredArgumentsList.filter((arg) => arg.path.split('.').length === 1), + 0, + filteredArgumentsList, + )} +
+ )} +
); }; diff --git a/client/src/components/SearchInput/TripSchemaContext.tsx b/client/src/components/SearchInput/TripSchemaContext.tsx new file mode 100644 index 00000000000..2e3f66def4c --- /dev/null +++ b/client/src/components/SearchInput/TripSchemaContext.tsx @@ -0,0 +1,79 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { fetchTripArgs } from './useTripArgs'; +// 'fetchTripArgs' is the function that does the introspection and returns the in-memory representation + +// This is the shape of the data we'll store in context: +import type { TripArgsRepresentation } from './useTripArgs'; + +// 1. Create the context +interface TripSchemaContextValue { + tripArgs: TripArgsRepresentation | null; + loading: boolean; + error: string | null; +} + +// Make sure to allow null or partial if you plan to do lazy initialization +const TripSchemaContext = createContext(undefined); + +// 2. Create a Provider component +interface TripSchemaProviderProps { + endpoint: string; // the GraphQL URL for introspection + children: React.ReactNode; +} + +export function TripSchemaProvider({ endpoint, children }: TripSchemaProviderProps) { + const [tripArgs, setTripArgs] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + let isMounted = true; + + async function loadSchema() { + setLoading(true); + setError(null); + try { + const result = await fetchTripArgs(endpoint); + if (isMounted) { + setTripArgs(result); + } + } catch (err: any) { + console.error('Error loading trip arguments:', err); + if (isMounted) { + setError(err.message ?? 'Failed to load trip schema'); + } + } finally { + if (isMounted) { + setLoading(false); + } + } + } + + loadSchema(); + + return () => { + isMounted = false; + }; + }, [endpoint]); + + const value: TripSchemaContextValue = { + tripArgs, + loading, + error, + }; + + return ( + + {children} + + ); +} + +// 3. Create a custom hook to consume the context +export function useTripSchema() { + const context = useContext(TripSchemaContext); + if (!context) { + throw new Error('useTripSchema must be used within a TripSchemaProvider'); + } + return context; +} diff --git a/client/src/components/SearchInput/useTripArgs.ts b/client/src/components/SearchInput/useTripArgs.ts new file mode 100644 index 00000000000..449125e1bdb --- /dev/null +++ b/client/src/components/SearchInput/useTripArgs.ts @@ -0,0 +1,182 @@ +import { + buildClientSchema, + getIntrospectionQuery, + GraphQLSchema, + GraphQLType, + GraphQLNamedType, + GraphQLNonNull, + GraphQLList, + isNonNullType, + isListType, + isScalarType, + isEnumType, + isInputObjectType, +} from 'graphql'; + +// +// Types +// +interface ResolvedType { + type: 'Scalar' | 'Enum' | 'InputObject'; + // For scalars or fallback, e.g. "String", "Int", etc. + subtype?: string; + // For input objects + name?: string; + fields?: { + [fieldName: string]: { + type: ResolvedType; + defaultValue: any; + isList: boolean; + }; + }; + // For enums + values?: string[]; +} + +interface ArgumentRepresentation { + [argName: string]: { + type: ResolvedType; + defaultValue?: any; + isList: boolean; + }; +} + +export interface TripArgsRepresentation { + trip: { + arguments: ArgumentRepresentation; + }; +} + +// +// 1. Utility function to unwrap NonNull and List until we get a "named" type +// +function getNamedType(type: GraphQLType): GraphQLNamedType { + let namedType = type; + while (isNonNullType(namedType) || isListType(namedType)) { + namedType = (namedType as GraphQLNonNull | GraphQLList).ofType; + } + return namedType; +} + +// +// 2. Recursively describe a type +// +function resolveType(type: GraphQLType): ResolvedType { + const namedType = getNamedType(type); + + if (isScalarType(namedType)) { + return { type: 'Scalar', subtype: namedType.name }; + } + + if (isEnumType(namedType)) { + return { + type: 'Enum', + values: namedType.getValues().map((val) => val.name), + }; + } + + if (isInputObjectType(namedType)) { + const fields = namedType.getFields(); + const fieldTypes: Record< + string, + { type: ResolvedType; defaultValue: any; isList: boolean } + > = {}; + + for (const fieldName of Object.keys(fields)) { + const field = fields[fieldName]; + + // Exclude deprecated fields + if (field.deprecationReason) { + continue; + } + + const isList = isListType(field.type); + const defaultValue = field.defaultValue !== undefined ? field.defaultValue : null; + + fieldTypes[fieldName] = { + type: resolveType(field.type), + defaultValue: defaultValue, + isList, + }; + } + + return { + type: 'InputObject', + name: namedType.name, + fields: fieldTypes, + }; + } + + // Fallback (e.g., unions, interfaces) + return { type: 'Scalar', subtype: 'String' }; +} + +// +// 3. Main function to generate in-memory representation for "trip" query arguments +// +function generateTripArgs(schema: GraphQLSchema): TripArgsRepresentation { + const queryType = schema.getQueryType(); + if (!queryType) { + throw new Error('No Query type found in the schema.'); + } + + const tripField = queryType.getFields()['trip']; + if (!tripField) { + throw new Error('No trip query found in the schema.'); + } + + const argsJson: ArgumentRepresentation = {}; + + tripField.args.forEach((arg) => { + if (arg.deprecationReason) { + // Skip deprecated arguments + return; + } + + const argName = arg.name; + const argType = resolveType(arg.type); + const argDefaultValue = arg.defaultValue !== undefined ? arg.defaultValue : null; + const isList = isListType(arg.type); + + argsJson[argName] = { + type: argType, + ...(argDefaultValue !== null && { defaultValue: argDefaultValue }), + isList, + }; + }); + + return { + trip: { + arguments: argsJson, + }, + }; +} + +// +// 4. Fetch the remote GraphQL schema via introspection +// then convert it into an in-memory representation of trip arguments. +// +export async function fetchTripArgs(graphqlEndpointUrl: string): Promise { + // 1. Perform introspection query + const introspectionQuery = getIntrospectionQuery(); + + // 2. Use browser's fetch (no Node needed) + const response = await fetch(graphqlEndpointUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: introspectionQuery }), + }); + + if (!response.ok) { + throw new Error(`Failed to fetch schema. HTTP error: ${response.status}`); + } + + // 3. Parse JSON + const { data } = await response.json(); + + // 4. Build GraphQLSchema from introspection + const schema = buildClientSchema(data); + + // 5. Generate and return the "trip" arguments representation + return generateTripArgs(schema); +} diff --git a/client/src/screens/App.tsx b/client/src/screens/App.tsx index 83465b1ccd9..018ead372e6 100644 --- a/client/src/screens/App.tsx +++ b/client/src/screens/App.tsx @@ -9,7 +9,9 @@ import { LogoSection } from '../components/SearchBar/LogoSection.tsx'; import { InputFieldsSection } from '../components/SearchBar/InputFieldsSection.tsx'; import TripQueryArguments from '../components/SearchInput/TripQueryArguments.tsx'; import Sidebar from '../components/SearchInput/Sidebar.tsx'; -import ViewArgumentsRaw from "../components/SearchInput/ViewArgumentsRaw.tsx"; +import ViewArgumentsRaw from '../components/SearchInput/ViewArgumentsRaw.tsx'; +import { TripSchemaProvider } from '../components/SearchInput/TripSchemaContext.tsx'; +import { getApiUrl } from '../util/getApiUrl.ts'; export function App() { const serverInfo = useServerInfo(); @@ -42,10 +44,12 @@ export function App() { pageResults={callback} loading={loading} > - + + +
diff --git a/client/src/style.css b/client/src/style.css index edf980dab7e..74da6d5262c 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -404,6 +404,8 @@ select { box-sizing: border-box; } + + .argument-list input[type="text"], .argument-list input[type="number"] { width: 50px; @@ -412,6 +414,11 @@ select { width: 140px; } +input.comma-separated-input[type="text"], +input.comma-separated-input[type="number"] { + width: 140px; +} + .remove-argument { margin-left: 2px; color: red; diff --git a/client/src/util/generate-arguments.cjs b/client/src/util/generate-arguments.cjs index 680fb05b92f..d2ff4b639b7 100644 --- a/client/src/util/generate-arguments.cjs +++ b/client/src/util/generate-arguments.cjs @@ -23,17 +23,12 @@ function getNamedType(type) { function resolveType(type, schema = new Set()) { const namedType = getNamedType(type); - // Debug: Log the named type - console.log('Resolving type:', namedType.name); if (isScalarType(namedType)) { return { type: 'Scalar', subtype: namedType.name }; } if (isEnumType(namedType)) { - // Debug: Log enum type values - console.log('Enum type detected:', namedType.name, 'Values:', namedType.getValues().map((val) => val.name)); - // Return enum type explicitly return { type: 'Enum', values: namedType.getValues().map((val) => val.name) }; } @@ -41,10 +36,7 @@ function resolveType(type, schema = new Set()) { const fields = namedType.getFields(); const fieldTypes = {}; - // Debug: Log the fields of the input object type - console.log('Input object type detected:', namedType.name, 'Fields:', Object.keys(fields)); - - Object.keys(fields).forEach((fieldName) => { + Object.keys(fields).forEach((fieldName) => { const field = fields[fieldName]; // Exclude deprecated fields within input objects @@ -99,10 +91,7 @@ const generateTripArgsJsonPlugin = async (schema) => { return; // Skip deprecated arguments } - // Debug: Log each argument being processed - console.log('Processing argument:', arg.name, 'Type:', arg.type.toString()); - - const argName = arg.name; + const argName = arg.name; const argType = resolveType(arg.type, schema); const argDefaultValue = arg.defaultValue !== undefined ? arg.defaultValue : null; const isList = isListType(arg.type); // Detect if the argument is a list diff --git a/client/src/util/getSchemaUrl.ts b/client/src/util/getSchemaUrl.ts new file mode 100644 index 00000000000..2ef707f6856 --- /dev/null +++ b/client/src/util/getSchemaUrl.ts @@ -0,0 +1,5 @@ +const endpoint = import.meta.env.VITE_SCHEMA_URL; + +export const getSchemaUrl = () => { + return endpoint; +}; From 3e1abad4c980293102063cb54077230ec4257d97 Mon Sep 17 00:00:00 2001 From: a-limyr Date: Wed, 15 Jan 2025 14:12:36 +0100 Subject: [PATCH 143/195] Fixed any type issues + some other things. --- .../src/components/MapView/LayerControl.tsx | 8 +- .../SearchInput/ArgumentTooltip.tsx | 2 +- .../components/SearchInput/TripArguments.ts | 2 +- .../SearchInput/TripQueryArguments.tsx | 21 ++- .../SearchInput/ViewArgumentsRaw.tsx | 3 +- .../components/SearchInput/nestedUtils.tsx | 146 ++++++++++++------ 6 files changed, 115 insertions(+), 67 deletions(-) diff --git a/client/src/components/MapView/LayerControl.tsx b/client/src/components/MapView/LayerControl.tsx index 42d18d4e1f6..12014b5bc3d 100644 --- a/client/src/components/MapView/LayerControl.tsx +++ b/client/src/components/MapView/LayerControl.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, useCallback } from 'react'; -import type { ControlPosition } from 'react-map-gl'; +import type {AnyLayer, ControlPosition} from 'react-map-gl'; import type { MapRef } from 'react-map-gl/maplibre'; interface Layer { @@ -43,8 +43,8 @@ const LayerControl: React.FC = ({ .map((layer) => { // Try to pick up a pretty name from metadata if available. let name = layer.id; - if ((layer as any).metadata?.name) { - name = (layer as any).metadata.name; + if ((layer as AnyLayer).metadata?.name) { + name = (layer as AnyLayer).metadata.name; } return { id: layer.id, name }; }); @@ -57,7 +57,7 @@ const LayerControl: React.FC = ({ .filter((layer) => layer.type !== 'raster' && !layer.id.startsWith('jsx')) .reverse() // so that the topmost layers appear first .forEach((layer) => { - const groupName = (layer as any).metadata?.group || 'Misc'; + const groupName = (layer as AnyLayer).metadata?.group || 'Misc'; if (!groups[groupName]) { groups[groupName] = []; } diff --git a/client/src/components/SearchInput/ArgumentTooltip.tsx b/client/src/components/SearchInput/ArgumentTooltip.tsx index 4d5d07ebfdf..f444ac323f9 100644 --- a/client/src/components/SearchInput/ArgumentTooltip.tsx +++ b/client/src/components/SearchInput/ArgumentTooltip.tsx @@ -4,7 +4,7 @@ import inputIcon from '../../static/img/input.svg'; import durationIcon from '../../static/img/lap-timer.svg'; interface ArgumentTooltipProps { - defaultValue: any; + defaultValue?: string; type?: string; } diff --git a/client/src/components/SearchInput/TripArguments.ts b/client/src/components/SearchInput/TripArguments.ts index d911032bd72..fbf31b5cbf8 100644 --- a/client/src/components/SearchInput/TripArguments.ts +++ b/client/src/components/SearchInput/TripArguments.ts @@ -8,7 +8,7 @@ export interface TripArguments { export interface Argument { type: TypeDescriptor; - defaultValue?: any; + defaultValue?: string; } export type TypeDescriptor = ScalarType | NestedObject; diff --git a/client/src/components/SearchInput/TripQueryArguments.tsx b/client/src/components/SearchInput/TripQueryArguments.tsx index 3091b13836d..e1706f60458 100644 --- a/client/src/components/SearchInput/TripQueryArguments.tsx +++ b/client/src/components/SearchInput/TripQueryArguments.tsx @@ -22,7 +22,7 @@ function formatArgumentName(input: string): string { type ArgumentConfig = { path: string; type: string; - defaultValue: any; + defaultValue?: string; enumValues?: string[]; isComplex?: boolean; isList?: boolean; @@ -34,7 +34,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab path: string; type: string; subtype?: string; - defaultValue: any; + defaultValue?: string; enumValues?: string[]; isComplex?: boolean; isList?: boolean; @@ -67,7 +67,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab path: string; type: string; name?: string; - defaultValue: any; + defaultValue?: string; enumValues?: string[]; isComplex?: boolean; isList?: boolean; @@ -76,7 +76,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab path: string; type: string; name?: string; - defaultValue: any; + defaultValue?: string; enumValues?: string[]; isComplex?: boolean; isList?: boolean; @@ -98,7 +98,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab path: string; type: string; name?: string; - defaultValue: any; + defaultValue?: string; enumValues?: string[]; isComplex?: boolean; isList?: boolean; @@ -107,7 +107,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab path: string; type: string; name?: string; - defaultValue: any; + defaultValue?: string; enumValues?: string[]; isComplex?: boolean; isList?: boolean; @@ -142,7 +142,6 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab allArgs.push({ path: currentPath, type: 'Group', - defaultValue: null, isComplex: true, isList: false, }); @@ -156,7 +155,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab }); } } else if (typeof argData === 'object' && argData.fields) { - allArgs.push({ path: currentPath, type: 'Group', defaultValue: null, isComplex: true }); + allArgs.push({ path: currentPath, type: 'Group', isComplex: true }); allArgs = allArgs.concat(extractAllArgs(argData.fields, [...parentPath, argName])); } else { allArgs.push({ path: currentPath, type: argData.type ?? typeof argData, defaultValue: argData.defaultValue }); @@ -286,7 +285,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab name?: string; fields?: Record; }; - defaultValue?: any; + defaultValue?: string; isList?: boolean; } >; @@ -371,7 +370,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab args: { path: string; type: string; - defaultValue: any; + defaultValue?: string; enumValues?: string[]; isComplex?: boolean; isList?: boolean; @@ -380,7 +379,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab allArgs: { path: string; type: string; - defaultValue: any; + defaultValue?: string; enumValues?: string[]; isComplex?: boolean; isList?: boolean; diff --git a/client/src/components/SearchInput/ViewArgumentsRaw.tsx b/client/src/components/SearchInput/ViewArgumentsRaw.tsx index f38bcbde7d0..586cdd1f658 100644 --- a/client/src/components/SearchInput/ViewArgumentsRaw.tsx +++ b/client/src/components/SearchInput/ViewArgumentsRaw.tsx @@ -1,7 +1,8 @@ import React from 'react'; +import {TripQueryVariables} from "../../gql/graphql.ts"; interface ViewArgumentsRawProps { - tripQueryVariables: any; + tripQueryVariables: TripQueryVariables; } const ViewArgumentsRaw: React.FC = ({ tripQueryVariables }) => { diff --git a/client/src/components/SearchInput/nestedUtils.tsx b/client/src/components/SearchInput/nestedUtils.tsx index 5b6a01ed644..d72296d3cb6 100644 --- a/client/src/components/SearchInput/nestedUtils.tsx +++ b/client/src/components/SearchInput/nestedUtils.tsx @@ -1,29 +1,56 @@ +export type NestedData = null | undefined | string | number | boolean | NestedData[] | { [key: string]: NestedData }; + /** - * Retrieves a nested value from an object based on a dot-separated path. + * Retrieves a nested value from an object/array based on a dot-separated path. * Supports wildcard (`*`) in paths to match any index in arrays. * * @param obj - The object/array to traverse. * @param path - The dot-separated path string (e.g. "myList.*.fieldName"). * @returns The value at the specified path or undefined if not found. */ -export const getNestedValue = (obj: any, path: string): any => { +export function getNestedValue(obj: NestedData, path: string): NestedData { const keys = path.split('.'); - return keys.reduce((acc, key) => { - if (acc == null) return undefined; + return keys.reduce((acc, key) => { + if (acc == null) { + // If acc is null or undefined, no deeper value can be retrieved + return undefined; + } if (key === '*') { // If wildcard, return all matching values from the array if (Array.isArray(acc)) { - return acc.map((item) => getNestedValue(item, keys.slice(1).join('.'))).filter((val) => val !== undefined); + // We map over each item in the array, recursing on the "remaining" path + return acc + .map( + (item) => getNestedValue(item, keys.slice(1).join('.')), // skip the current wildcard + ) + .filter((val) => val !== undefined); + } + // Wildcard on non-array is invalid => undefined + return undefined; + } + + // Non-wildcard key: + if (Array.isArray(acc)) { + // If current data is an array, try to interpret `key` as an index + const index = Number(key); + if (!Number.isNaN(index)) { + return acc[index]; } - // Wildcard on non-array is invalid + // If the path key isn't a valid index, return undefined return undefined; } - return acc[key]; + if (typeof acc === 'object') { + // If it's a plain object, we can index by key + return (acc as { [k: string]: NestedData })[key]; + } + + // If it's a primitive (string, number, boolean), there's nothing left to traverse + return undefined; }, obj); -}; +} /** * Sets a nested value in an object (or array) based on a dot-separated path, @@ -35,82 +62,103 @@ export const getNestedValue = (obj: any, path: string): any => { * @param value - The value to set at that path. * @returns A new object (or array) with the updated value. */ -export const setNestedValue = (obj: any, path: string, value: any): any => { +export function setNestedValue(obj: NestedData, path: string, value: NestedData): NestedData { const keys = path.split('.'); - /** - * Recursively traverse `obj` based on the path segments. - */ - function cloneAndSet(current: any, index: number): any { + function cloneAndSet(current: NestedData, index: number): NestedData { const key = keys[index]; - const isNumeric = !isNaN(Number(key)); + const isLastSegment = index === keys.length - 1; - // Handle wildcard (`*`) updates + // Wildcard logic if (key === '*') { if (!Array.isArray(current)) { + // Wildcard used on non-array => just return current console.error(`Wildcard '*' used on non-array at path: ${keys.slice(0, index).join('.')}`); return current; } - // Update all items in the array + + // If last segment is '*', we are setting the entire array, but that doesn't + // quite make sense. Usually you'd do something like `myList.*.fieldName`. + // We'll assume it means "set each item in the array to `value`" if last segment. + if (isLastSegment) { + return current.map(() => value); + } + + // Otherwise, for each array item, recurse return current.map((item) => cloneAndSet(item, index + 1)); } - // Base case: if we're at the final segment, just return `value`. - if (index === keys.length - 1) { - if (Array.isArray(current) && isNumeric) { + // For arrays, interpret `key` as an index, if it's numeric + const numericKey = Number(key); + const isNumericKey = !Number.isNaN(numericKey); + + if (isLastSegment) { + // Base case: actually set the value here + if (Array.isArray(current) && isNumericKey) { + // Clone array, set index const newArray = [...current]; - newArray[Number(key)] = value; + newArray[numericKey] = value; return newArray; } - if (!Array.isArray(current) && !isNumeric) { + if (typeof current === 'object' && !Array.isArray(current)) { + // Clone object, set property return { ...current, [key]: value }; } - if (isNumeric) { - const arr: any[] = Array.isArray(current) ? [...current] : []; - arr[Number(key)] = value; - return arr; + // If we're here, `current` might be a primitive or an array but `key` isn’t numeric + // We must create a new array or object to attach the value + if (isNumericKey) { + const newArr: NestedData[] = Array.isArray(current) ? [...current] : []; + newArr[numericKey] = value; + return newArr; } else { - return { ...(Array.isArray(current) ? {} : current), [key]: value }; + // Create a new object if it isn't an object + if (Array.isArray(current)) { + // If "current" is an array, fallback to a new object. + return { [key]: value }; + } else if (typeof current === 'object' && current !== null) { + // If "current" is a non-null object, safely spread it. + return { ...current as object, [key]: value }; + } else { + // Otherwise (primitive or null), fallback to a new object. + return { [key]: value }; + } } } - // Recursively update the next level - const nextKey = keys[index + 1]; - const nextIsNumeric = !isNaN(Number(nextKey)); - - if (Array.isArray(current) && isNumeric) { + // Recursive case: set deeper paths + let child: NestedData; + if (Array.isArray(current) && isNumericKey) { + // If current is an array and key is numeric => we set that index const newArray = [...current]; - const childVal = current[Number(key)]; - newArray[Number(key)] = cloneAndSet( - childVal !== undefined ? childVal : nextIsNumeric ? [] : {}, - index + 1, - ); + child = current[numericKey]; + newArray[numericKey] = cloneAndSet(child !== undefined ? child : /* create child if missing */ {}, index + 1); return newArray; - } else if (!Array.isArray(current) && !isNumeric) { + } else if (typeof current === 'object' && !Array.isArray(current) && !isNumericKey && current !== null) { const newObj = { ...current }; - const childVal = current[key]; - newObj[key] = cloneAndSet( - childVal !== undefined ? childVal : nextIsNumeric ? [] : {}, - index + 1, - ); + child = current[key]; + newObj[key] = cloneAndSet(child !== undefined ? child : /* create child if missing */ {}, index + 1); return newObj; } else { - if (isNumeric) { - const arr: any[] = []; - arr[Number(key)] = cloneAndSet(nextIsNumeric ? [] : {}, index + 1); - return arr; + // If we got here, `current` might be a primitive or array with wrong key type, etc. + // We must create something new to continue. + if (isNumericKey) { + const newArr: NestedData[] = []; + newArr[numericKey] = cloneAndSet({}, index + 1); + return newArr; } else { return { - [key]: cloneAndSet(nextIsNumeric ? [] : {}, index + 1), + [key]: cloneAndSet({}, index + 1), }; } } } + // Ensure `obj` is initialized to an array or object if it's null/undefined if (obj == null) { - const firstKeyIsNumeric = !isNaN(Number(keys[0])); + const firstKeyIsNumeric = !Number.isNaN(Number(keys[0])); + // If the first key is numeric, we start with an array; otherwise, an object obj = firstKeyIsNumeric ? [] : {}; } return cloneAndSet(obj, 0); -}; +} From 46a094f14b277ff23528313ce9beca8d46862282 Mon Sep 17 00:00:00 2001 From: a-limyr Date: Wed, 15 Jan 2025 15:13:16 +0100 Subject: [PATCH 144/195] Keeping code up to date. --- client/package-lock.json | 108 ++++++++++ .../SearchInput/TripQueryArguments.tsx | 2 +- .../components/SearchInput/nestedUtils.tsx | 196 +++++++----------- 3 files changed, 185 insertions(+), 121 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index e403fa203e4..454bccb8856 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1137,6 +1137,114 @@ "node": ">=18" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@envelop/core": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@envelop/core/-/core-5.0.2.tgz", diff --git a/client/src/components/SearchInput/TripQueryArguments.tsx b/client/src/components/SearchInput/TripQueryArguments.tsx index e1706f60458..42ef1d4fb8d 100644 --- a/client/src/components/SearchInput/TripQueryArguments.tsx +++ b/client/src/components/SearchInput/TripQueryArguments.tsx @@ -302,7 +302,7 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab return (
- {arrayVal.map((_item: any, index: number) => { + {arrayVal.map((_item: unknown, index: number) => { const itemPath = `${listPath}.${index}`; const itemNestedArgs = allArgs diff --git a/client/src/components/SearchInput/nestedUtils.tsx b/client/src/components/SearchInput/nestedUtils.tsx index d72296d3cb6..ffa915de087 100644 --- a/client/src/components/SearchInput/nestedUtils.tsx +++ b/client/src/components/SearchInput/nestedUtils.tsx @@ -1,164 +1,120 @@ -export type NestedData = null | undefined | string | number | boolean | NestedData[] | { [key: string]: NestedData }; - /** - * Retrieves a nested value from an object/array based on a dot-separated path. - * Supports wildcard (`*`) in paths to match any index in arrays. - * + * Retrieves a nested value from an object based on a dot-separated path. * @param obj - The object/array to traverse. - * @param path - The dot-separated path string (e.g. "myList.*.fieldName"). + * @param path - The dot-separated path string (e.g. "myList.0.fieldName"). * @returns The value at the specified path or undefined if not found. */ -export function getNestedValue(obj: NestedData, path: string): NestedData { - const keys = path.split('.'); - - return keys.reduce((acc, key) => { - if (acc == null) { - // If acc is null or undefined, no deeper value can be retrieved - return undefined; - } - - if (key === '*') { - // If wildcard, return all matching values from the array - if (Array.isArray(acc)) { - // We map over each item in the array, recursing on the "remaining" path - return acc - .map( - (item) => getNestedValue(item, keys.slice(1).join('.')), // skip the current wildcard - ) - .filter((val) => val !== undefined); - } - // Wildcard on non-array is invalid => undefined - return undefined; - } - - // Non-wildcard key: - if (Array.isArray(acc)) { - // If current data is an array, try to interpret `key` as an index - const index = Number(key); - if (!Number.isNaN(index)) { - return acc[index]; - } - // If the path key isn't a valid index, return undefined - return undefined; - } - - if (typeof acc === 'object') { - // If it's a plain object, we can index by key - return (acc as { [k: string]: NestedData })[key]; - } - - // If it's a primitive (string, number, boolean), there's nothing left to traverse - return undefined; +export const getNestedValue = (obj: any, path: string): any => { + return path.split('.').reduce((acc, key) => { + if (acc == null) return undefined; + return acc[key]; }, obj); -} +}; /** * Sets a nested value in an object (or array) based on a dot-separated path, * returning a new top-level object/array to ensure immutability. - * Supports wildcard (`*`) in paths for updating all items in an array. + * + * This version detects numeric path segments (like "0", "1") and uses arrays + * at those levels. Non-numeric segments use objects. If there's a mismatch, + * it will convert that level to the correct type. * * @param obj - The original object/array. - * @param path - The dot-separated path string (e.g. "myList.*.fieldName"). + * @param path - The dot-separated path string (e.g. "myList.0.fieldName"). * @param value - The value to set at that path. * @returns A new object (or array) with the updated value. */ -export function setNestedValue(obj: NestedData, path: string, value: NestedData): NestedData { +export const setNestedValue = (obj: any, path: string, value: any): any => { const keys = path.split('.'); - function cloneAndSet(current: NestedData, index: number): NestedData { + /** + * Recursively traverse `obj` based on the path segments. + * At each level, create a shallow clone of the array/object, + * then update the next key. + */ + function cloneAndSet(current: any, index: number): any { const key = keys[index]; - const isLastSegment = index === keys.length - 1; - - // Wildcard logic - if (key === '*') { - if (!Array.isArray(current)) { - // Wildcard used on non-array => just return current - console.error(`Wildcard '*' used on non-array at path: ${keys.slice(0, index).join('.')}`); - return current; - } + const isNumeric = !isNaN(Number(key)); - // If last segment is '*', we are setting the entire array, but that doesn't - // quite make sense. Usually you'd do something like `myList.*.fieldName`. - // We'll assume it means "set each item in the array to `value`" if last segment. - if (isLastSegment) { - return current.map(() => value); - } - - // Otherwise, for each array item, recurse - return current.map((item) => cloneAndSet(item, index + 1)); - } - - // For arrays, interpret `key` as an index, if it's numeric - const numericKey = Number(key); - const isNumericKey = !Number.isNaN(numericKey); - - if (isLastSegment) { - // Base case: actually set the value here - if (Array.isArray(current) && isNumericKey) { - // Clone array, set index + // Base case: if we're at the final segment, just return `value`. + if (index === keys.length - 1) { + // If current is an array and key is numeric, place `value` at that index + if (Array.isArray(current) && isNumeric) { const newArray = [...current]; - newArray[numericKey] = value; + newArray[Number(key)] = value; return newArray; } - if (typeof current === 'object' && !Array.isArray(current)) { - // Clone object, set property + // If current is an object and key is non-numeric, place `value` + if (!Array.isArray(current) && !isNumeric) { return { ...current, [key]: value }; } - // If we're here, `current` might be a primitive or an array but `key` isn’t numeric - // We must create a new array or object to attach the value - if (isNumericKey) { - const newArr: NestedData[] = Array.isArray(current) ? [...current] : []; - newArr[numericKey] = value; - return newArr; + // If there's a mismatch, create the correct type + if (isNumeric) { + // We expected an array, so create a new one + const arr: any[] = Array.isArray(current) ? [...current] : []; + arr[Number(key)] = value; + return arr; } else { - // Create a new object if it isn't an object - if (Array.isArray(current)) { - // If "current" is an array, fallback to a new object. - return { [key]: value }; - } else if (typeof current === 'object' && current !== null) { - // If "current" is a non-null object, safely spread it. - return { ...current as object, [key]: value }; - } else { - // Otherwise (primitive or null), fallback to a new object. - return { [key]: value }; - } + // We expected an object + return { ...(Array.isArray(current) ? {} : current), [key]: value }; } } - // Recursive case: set deeper paths - let child: NestedData; - if (Array.isArray(current) && isNumericKey) { - // If current is an array and key is numeric => we set that index + // If we are *not* at the final segment, we need to recurse deeper. + + // Next level + const nextKey = keys[index + 1]; + const nextIsNumeric = !isNaN(Number(nextKey)); + + if (Array.isArray(current) && isNumeric) { + // current is an array, and we have a numeric key const newArray = [...current]; - child = current[numericKey]; - newArray[numericKey] = cloneAndSet(child !== undefined ? child : /* create child if missing */ {}, index + 1); + const childVal = current[Number(key)]; + newArray[Number(key)] = cloneAndSet( + // pass existing childVal or fallback to correct type + childVal !== undefined ? childVal : nextIsNumeric ? [] : {}, + index + 1 + ); return newArray; - } else if (typeof current === 'object' && !Array.isArray(current) && !isNumericKey && current !== null) { + + } else if (!Array.isArray(current) && !isNumeric) { + // current is an object, and we have a string key const newObj = { ...current }; - child = current[key]; - newObj[key] = cloneAndSet(child !== undefined ? child : /* create child if missing */ {}, index + 1); + const childVal = current[key]; + newObj[key] = cloneAndSet( + childVal !== undefined ? childVal : nextIsNumeric ? [] : {}, + index + 1 + ); return newObj; + } else { - // If we got here, `current` might be a primitive or array with wrong key type, etc. - // We must create something new to continue. - if (isNumericKey) { - const newArr: NestedData[] = []; - newArr[numericKey] = cloneAndSet({}, index + 1); - return newArr; + // There's a mismatch: + // e.g. current is an object but key is numeric (so we want an array), + // or current is an array but key is non-numeric (so we want an object). + // We'll convert to the correct type at this level. + if (isNumeric) { + // create array + const arr: any[] = []; + arr[Number(key)] = cloneAndSet( + nextIsNumeric ? [] : {}, + index + 1 + ); + return arr; } else { + // create object return { - [key]: cloneAndSet({}, index + 1), + [key]: cloneAndSet(nextIsNumeric ? [] : {}, index + 1), }; } } } - // Ensure `obj` is initialized to an array or object if it's null/undefined + // If the root `obj` is undefined or null, let the root + // be an object or array depending on the first key: if (obj == null) { - const firstKeyIsNumeric = !Number.isNaN(Number(keys[0])); - // If the first key is numeric, we start with an array; otherwise, an object + const firstKeyIsNumeric = !isNaN(Number(keys[0])); obj = firstKeyIsNumeric ? [] : {}; } return cloneAndSet(obj, 0); -} +}; From afa1b161ebeb30317f9843e280b1e18fca7de50d Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 16 Jan 2025 09:54:48 +0200 Subject: [PATCH 145/195] Use method to get field instead of the field directly. --- .../module/DirectTransferGenerator.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index bcede5798c4..0ea7d30f25a 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -142,9 +142,10 @@ public void buildGraph() { LOG.debug("Linking stop '{}' {}", stop, ts0); // Calculate default transfers. - for (RouteRequest transferProfile : transferConfiguration.defaultTransferRequests) { + for (RouteRequest transferProfile : transferConfiguration.defaultTransferRequests()) { StreetMode mode = transferProfile.journey().transfer().mode(); - var nearbyStops = transferConfiguration.defaultNearbyStopFinderForMode + var nearbyStops = transferConfiguration + .defaultNearbyStopFinderForMode() .get(mode) .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false); for (NearbyStop sd : nearbyStops) { @@ -159,10 +160,11 @@ public void buildGraph() { } } // Calculate flex transfers if flex routing is enabled. - for (RouteRequest transferProfile : transferConfiguration.flexTransferRequests) { + for (RouteRequest transferProfile : transferConfiguration.flexTransferRequests()) { // Flex transfer requests only use the WALK mode. StreetMode mode = StreetMode.WALK; - var nearbyStops = transferConfiguration.defaultNearbyStopFinderForMode + var nearbyStops = transferConfiguration + .defaultNearbyStopFinderForMode() .get(mode) .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), true); // This code is for finding transfers from AreaStops to Stops, transfers @@ -181,9 +183,10 @@ public void buildGraph() { } // Calculate transfers between stops that are visited by trips that allow cars, if configured. if (carsAllowedStops.contains(stop)) { - for (RouteRequest transferProfile : transferConfiguration.carsAllowedStopTransferRequests) { + for (RouteRequest transferProfile : transferConfiguration.carsAllowedStopTransferRequests()) { StreetMode mode = transferProfile.journey().transfer().mode(); - var nearbyStops = transferConfiguration.carsAllowedStopNearbyStopFinderForMode + var nearbyStops = transferConfiguration + .carsAllowedStopNearbyStopFinderForMode() .get(mode) .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false); for (NearbyStop sd : nearbyStops) { From c649ab70d4382226e6a126bc287aee1b99a5bbd2 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 16 Jan 2025 09:57:09 +0200 Subject: [PATCH 146/195] Change comment style. --- .../graph_builder/module/DirectTransferGenerator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index 0ea7d30f25a..b8f8a88af6c 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -89,10 +89,10 @@ public DirectTransferGenerator( @Override public void buildGraph() { - /* Initialize transit model index which is needed by the nearby stop finder. */ + // Initialize transit model index which is needed by the nearby stop finder. timetableRepository.index(); - /* The linker will use streets if they are available, or straight-line distance otherwise. */ + // The linker will use streets if they are available, or straight-line distance otherwise. NearbyStopFinder nearbyStopFinder = createNearbyStopFinder(defaultMaxTransferDuration); List stops = graph.getVerticesOfType(TransitStopVertex.class); @@ -301,7 +301,7 @@ private TransferConfiguration parseTransferParameters(NearbyStopFinder nearbySto List carsAllowedStopTransferRequests = new ArrayList<>(); List flexTransferRequests = new ArrayList<>(); HashMap defaultNearbyStopFinderForMode = new HashMap<>(); - /* These are used for calculating transfers only between carsAllowedStops. */ + // These are used for calculating transfers only between carsAllowedStops. HashMap carsAllowedStopNearbyStopFinderForMode = new HashMap<>(); // Check that the mode specified in transferParametersForMode can also be found in transferRequests. From 91782ef5ac294d573ba370f43490fa8e14dc4f66 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 16 Jan 2025 12:29:09 +0200 Subject: [PATCH 147/195] Split transfer generation into methods. --- .../module/DirectTransferGenerator.java | 168 +++++++++++------- 1 file changed, 104 insertions(+), 64 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java index b8f8a88af6c..f6e6ce1b238 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/DirectTransferGenerator.java @@ -141,70 +141,15 @@ public void buildGraph() { LOG.debug("Linking stop '{}' {}", stop, ts0); - // Calculate default transfers. - for (RouteRequest transferProfile : transferConfiguration.defaultTransferRequests()) { - StreetMode mode = transferProfile.journey().transfer().mode(); - var nearbyStops = transferConfiguration - .defaultNearbyStopFinderForMode() - .get(mode) - .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false); - for (NearbyStop sd : nearbyStops) { - // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { - continue; - } - if (sd.stop.transfersNotAllowed()) { - continue; - } - createPathTransfer(stop, sd.stop, sd, distinctTransfers, mode); - } - } - // Calculate flex transfers if flex routing is enabled. - for (RouteRequest transferProfile : transferConfiguration.flexTransferRequests()) { - // Flex transfer requests only use the WALK mode. - StreetMode mode = StreetMode.WALK; - var nearbyStops = transferConfiguration - .defaultNearbyStopFinderForMode() - .get(mode) - .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), true); - // This code is for finding transfers from AreaStops to Stops, transfers - // from Stops to AreaStops and between Stops are already covered above. - for (NearbyStop sd : nearbyStops) { - // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { - continue; - } - if (sd.stop instanceof RegularStop) { - continue; - } - // The TransferKey and PathTransfer are created differently for flex routing. - createPathTransfer(sd.stop, stop, sd, distinctTransfers, mode); - } - } - // Calculate transfers between stops that are visited by trips that allow cars, if configured. - if (carsAllowedStops.contains(stop)) { - for (RouteRequest transferProfile : transferConfiguration.carsAllowedStopTransferRequests()) { - StreetMode mode = transferProfile.journey().transfer().mode(); - var nearbyStops = transferConfiguration - .carsAllowedStopNearbyStopFinderForMode() - .get(mode) - .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false); - for (NearbyStop sd : nearbyStops) { - // Skip the origin stop, loop transfers are not needed. - if (sd.stop == stop) { - continue; - } - if (sd.stop.transfersNotAllowed()) { - continue; - } - // Only calculate transfers between carsAllowedStops. - if (!carsAllowedStops.contains(sd.stop)) { - continue; - } - createPathTransfer(stop, sd.stop, sd, distinctTransfers, mode); - } - } - } + calculateDefaultTransfers(transferConfiguration, ts0, stop, distinctTransfers); + calculateFlexTransfers(transferConfiguration, ts0, stop, distinctTransfers); + calculateCarsAllowedTransfers( + transferConfiguration, + ts0, + stop, + distinctTransfers, + carsAllowedStops + ); LOG.debug( "Linked stop {} with {} transfers to stops with different patterns.", @@ -373,6 +318,101 @@ private TransferConfiguration parseTransferParameters(NearbyStopFinder nearbySto ); } + /** + * This method calculates default transfers. + */ + private void calculateDefaultTransfers( + TransferConfiguration transferConfiguration, + TransitStopVertex ts0, + RegularStop stop, + Map distinctTransfers + ) { + for (RouteRequest transferProfile : transferConfiguration.defaultTransferRequests()) { + StreetMode mode = transferProfile.journey().transfer().mode(); + var nearbyStops = transferConfiguration + .defaultNearbyStopFinderForMode() + .get(mode) + .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false); + for (NearbyStop sd : nearbyStops) { + // Skip the origin stop, loop transfers are not needed. + if (sd.stop == stop) { + continue; + } + if (sd.stop.transfersNotAllowed()) { + continue; + } + createPathTransfer(stop, sd.stop, sd, distinctTransfers, mode); + } + } + } + + /** + * This method calculates flex transfers if flex routing is enabled. + */ + private void calculateFlexTransfers( + TransferConfiguration transferConfiguration, + TransitStopVertex ts0, + RegularStop stop, + Map distinctTransfers + ) { + for (RouteRequest transferProfile : transferConfiguration.flexTransferRequests()) { + // Flex transfer requests only use the WALK mode. + StreetMode mode = StreetMode.WALK; + var nearbyStops = transferConfiguration + .defaultNearbyStopFinderForMode() + .get(mode) + .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), true); + // This code is for finding transfers from AreaStops to Stops, transfers + // from Stops to AreaStops and between Stops are already covered above. + for (NearbyStop sd : nearbyStops) { + // Skip the origin stop, loop transfers are not needed. + if (sd.stop == stop) { + continue; + } + if (sd.stop instanceof RegularStop) { + continue; + } + // The TransferKey and PathTransfer are created differently for flex routing. + createPathTransfer(sd.stop, stop, sd, distinctTransfers, mode); + } + } + } + + /** + * This method calculates transfers between stops that are visited by trips that allow cars, if configured. + */ + private void calculateCarsAllowedTransfers( + TransferConfiguration transferConfiguration, + TransitStopVertex ts0, + RegularStop stop, + Map distinctTransfers, + Set carsAllowedStops + ) { + if (carsAllowedStops.contains(stop)) { + for (RouteRequest transferProfile : transferConfiguration.carsAllowedStopTransferRequests()) { + StreetMode mode = transferProfile.journey().transfer().mode(); + var nearbyStops = transferConfiguration + .carsAllowedStopNearbyStopFinderForMode() + .get(mode) + .findNearbyStops(ts0, transferProfile, transferProfile.journey().transfer(), false); + for (NearbyStop sd : nearbyStops) { + // Skip the origin stop, loop transfers are not needed. + if (sd.stop == stop) { + continue; + } + if (sd.stop.transfersNotAllowed()) { + continue; + } + // Only calculate transfers between carsAllowedStops. + if (!carsAllowedStops.contains(sd.stop)) { + continue; + } + createPathTransfer(stop, sd.stop, sd, distinctTransfers, mode); + } + } + } + } + private record TransferConfiguration( List defaultTransferRequests, List carsAllowedStopTransferRequests, From 4312caf6a4dd789776ebed3d74531270ef027faf Mon Sep 17 00:00:00 2001 From: JustCris Date: Thu, 16 Jan 2025 11:57:39 +0100 Subject: [PATCH 148/195] throttle invalid current fuel percent log --- .../datasources/GbfsFreeVehicleStatusMapper.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index eb5610646fe..3510c0768a1 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -17,12 +17,14 @@ import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.basic.Ratio; import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.utils.logging.Throttle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class GbfsFreeVehicleStatusMapper { private static final Logger LOG = LoggerFactory.getLogger(GbfsFreeVehicleStatusMapper.class); + private static final Throttle THROTTLE = Throttle.ofOneMinute(); private final VehicleRentalSystem system; @@ -65,11 +67,12 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { fuelPercent = new Ratio(vehicle.getCurrentFuelPercent()); } } catch (IllegalArgumentException e) { - LOG.warn( - "Current fuel percent value not valid: {} - {}", - vehicle.getCurrentFuelPercent(), - e.getMessage() - ); + THROTTLE.throttle(() -> + LOG.warn( + "Current fuel percent value not valid: {} - {}", + vehicle.getCurrentFuelPercent(), + e.getMessage() + )); } Distance rangeMeters = null; try { From 17a9d795f4770d60bcf92588dc6ba02426965ad2 Mon Sep 17 00:00:00 2001 From: JustCris Date: Thu, 16 Jan 2025 12:17:15 +0100 Subject: [PATCH 149/195] use Ratio refactor in tests --- .../model/TestFreeFloatingRentalVehicleBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java index 85dd57bfc1d..b99745cbd97 100644 --- a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java +++ b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java @@ -17,7 +17,7 @@ public class TestFreeFloatingRentalVehicleBuilder { private double latitude = DEFAULT_LATITUDE; private double longitude = DEFAULT_LONGITUDE; - private Ratio currentFuelPercent = new Ratio(DEFAULT_CURRENT_FUEL_PERCENT); + private Ratio currentFuelPercent = Ratio.of(DEFAULT_CURRENT_FUEL_PERCENT); private Double currentRangeMeters = DEFAULT_CURRENT_RANGE_METERS; private VehicleRentalSystem system = null; private String network = NETWORK_1; @@ -44,7 +44,7 @@ public TestFreeFloatingRentalVehicleBuilder withCurrentFuelPercent( if (currentFuelPercent == null) { this.currentFuelPercent = null; } else { - this.currentFuelPercent = new Ratio(currentFuelPercent); + this.currentFuelPercent = Ratio.ofBoxed(currentFuelPercent, ignore -> {}).orElse(null); } return this; } From 88e52524a17ce1defc42f77e8514252a0b05d26c Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 16 Jan 2025 14:19:55 +0000 Subject: [PATCH 150/195] Add changelog entry for #6355 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 4639e68da92..6384feb75bf 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -74,6 +74,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - 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) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From dd5e23ddaf91747c6210b4e394c3a20fe651e838 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 16 Jan 2025 14:22:29 +0000 Subject: [PATCH 151/195] Add changelog entry for #6343 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 6384feb75bf..a01ad18a8fb 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -75,6 +75,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - 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) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From 1030408bf66e6f2290815427d06c34094caca5bc Mon Sep 17 00:00:00 2001 From: OTP Serialization Version Bot Date: Thu, 16 Jan 2025 14:22:57 +0000 Subject: [PATCH 152/195] Bump serialization version id for #6343 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f77d753472d..f1d1729b936 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ - 176 + 177 32.1 From 4d50bdc0d61b746c26cd85f1af00b067654861c4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 16 Jan 2025 15:25:45 +0100 Subject: [PATCH 153/195] Combine catch blocks --- .../opentripplanner/transit/speed_test/TransferCacheTest.java | 3 --- 1 file changed, 3 deletions(-) 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 index 1c41cf67044..e6c8de67688 100644 --- a/application/src/test/java/org/opentripplanner/transit/speed_test/TransferCacheTest.java +++ b/application/src/test/java/org/opentripplanner/transit/speed_test/TransferCacheTest.java @@ -40,9 +40,6 @@ public static void main(String[] args) { measureTransferCacheComputation(timer, timetableRepository); timer.finishUp(); - } catch (OtpAppException ae) { - System.err.println(ae.getMessage()); - System.exit(1); } catch (Exception e) { System.err.println(e.getMessage()); e.printStackTrace(System.err); From c16503a484a8898965829f590e451ffa4cc2d1bb Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 17 Jan 2025 13:40:29 +0100 Subject: [PATCH 154/195] Extract mapper for bookWhen, add test --- .../transmodel/mapping/BookingInfoMapper.java | 38 +++++++++ .../timetable/BookingArrangementType.java | 30 +------ .../mapping/BookingInfoMapperTest.java | 83 +++++++++++++++++++ 3 files changed, 123 insertions(+), 28 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/apis/transmodel/mapping/BookingInfoMapper.java create mode 100644 application/src/test/java/org/opentripplanner/apis/transmodel/mapping/BookingInfoMapperTest.java diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/BookingInfoMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/BookingInfoMapper.java new file mode 100644 index 00000000000..de59794ea18 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/BookingInfoMapper.java @@ -0,0 +1,38 @@ +package org.opentripplanner.apis.transmodel.mapping; + +import org.opentripplanner.transit.model.timetable.booking.BookingInfo; +import org.opentripplanner.transit.model.timetable.booking.BookingTime; + +/** + * Maps the {@link BookingInfo} to enum value (as a string) returned by the API. + */ +public class BookingInfoMapper { + + public static String mapToBookWhen(BookingInfo bookingInfo) { + if (bookingInfo.getMinimumBookingNotice().isPresent()) { + return null; + } + BookingTime latestBookingTime = bookingInfo.getLatestBookingTime(); + BookingTime earliestBookingTime = bookingInfo.getEarliestBookingTime(); + + // Try to deduce the original enum from stored values + if (earliestBookingTime == null) { + if (latestBookingTime == null) { + return "timeOfTravelOnly"; + } else if (latestBookingTime.getDaysPrior() == 1) { + return "untilPreviousDay"; + } else if (latestBookingTime.getDaysPrior() == 0) { + return "advanceAndDayOfTravel"; + } else { + return "other"; + } + } else if ( + earliestBookingTime.getDaysPrior() == 0 && + (latestBookingTime == null || latestBookingTime.getDaysPrior() == 0) + ) { + return "dayOfTravelOnly"; + } else { + return "other"; + } + } +} diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java index 911ec8d9b0c..097fa92baca 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java @@ -6,6 +6,7 @@ import graphql.schema.GraphQLList; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; +import org.opentripplanner.apis.transmodel.mapping.BookingInfoMapper; import org.opentripplanner.apis.transmodel.model.EnumTypes; import org.opentripplanner.apis.transmodel.model.framework.TransmodelScalars; import org.opentripplanner.transit.model.organization.ContactInfo; @@ -107,34 +108,7 @@ public static GraphQLObjectType create() { .name("bookWhen") .description("Time constraints for booking") .type(EnumTypes.PURCHASE_WHEN) - .dataFetcher(environment -> { - BookingInfo bookingInfo = bookingInfo(environment); - if (bookingInfo.getMinimumBookingNotice().isPresent()) { - return null; - } - BookingTime latestBookingTime = bookingInfo.getLatestBookingTime(); - BookingTime earliestBookingTime = bookingInfo.getEarliestBookingTime(); - - // Try to deduce the original enum from stored values - if (earliestBookingTime == null) { - if (latestBookingTime == null) { - return "timeOfTravelOnly"; - } else if (latestBookingTime.getDaysPrior() == 1) { - return "untilPreviousDay"; - } else if (latestBookingTime.getDaysPrior() == 0) { - return "advanceAndDayOfTravel"; - } else { - return "other"; - } - } else if ( - earliestBookingTime.getDaysPrior() == 0 && - (latestBookingTime == null || latestBookingTime.getDaysPrior() == 0) - ) { - return "dayOfTravelOnly"; - } else { - return "other"; - } - }) + .dataFetcher(environment -> BookingInfoMapper.mapToBookWhen(bookingInfo(environment))) .build() ) .field( diff --git a/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/BookingInfoMapperTest.java b/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/BookingInfoMapperTest.java new file mode 100644 index 00000000000..76af5f76d37 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/apis/transmodel/mapping/BookingInfoMapperTest.java @@ -0,0 +1,83 @@ +package org.opentripplanner.apis.transmodel.mapping; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.apis.transmodel.mapping.BookingInfoMapper.mapToBookWhen; + +import java.time.Duration; +import java.time.LocalTime; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.opentripplanner.transit.model.timetable.booking.BookingInfo; +import org.opentripplanner.transit.model.timetable.booking.BookingTime; + +class BookingInfoMapperTest { + + private static final Duration TEN_MINUTES = Duration.ofMinutes(10); + private static final BookingTime BOOKING_TIME_ZERO_DAYS_PRIOR = new BookingTime( + LocalTime.of(10, 0), + 0 + ); + + @Test + void bookingNotice() { + assertNull(mapToBookWhen(BookingInfo.of().withMinimumBookingNotice(TEN_MINUTES).build())); + } + + @Test + void timeOfTravelOnly() { + assertEquals("timeOfTravelOnly", mapToBookWhen(BookingInfo.of().build())); + } + + @Test + void untilPreviousDay() { + var info = daysPrior(1); + assertEquals("untilPreviousDay", mapToBookWhen(info)); + } + + @Test + void advanceAndDayOfTravel() { + var info = daysPrior(0); + assertEquals("advanceAndDayOfTravel", mapToBookWhen(info)); + } + + @ParameterizedTest + @ValueSource(ints = { 2, 3, 4, 14, 28 }) + void other(int days) { + var info = daysPrior(days); + assertEquals("other", mapToBookWhen(info)); + } + + @Test + void dayOfTravelOnly() { + var info = BookingInfo.of().withEarliestBookingTime(BOOKING_TIME_ZERO_DAYS_PRIOR).build(); + assertEquals("dayOfTravelOnly", mapToBookWhen(info)); + } + + @Test + void latestBookingTime() { + var info = BookingInfo + .of() + .withEarliestBookingTime(BOOKING_TIME_ZERO_DAYS_PRIOR) + .withLatestBookingTime(BOOKING_TIME_ZERO_DAYS_PRIOR) + .build(); + assertEquals("dayOfTravelOnly", mapToBookWhen(info)); + } + + @Test + void earliestBookingTimeZero() { + var info = BookingInfo + .of() + .withEarliestBookingTime(new BookingTime(LocalTime.of(10, 0), 10)) + .build(); + assertEquals("other", mapToBookWhen(info)); + } + + private static BookingInfo daysPrior(int daysPrior) { + return BookingInfo + .of() + .withLatestBookingTime(new BookingTime(LocalTime.of(10, 0), daysPrior)) + .build(); + } +} From 5639c3181955c665aa3a55866cd4fedc93caa5c7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 17 Jan 2025 14:05:28 +0100 Subject: [PATCH 155/195] Add enum values for RelativeDirection in Transmodel API --- .../transmodel/mapping/RelativeDirectionMapper.java | 10 +++++----- .../apis/transmodel/model/EnumTypes.java | 3 +++ .../org/opentripplanner/apis/transmodel/schema.graphql | 3 +++ .../apis/transmodel/model/EnumTypesTest.java | 3 ++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/RelativeDirectionMapper.java b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/RelativeDirectionMapper.java index 3228cb914df..1787515e0c7 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/RelativeDirectionMapper.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/mapping/RelativeDirectionMapper.java @@ -22,12 +22,12 @@ public static RelativeDirection map(RelativeDirection relativeDirection) { CIRCLE_COUNTERCLOCKWISE, ELEVATOR, UTURN_LEFT, - UTURN_RIGHT -> relativeDirection; - // for these the Transmodel API doesn't have a mapping. should it? - case ENTER_STATION, + UTURN_RIGHT, + ENTER_STATION, EXIT_STATION, - ENTER_OR_EXIT_STATION, - FOLLOW_SIGNS -> RelativeDirection.CONTINUE; + FOLLOW_SIGNS -> relativeDirection; + // this type should never be exposed by an API + case ENTER_OR_EXIT_STATION -> RelativeDirection.CONTINUE; }; } } diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java index 2f8e69cc593..fba1a8f637a 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java @@ -289,6 +289,9 @@ public class EnumTypes { .value("elevator", RelativeDirection.ELEVATOR) .value("uturnLeft", RelativeDirection.UTURN_LEFT) .value("uturnRight", RelativeDirection.UTURN_RIGHT) + .value("enterStation", RelativeDirection.ENTER_STATION) + .value("exitStation", RelativeDirection.EXIT_STATION) + .value("followSigns", RelativeDirection.FOLLOW_SIGNS) .build(); public static final GraphQLEnumType REPORT_TYPE = GraphQLEnumType diff --git a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 6834d375bf1..c01115a2120 100644 --- a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -1719,6 +1719,9 @@ enum RelativeDirection { continue depart elevator + enterStation + exitStation + followSigns hardLeft hardRight left 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 9090cd1bdc5..5a83ec66bb8 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,6 +1,7 @@ package org.opentripplanner.apis.transmodel.model; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -93,7 +94,7 @@ void serializeRelativeDirection(RelativeDirection direction) { Locale.ENGLISH ); assertInstanceOf(String.class, value); - assertNotNull(value); + assertFalse(((String) value).isEmpty()); } @Test From e4653945dcfd5f66e1ff98c9f995780edeaa2a19 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:50:23 +0000 Subject: [PATCH 156/195] chore(deps): update vitest monorepo to v3 --- client/package-lock.json | 1183 +++++--------------------------------- client/package.json | 4 +- 2 files changed, 137 insertions(+), 1050 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index bb145274bd4..86d94c08ae1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -30,7 +30,7 @@ "@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-plugin-import": "2.31.0", @@ -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" @@ -9055,10 +9093,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 +10496,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 +10510,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 +11036,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 +11124,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..c25ddc14212 100644 --- a/client/package.json +++ b/client/package.json @@ -39,7 +39,7 @@ "@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-plugin-import": "2.31.0", @@ -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" } } From 7d5d14438632da79a1fbea253d3dfba3031d4bb4 Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Sun, 19 Jan 2025 18:40:23 +0000 Subject: [PATCH 157/195] Upgrade debug client to version 2025/01/2025-01-19T18:39 --- application/src/client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/client/index.html b/application/src/client/index.html index d3dd8544a53..29f6abb024b 100644 --- a/application/src/client/index.html +++ b/application/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug - - + +
From eb226fd4570b815a8947be6c63f5d1d4eacdda4b Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Mon, 20 Jan 2025 08:57:02 +0000 Subject: [PATCH 158/195] Upgrade debug client to version 2025/01/2025-01-20T08:56 --- application/src/client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/client/index.html b/application/src/client/index.html index 29f6abb024b..d105b0aab08 100644 --- a/application/src/client/index.html +++ b/application/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug - - + +
From 447e7dcf13ee058ab365ea0791dd8a6c761c801a Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 20 Jan 2025 10:42:28 +0000 Subject: [PATCH 159/195] Add changelog entry for #6357 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index a01ad18a8fb..3d7e4b1c6c6 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -76,6 +76,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - 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) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From e179d0277341f1b4027aad65432a8497968a9231 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 21 Jan 2025 10:43:54 +0200 Subject: [PATCH 160/195] Add maxStopCountForMode to the standalone router config. --- .../src/test/resources/standalone/config/router-config.json | 3 +++ doc/user/RouteRequest.md | 3 +++ doc/user/RouterConfiguration.md | 3 +++ 3 files changed, 9 insertions(+) diff --git a/application/src/test/resources/standalone/config/router-config.json b/application/src/test/resources/standalone/config/router-config.json index 77c67d85742..c20a5fbc6ca 100644 --- a/application/src/test/resources/standalone/config/router-config.json +++ b/application/src/test/resources/standalone/config/router-config.json @@ -99,6 +99,9 @@ "BIKE_RENTAL": "20m" }, "maxStopCount": 500, + "maxStopCountForMode": { + "CAR": 0 + }, "penalty": { "FLEXIBLE": { "timePenalty": "2m + 1.1t", diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index f223c644604..ea7ced375ab 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -1263,6 +1263,9 @@ include stairs as a last result. "BIKE_RENTAL" : "20m" }, "maxStopCount" : 500, + "maxStopCountForMode" : { + "CAR" : 0 + }, "penalty" : { "FLEXIBLE" : { "timePenalty" : "2m + 1.1t", diff --git a/doc/user/RouterConfiguration.md b/doc/user/RouterConfiguration.md index 82d14f36392..a9391818fc1 100644 --- a/doc/user/RouterConfiguration.md +++ b/doc/user/RouterConfiguration.md @@ -551,6 +551,9 @@ Used to group requests when monitoring OTP. "BIKE_RENTAL" : "20m" }, "maxStopCount" : 500, + "maxStopCountForMode" : { + "CAR" : 0 + }, "penalty" : { "FLEXIBLE" : { "timePenalty" : "2m + 1.1t", From 7284086da5c49dc254b2dc5e8192242f05b50e9c Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 21 Jan 2025 09:59:49 +0000 Subject: [PATCH 161/195] Add changelog entry for #6326 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 3d7e4b1c6c6..9a5f2731733 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -77,6 +77,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - 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) From fe18de186e92d5a0470c8b3d6530f887c375f433 Mon Sep 17 00:00:00 2001 From: a-limyr Date: Tue, 21 Jan 2025 13:31:12 +0100 Subject: [PATCH 162/195] Refactoring to remove use of "any" types. --- .../SearchInput/ArgumentTooltip.tsx | 13 +- .../components/SearchInput/ResetButton.tsx | 34 ++ .../SearchInput/TripQueryArguments.tsx | 459 ++++++++---------- .../SearchInput/ViewArgumentsRaw.tsx | 5 +- .../components/SearchInput/nestedUtils.tsx | 117 ++--- .../src/components/SearchInput/useTripArgs.ts | 14 +- client/src/screens/App.tsx | 2 +- 7 files changed, 331 insertions(+), 313 deletions(-) create mode 100644 client/src/components/SearchInput/ResetButton.tsx diff --git a/client/src/components/SearchInput/ArgumentTooltip.tsx b/client/src/components/SearchInput/ArgumentTooltip.tsx index f444ac323f9..916e5f11e7c 100644 --- a/client/src/components/SearchInput/ArgumentTooltip.tsx +++ b/client/src/components/SearchInput/ArgumentTooltip.tsx @@ -2,10 +2,11 @@ import React from 'react'; import infoIcon from '../../static/img/help-info-solid.svg'; import inputIcon from '../../static/img/input.svg'; import durationIcon from '../../static/img/lap-timer.svg'; +import {ResolvedType} from "./useTripArgs.ts"; interface ArgumentTooltipProps { - defaultValue?: string; - type?: string; + defaultValue?: string | number | boolean | object | null | undefined + type?: ResolvedType; } const ArgumentTooltip: React.FC = ({ defaultValue, type }) => { @@ -16,15 +17,15 @@ const ArgumentTooltip: React.FC = ({ defaultValue, type }) {'Info'} )} - {type !== undefined && type !== null && type === 'DoubleFunction' && ( - + {type !== undefined && type !== null && type.subtype === 'DoubleFunction' && ( + {'Info'} )} - {type !== undefined && type !== null && type === 'Duration' && ( + {type !== undefined && type !== null && type.subtype === 'Duration' && ( {'Info'} diff --git a/client/src/components/SearchInput/ResetButton.tsx b/client/src/components/SearchInput/ResetButton.tsx new file mode 100644 index 00000000000..42e9d9e3d6b --- /dev/null +++ b/client/src/components/SearchInput/ResetButton.tsx @@ -0,0 +1,34 @@ +import { TripQueryVariables } from '../../gql/graphql.ts'; +import { excludedArguments } from './excluded-arguments.ts'; +import { getNestedValue, setNestedValue } from './nestedUtils.tsx'; +import React from 'react'; + +interface ResetButtonProps { + tripQueryVariables: TripQueryVariables; + setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; +} + +const ResetButton: React.FC = ({ tripQueryVariables, setTripQueryVariables }) => { + function handleReset(): void { + // Start with an empty object (or partially typed) + let newVars: TripQueryVariables = {} as TripQueryVariables; + + // For each path in our excluded set, copy over that value (if any) + excludedArguments.forEach((excludedPath) => { + const value = getNestedValue(tripQueryVariables, excludedPath); + if (value !== undefined) { + newVars = setNestedValue(newVars, excludedPath, value) as TripQueryVariables; + } + }); + + setTripQueryVariables(newVars); + } + + return ( + + ); +}; + +export default ResetButton; diff --git a/client/src/components/SearchInput/TripQueryArguments.tsx b/client/src/components/SearchInput/TripQueryArguments.tsx index 42ef1d4fb8d..3a01372a89b 100644 --- a/client/src/components/SearchInput/TripQueryArguments.tsx +++ b/client/src/components/SearchInput/TripQueryArguments.tsx @@ -1,16 +1,44 @@ -import React, { useEffect, useState } from 'react'; -//import tripArgumentsData from '../../gql/query-arguments.json'; +import React, { JSX, useEffect, useState } from 'react'; +// import tripArgumentsData from '../../gql/query-arguments.json'; import { useTripSchema } from './TripSchemaContext'; import { TripQueryVariables } from '../../gql/graphql'; import { getNestedValue, setNestedValue } from './nestedUtils'; import ArgumentTooltip from './ArgumentTooltip.tsx'; import { excludedArguments } from './excluded-arguments.ts'; +import { ResolvedType } from './useTripArgs.ts'; +import ResetButton from './ResetButton.tsx'; interface TripQueryArgumentsProps { tripQueryVariables: TripQueryVariables; setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void; } +type DefaultValue = string | number | boolean | object | null; + +// Adjust as needed; you might need a more precise type for `ArgData`. +interface ArgData { + type: ResolvedType; + name?: string; + defaultValue?: DefaultValue; + enumValues?: string[]; + isComplex?: boolean; + isList?: boolean; + args?: Record; // Recursive for nested arguments +} + +interface ProcessedArgument { + path: string; + type: ResolvedType; + name?: string; + defaultValue?: DefaultValue; + enumValues?: string[]; + isComplex?: boolean; + isList?: boolean; +} + +/** + * Returns a human-readable name from a path like "someNestedArg.subArg". + */ function formatArgumentName(input: string): string { if (!input) { return ' '; @@ -19,28 +47,10 @@ function formatArgumentName(input: string): string { const formatted = parts[parts.length - 1].replace(/([A-Z])/g, ' $1').trim(); return formatted.replace(/\b\w/g, (char) => char.toUpperCase()) + ' '; } -type ArgumentConfig = { - path: string; - type: string; - defaultValue?: string; - enumValues?: string[]; - isComplex?: boolean; - isList?: boolean; -}; const TripQueryArguments: React.FC = ({ tripQueryVariables, setTripQueryVariables }) => { - const [argumentsList, setArgumentsList] = useState< - { - path: string; - type: string; - subtype?: string; - defaultValue?: string; - enumValues?: string[]; - isComplex?: boolean; - isList?: boolean; - }[] - >([]); - const [expandedArguments, setExpandedArguments] = useState<{ [key: string]: boolean }>({}); + const [argumentsList, setArgumentsList] = useState([]); + const [expandedArguments, setExpandedArguments] = useState>({}); const [searchText] = useState(''); const { tripArgs, loading, error } = useTripSchema(); @@ -54,94 +64,63 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab setArgumentsList(extractedArgs); }, [tripArgs, loading, error]); - /**useEffect(() => { - const tripArgs = tripArgumentsData.trip.arguments; - const extractedArgs = extractAllArgs(tripArgs); - setArgumentsList(extractedArgs); - }, []); -**/ - const extractAllArgs = ( - args: { [key: string]: any }, - parentPath: string[] = [], - ): { - path: string; - type: string; - name?: string; - defaultValue?: string; - enumValues?: string[]; - isComplex?: boolean; - isList?: boolean; - }[] => { - let allArgs: { - path: string; - type: string; - name?: string; - defaultValue?: string; - enumValues?: string[]; - isComplex?: boolean; - isList?: boolean; - }[] = []; + /** + * Recursively extracts a flat list of arguments (ProcessedArgument[]). + */ + function extractAllArgs(args: Record | undefined, parentPath: string[] = []): ProcessedArgument[] { + let allArgs: ProcessedArgument[] = []; + if (!args) return []; Object.entries(args).forEach(([argName, argData]) => { const currentPath = [...parentPath, argName].join('.'); allArgs = allArgs.concat(processArgument(argName, argData, currentPath, parentPath)); }); + return allArgs; - }; + } - const processArgument = ( + /** + * Converts a single ArgData into one or more ProcessedArgument entries. + * If the argData is an InputObject with nested fields, we recurse. + */ + function processArgument( argName: string, - argData: any, + argData: ArgData, currentPath: string, parentPath: string[], - ): { - path: string; - type: string; - name?: string; - defaultValue?: string; - enumValues?: string[]; - isComplex?: boolean; - isList?: boolean; - }[] => { - let allArgs: { - path: string; - type: string; - name?: string; - defaultValue?: string; - enumValues?: string[]; - isComplex?: boolean; - isList?: boolean; - }[] = []; + ): ProcessedArgument[] { + let allArgs: ProcessedArgument[] = []; + // Check if we have a recognized ArgData object with a `type` property if (typeof argData === 'object' && argData.type) { if (argData.type.type === 'Enum') { - const enumValues = ['Not selected', ...argData.type.values]; + const enumValues = ['Not selected', ...(argData.type.values || [])]; const defaultValue = argData.defaultValue !== undefined ? argData.defaultValue : 'Not selected'; + allArgs.push({ path: currentPath, - type: 'Enum', + type: { type: 'Enum' }, defaultValue, enumValues, isList: argData.isList, }); } else if (argData.type.type === 'InputObject' && argData.isList) { + debugger; // This is a list of InputObjects allArgs.push({ path: currentPath, - type: 'Group', // We'll still call this 'Group' + type: { type: 'Group', name: argData.type.name }, // We'll still call this 'Group' defaultValue: argData.defaultValue, isComplex: true, isList: true, }); - // NEW: Also extract subfields with a wildcard - // e.g. for `accessEgressPenalty`, we'll get `accessEgressPenalty.*.costFactor`, etc. allArgs = allArgs.concat(extractAllArgs(argData.type.fields, [...parentPath, `${argName}.*`])); } else if (argData.type.type === 'InputObject') { // Single InputObject allArgs.push({ path: currentPath, - type: 'Group', + type: { type: 'Group', name: argData.type.name }, isComplex: true, isList: false, }); @@ -149,28 +128,42 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab } else if (argData.type.type === 'Scalar') { allArgs.push({ path: currentPath, - type: argData.type.subtype, + type: { type: argData.type.type, subtype: argData.type.subtype }, defaultValue: argData.defaultValue, isList: argData.isList, }); } - } else if (typeof argData === 'object' && argData.fields) { - allArgs.push({ path: currentPath, type: 'Group', isComplex: true }); - allArgs = allArgs.concat(extractAllArgs(argData.fields, [...parentPath, argName])); + } else if (typeof argData === 'object' && argData.type?.fields) { + // Possibly a nested object with fields + allArgs.push({ + path: currentPath, + type: { type: 'Group' }, + isComplex: true, + }); + allArgs = allArgs.concat(extractAllArgs(argData.type.fields, [...parentPath, argName])); } else { - allArgs.push({ path: currentPath, type: argData.type ?? typeof argData, defaultValue: argData.defaultValue }); + // Fallback case + allArgs.push({ + path: currentPath, + type: argData.type ?? (typeof argData as unknown), // <— If argData.type is missing, fallback + defaultValue: argData.defaultValue, + }); } return allArgs; - }; + } - const normalizePathForList = (path: string): string => { + /** + * Normalizes array indices in a path (e.g., "parent.0.child" -> "parent.*.child") + * so that we match the patterns stored in `argumentsList`. + */ + function normalizePathForList(path: string): string { // Replace numeric segments with `*` return path.replace(/\.\d+/g, '.*'); - }; + } - const handleInputChange = (path: string, value: any) => { - const normalizedPath = normalizePathForList(path); // Normalize the path to match `argumentsList` + function handleInputChange(path: string, value: DefaultValue | undefined): void { + const normalizedPath = normalizePathForList(path); const argumentConfig = argumentsList.find((arg) => arg.path === normalizedPath); if (!argumentConfig) { @@ -178,16 +171,17 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab return; } - // Handle `ID` types with `isList=true` - if (['String', 'DoubleFunction', 'ID', 'Duration'].includes(argumentConfig.type) && argumentConfig.isList) { + // Handle comma-separated input for string arrays + if ( + argumentConfig.type.subtype != null && + ['String', 'DoubleFunction', 'ID', 'Duration'].includes(argumentConfig.type.subtype) && + argumentConfig.isList + ) { if (typeof value === 'string') { // Convert comma-separated string into an array - const idsArray = value.split(',').map((id) => id.trim()); // Remove whitespace + const idsArray = value.split(',').map((id) => id.trim()); - // Update the `tripQueryVariables` with the array - let updatedTripQueryVariables = setNestedValue(tripQueryVariables, path, idsArray); - - // Clean up parent structure if necessary + let updatedTripQueryVariables = setNestedValue(tripQueryVariables, path, idsArray) as TripQueryVariables; updatedTripQueryVariables = cleanUpParentIfEmpty(updatedTripQueryVariables, path); setTripQueryVariables(updatedTripQueryVariables); return; @@ -195,38 +189,52 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab } // Default handling for other cases - let updatedTripQueryVariables = setNestedValue(tripQueryVariables, path, value); - + let updatedTripQueryVariables = setNestedValue(tripQueryVariables, path, value) as TripQueryVariables; updatedTripQueryVariables = cleanUpParentIfEmpty(updatedTripQueryVariables, path); setTripQueryVariables(updatedTripQueryVariables); - }; + } + + function isPlainObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); + } - const cleanUpParentIfEmpty = (variables: any, path: string): any => { + /** + * Recursively removes empty arrays/objects from `variables` based on a path. + * Returns the updated variables. + */ + function cleanUpParentIfEmpty(variables: TripQueryVariables, path: string): TripQueryVariables { // Handle the case where `path` is top-level (no dots) if (!path.includes('.')) { const topValue = getNestedValue(variables, path); // If it’s an empty array, remove it entirely from `variables` if (Array.isArray(topValue) && topValue.length === 0) { - const { [path]: _, ...rest } = variables; - return rest; + // Create a shallow copy as a flexible object: + const copy = { ...variables } as Record; + // Remove the property: + delete copy[path]; + // Cast back to TripQueryVariables (still type-safe as far as usage): + return copy as TripQueryVariables; } - // If it's an object and all keys are undefined/null or empty, remove it - if (topValue && typeof topValue === 'object') { + // If it's a plain object and all keys are undefined/null or empty, remove it + if (isPlainObject(topValue)) { const allKeysEmpty = Object.keys(topValue).every((key) => { - const childVal = topValue[key]; + const childVal = (topValue as Record)[key]; return childVal === undefined || childVal === null || (Array.isArray(childVal) && childVal.length === 0); }); + if (allKeysEmpty) { - const { [path]: _, ...rest } = variables; - return rest; + const copy = { ...variables } as Record; + delete copy[path]; + return copy as TripQueryVariables; } } return variables; // Otherwise leave it as is } + // For nested paths (e.g. "nested.key.inner"): const pathParts = path.split('.'); for (let i = pathParts.length - 1; i > 0; i--) { const parentPath = pathParts.slice(0, i).join('.'); @@ -240,9 +248,9 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab if (Array.isArray(parentValue)) { // If the parent array is now empty, remove it if (parentValue.length === 0) { - variables = setNestedValue(variables, parentPath, undefined); + variables = setNestedValue(variables, parentPath, undefined) as TripQueryVariables; } - } else if (typeof parentValue === 'object') { + } else if (isPlainObject(parentValue)) { // If all child values are null/undefined or empty, remove the parent const allKeysEmpty = Object.keys(parentValue).every((key) => { const childPath = `${parentPath}.${key}`; @@ -253,58 +261,47 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab }); if (allKeysEmpty) { - variables = setNestedValue(variables, parentPath, undefined); + variables = setNestedValue(variables, parentPath, undefined) as TripQueryVariables; } } } return variables; - }; + } - const toggleExpand = (path: string) => { - setExpandedArguments((prev) => { - const newState = { ...prev }; - newState[path] = !prev[path]; - return newState; - }); - }; + function toggleExpand(path: string): void { + setExpandedArguments((prev) => ({ + ...prev, + [path]: !prev[path], + })); + } const filteredArgumentsList = argumentsList .filter(({ path }) => formatArgumentName(path).toLowerCase().includes(searchText.toLowerCase())) .filter(({ path }) => !excludedArguments.has(path)); - const renderListOfInputObjects = (listPath: string, allArgs: ArgumentConfig[], level: number, tripArgs: any) => { - const arrayVal = getNestedValue(tripQueryVariables, listPath) || []; - - // Dynamically determine the button label based on the type name - const argumentsData = tripArgs as Record< - string, - { - type: { - type: string; - name?: string; - fields?: Record; - }; - defaultValue?: string; - isList?: boolean; - } - >; - - const parentArg = argumentsList.find((arg) => arg.path === listPath); - let typeName = 'Item'; // Default fallback - - if (parentArg?.type === 'Group' && argumentsData[listPath]?.type) { - const typeDetails = argumentsData[listPath].type; - if (typeDetails.type === 'InputObject' && typeDetails.name) { - typeName = typeDetails.name; // Use the type name directly - } - } + /** + * Renders multiple InputObjects within an array. Each item in the array + * is shown with an expand/collapse toggle and a remove button. + */ + function renderListOfInputObjects( + listPath: string, + allArgs: ProcessedArgument[], + level: number, + type: ResolvedType, + ): React.JSX.Element { + // We assume getNestedValue returns unknown; cast to an array if needed + const arrayVal = (getNestedValue(tripQueryVariables, listPath) ?? []) as unknown[]; + debugger; + // You can customize this if you have a better naming scheme + const typeName = type.name; return (
- {arrayVal.map((_item: unknown, index: number) => { + {arrayVal.map((_, index) => { const itemPath = `${listPath}.${index}`; + // Replace the `.*` placeholder with the actual index const itemNestedArgs = allArgs .filter((arg) => arg.path.startsWith(`${listPath}.*.`) && arg.path !== `${listPath}.*`) .map((arg) => ({ @@ -340,51 +337,33 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab
); - }; + } - const handleAddItem = (listPath: string) => { - const currentValue = getNestedValue(tripQueryVariables, listPath) || []; - // Insert an empty object or a default shape + function handleAddItem(listPath: string): void { + const currentValue = (getNestedValue(tripQueryVariables, listPath) ?? []) as unknown[]; const newValue = [...currentValue, {}]; - const updatedTripQueryVariables = setNestedValue(tripQueryVariables, listPath, newValue); + const updatedTripQueryVariables = setNestedValue(tripQueryVariables, listPath, newValue) as TripQueryVariables; setTripQueryVariables(updatedTripQueryVariables); - }; - - const handleRemoveItem = (listPath: string, index: number) => { - const currentValue = getNestedValue(tripQueryVariables, listPath) || []; - const newValue = currentValue.filter((_: any, i: number) => i !== index); - let updatedTripQueryVariables = setNestedValue(tripQueryVariables, listPath, newValue); + } + function handleRemoveItem(listPath: string, index: number): void { + const currentValue = (getNestedValue(tripQueryVariables, listPath) ?? []) as unknown[]; + const newValue = currentValue.filter((_, i) => i !== index); + let updatedTripQueryVariables = setNestedValue(tripQueryVariables, listPath, newValue) as TripQueryVariables; updatedTripQueryVariables = cleanUpParentIfEmpty(updatedTripQueryVariables, listPath); setTripQueryVariables(updatedTripQueryVariables); - }; + } - const handleRemoveArgument = (path: string) => { - let updatedTripQueryVariables = setNestedValue(tripQueryVariables, path, undefined); - // Then let your cleanup function remove it if it’s an empty object/array + function handleRemoveArgument(path: string): void { + let updatedTripQueryVariables = setNestedValue(tripQueryVariables, path, undefined) as TripQueryVariables; updatedTripQueryVariables = cleanUpParentIfEmpty(updatedTripQueryVariables, path); setTripQueryVariables(updatedTripQueryVariables); - }; - - const renderArgumentInputs = ( - args: { - path: string; - type: string; - defaultValue?: string; - enumValues?: string[]; - isComplex?: boolean; - isList?: boolean; - }[], - level: number, - allArgs: { - path: string; - type: string; - defaultValue?: string; - enumValues?: string[]; - isComplex?: boolean; - isList?: boolean; - }[], - ) => { + } + + /** + * Recursively renders inputs for an array of `ProcessedArgument`s. + */ + function renderArgumentInputs(args: ProcessedArgument[], level: number, allArgs: ProcessedArgument[]): JSX.Element[] { return args.map(({ path, type, defaultValue, enumValues, isComplex, isList }) => { const isExpanded = expandedArguments[path]; const currentDepth = path.split('.').length; @@ -392,8 +371,10 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab const argDepth = arg.path.split('.').length; return arg.path.startsWith(`${path}.`) && arg.path !== path && argDepth === currentDepth + 1; }); + const nestedLevel = level + 1; + // Various input renderings depending on subtype return (
{isComplex ? ( @@ -401,11 +382,9 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab toggleExpand(path)}> {isExpanded ? '▼ ' : '▶ '} {formatArgumentName(path)} - {isExpanded && isList ? ( -
{renderListOfInputObjects(path, allArgs, nestedLevel, tripArgs)}
+
{renderListOfInputObjects(path, allArgs, nestedLevel, type)}
) : isExpanded ? ( - /* original single-object rendering */ renderArgumentInputs(nestedArgs, nestedLevel, allArgs) ) : null}
@@ -414,11 +393,10 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab - {type === 'Boolean' && + {type.subtype === 'Boolean' && (() => { - const currentValue = getNestedValue(tripQueryVariables, path); + const currentValue = getNestedValue(tripQueryVariables, path) as boolean | undefined; const isInUse = currentValue !== undefined; - return ( = ({ tripQueryVariab onChange={(e) => handleInputChange(path, e.target.checked)} /> {isInUse && ( - handleRemoveArgument(path)} className={'remove-argument'}> + handleRemoveArgument(path)} className="remove-argument"> x )} @@ -436,66 +414,77 @@ const TripQueryArguments: React.FC = ({ tripQueryVariab ); })()} - {['String', 'DoubleFunction', 'ID', 'Duration'].includes(type) && isList && ( - { - const currentValue = getNestedValue(tripQueryVariables, path); - return Array.isArray(currentValue) ? currentValue.join(', ') : ''; // Join array into a comma-separated string - })()} - onChange={(e) => handleInputChange(path, e.target.value)} - placeholder="Comma-separated list" - /> - )} - {['String', 'DoubleFunction', 'ID', 'Duration'].includes(type) && !isList && ( - handleInputChange(path, e.target.value || undefined)} - /> - )} - {type === 'Int' && ( + {type.subtype != null && + ['String', 'DoubleFunction', 'ID', 'Duration'].includes(type.subtype) && + isList && ( + { + const currentValue = getNestedValue(tripQueryVariables, path); + return Array.isArray(currentValue) ? currentValue.join(', ') : ''; + })()} + onChange={(e) => handleInputChange(path, e.target.value)} + placeholder="Comma-separated list" + /> + )} + + {type.subtype != null && + ['String', 'DoubleFunction', 'ID', 'Duration'].includes(type.subtype) && + !isList && ( + handleInputChange(path, e.target.value || undefined)} + /> + )} + + {type.subtype === 'Int' && ( handleInputChange(path, parseInt(e.target.value, 10) || undefined)} + value={(getNestedValue(tripQueryVariables, path) as number) ?? ''} + onChange={(e) => { + const val = parseInt(e.target.value, 10); + handleInputChange(path, Number.isNaN(val) ? undefined : val); + }} /> )} - {type === 'Float' && ( + + {type.subtype === 'Float' && ( handleInputChange(path, parseFloat(e.target.value) || undefined)} + value={(getNestedValue(tripQueryVariables, path) as number) ?? ''} + onChange={(e) => { + const val = parseFloat(e.target.value); + handleInputChange(path, Number.isNaN(val) ? undefined : val); + }} /> )} - {type === 'DateTime' && ( + {type.subtype === 'DateTime' && ( { const newValue = e.target.value ? new Date(e.target.value).toISOString() : undefined; handleInputChange(path, newValue); }} /> )} - {type === 'Enum' && enumValues && isList && ( + + {type.type === 'Enum' && enumValues && isList && ( )} - {type === 'Enum' && enumValues && !isList && ( + {type.type === 'Enum' && enumValues && !isList && ( setBackgroundLayer(e.target.value)}> - {rasterLayers.map((layer) => ( - - ))} - - - {/* DEBUG (VECTOR) LAYERS */} -

Debug Layers

- {Object.entries(layerGroups).map(([groupName, layers]) => ( -
-
{groupName}
- {layers.map((layer) => { - // Grab the map property for whether it’s visible or not: - const isVisible = - mapRef?.getMap().getLayoutProperty(layer.id, 'visibility') !== 'none'; - - return ( - - ); - })} -
- ))} +const LayerControl: React.FC = ({ mapRef, setInteractiveLayerIds }) => { + const [rasterLayers, setRasterLayers] = useState([]); + const [layerGroups, setLayerGroups] = useState>({}); + + /** + * Load background + debug layers from the style once the map is ready. + */ + useEffect(() => { + if (!mapRef) return; + const mapInstance = mapRef.getMap(); + + const loadLayers = () => { + const style = mapInstance.getStyle(); + if (!style || !style.layers) return; + + // 1. Gather all raster layers (for the background selector). + const rasters = style.layers + .filter((layer) => layer.type === 'raster') + .map((layer) => { + // Try to pick up a pretty name from metadata if available. + let name = layer.id; + if ((layer as AnyLayer).metadata?.name) { + name = (layer as AnyLayer).metadata.name; + } + return { id: layer.id, name }; + }); + setRasterLayers(rasters); + + // 2. Gather all "debug" layers (i.e. not raster, not "jsx"). + // Group them by metadata.group (falling back to "Misc"). + const groups: Record = {}; + style.layers + .filter((layer) => layer.type !== 'raster' && !layer.id.startsWith('jsx')) + .reverse() // so that the topmost layers appear first + .forEach((layer) => { + const groupName = (layer as AnyLayer).metadata?.group || 'Misc'; + if (!groups[groupName]) { + groups[groupName] = []; + } + groups[groupName].push({ id: layer.id, name: layer.id }); + }); + + setLayerGroups(groups); + }; + + if (mapInstance.isStyleLoaded()) { + loadLayers(); + } else { + mapInstance.on('styledata', loadLayers); + } + + return () => { + mapInstance.off('styledata', loadLayers); + }; + }, [mapRef]); + + /** + * Toggle the visibility of an individual debug layer. + */ + const toggleLayerVisibility = useCallback( + (layerId: string, isVisible: boolean) => { + if (!mapRef) return; + const mapInstance = mapRef.getMap(); + mapInstance.setLayoutProperty(layerId, 'visibility', isVisible ? 'visible' : 'none'); + + // After toggling, recalculate which interactive layers are visible. + const style = mapInstance.getStyle(); + if (!style || !style.layers) return; + + const visibleInteractive = style.layers + .filter((l) => l.type !== 'raster' && !l.id.startsWith('jsx')) + .filter((l) => mapInstance.getLayoutProperty(l.id, 'visibility') !== 'none') + .map((l) => l.id); + + setInteractiveLayerIds(visibleInteractive); + }, + [mapRef, setInteractiveLayerIds], + ); + + /** + * Show exactly one background (raster) layer at a time. + */ + const setBackgroundLayer = useCallback( + (layerId: string) => { + if (!mapRef) return; + const mapInstance = mapRef.getMap(); + rasterLayers.forEach((r) => { + mapInstance.setLayoutProperty(r.id, 'visibility', r.id === layerId ? 'visible' : 'none'); + }); + }, + [mapRef, rasterLayers], + ); + + return ( +
+ {/* BACKGROUND (RASTER) LAYERS */} +

Background

+ + + {/* DEBUG (VECTOR) LAYERS */} +

Debug Layers

+ {Object.entries(layerGroups).map(([groupName, layers]) => ( +
+
{groupName}
+ {layers.map((layer) => { + // Grab the map property for whether it’s visible or not: + const isVisible = mapRef?.getMap().getLayoutProperty(layer.id, 'visibility') !== 'none'; + + return ( + + ); + })}
- ); + ))} +
+ ); }; export default LayerControl; diff --git a/client/src/components/MapView/MapView.tsx b/client/src/components/MapView/MapView.tsx index 910733cfb99..21fad3140a6 100644 --- a/client/src/components/MapView/MapView.tsx +++ b/client/src/components/MapView/MapView.tsx @@ -80,7 +80,7 @@ export function MapView({ // 2) Add the native MapLibre attribution control onLoad(e); } - + const mapRef = useRef(null); // Create a ref for MapRef return (
diff --git a/client/src/components/SearchBar/GraphiQLRouteButton.tsx b/client/src/components/SearchBar/GraphiQLRouteButton.tsx index 36f2a0c7121..c115dbc1a52 100644 --- a/client/src/components/SearchBar/GraphiQLRouteButton.tsx +++ b/client/src/components/SearchBar/GraphiQLRouteButton.tsx @@ -1,6 +1,6 @@ import { Button } from 'react-bootstrap'; import { TripQueryVariables } from '../../gql/graphql.ts'; -import { queryAsString } from '../../static/query/tripQuery.tsx'; +import { queryAsString } from '../../gql/tripQuery.tsx'; import graphqlIcon from '../../static/img/graphql-solid.svg'; const graphiQLUrl = import.meta.env.VITE_GRAPHIQL_URL; diff --git a/client/src/components/SearchBar/InputFieldsSection.tsx b/client/src/components/SearchBar/InputFieldsSection.tsx index e1819dcaae2..234626d0c76 100644 --- a/client/src/components/SearchBar/InputFieldsSection.tsx +++ b/client/src/components/SearchBar/InputFieldsSection.tsx @@ -29,17 +29,21 @@ export function InputFieldsSection({ return (
- + - +
diff --git a/client/src/components/SearchInput/ArgumentTooltip.tsx b/client/src/components/SearchInput/ArgumentTooltip.tsx index 916e5f11e7c..efb7a11dc19 100644 --- a/client/src/components/SearchInput/ArgumentTooltip.tsx +++ b/client/src/components/SearchInput/ArgumentTooltip.tsx @@ -2,10 +2,10 @@ import React from 'react'; import infoIcon from '../../static/img/help-info-solid.svg'; import inputIcon from '../../static/img/input.svg'; import durationIcon from '../../static/img/lap-timer.svg'; -import {ResolvedType} from "./useTripArgs.ts"; +import { ResolvedType } from './useTripArgs.ts'; interface ArgumentTooltipProps { - defaultValue?: string | number | boolean | object | null | undefined + defaultValue?: string | number | boolean | object | null | undefined; type?: ResolvedType; } diff --git a/client/src/components/SearchInput/Sidebar.tsx b/client/src/components/SearchInput/Sidebar.tsx index ce4badb6d50..b362ad4720c 100644 --- a/client/src/components/SearchInput/Sidebar.tsx +++ b/client/src/components/SearchInput/Sidebar.tsx @@ -12,13 +12,13 @@ const Sidebar: React.FC = ({ children }) => { // Function to return the appropriate image based on the index const getIconForIndex = (index: number) => { - switch (index) { + switch (index) { case 0: return Itineray list; case 1: return Filters; case 2: - return Filters; + return Filters; default: return null; } diff --git a/client/src/components/SearchInput/ViewArgumentsRaw.tsx b/client/src/components/SearchInput/ViewArgumentsRaw.tsx index 26a34899b4d..c08fd833a65 100644 --- a/client/src/components/SearchInput/ViewArgumentsRaw.tsx +++ b/client/src/components/SearchInput/ViewArgumentsRaw.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {TripQueryVariables} from "../../gql/graphql.ts"; -import ResetButton from "./ResetButton.tsx"; +import { TripQueryVariables } from '../../gql/graphql.ts'; +import ResetButton from './ResetButton.tsx'; interface ViewArgumentsRawProps { tripQueryVariables: TripQueryVariables; @@ -8,19 +8,15 @@ interface ViewArgumentsRawProps { } const ViewArgumentsRaw: React.FC = ({ tripQueryVariables, setTripQueryVariables }) => { - return ( -
-
- Arguments raw - -
- -
-            {JSON.stringify(tripQueryVariables, null, 2)}
-          
- +
+
+ Arguments raw +
+ +
{JSON.stringify(tripQueryVariables, null, 2)}
+
); }; diff --git a/client/src/components/SearchInput/excluded-arguments.ts b/client/src/components/SearchInput/excluded-arguments.ts index 7dc75bf0b08..bef4f1f6075 100644 --- a/client/src/components/SearchInput/excluded-arguments.ts +++ b/client/src/components/SearchInput/excluded-arguments.ts @@ -1,13 +1,12 @@ export const excludedArguments = new Set([ - - 'numTripPatterns', - 'arriveBy', - 'from', - 'to', - 'dateTime', - 'searchWindow', - 'modes.accessMode', - 'modes.directMode', - 'modes.egressMode', - // Add every full path you want to exclude - top level paths will remove all children! -]); \ No newline at end of file + 'numTripPatterns', + 'arriveBy', + 'from', + 'to', + 'dateTime', + 'searchWindow', + 'modes.accessMode', + 'modes.directMode', + 'modes.egressMode', + // Add every full path you want to exclude - top level paths will remove all children! +]); diff --git a/client/src/hooks/useTripQuery.ts b/client/src/hooks/useTripQuery.ts index f1f8c859bd0..9c043f2e312 100644 --- a/client/src/hooks/useTripQuery.ts +++ b/client/src/hooks/useTripQuery.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react'; import { request } from 'graphql-request'; import { Location, QueryType, TripQueryVariables } from '../gql/graphql.ts'; import { getApiUrl } from '../util/getApiUrl.ts'; -import { query } from '../static/query/tripQuery.tsx'; +import { query } from '../gql/tripQuery.tsx'; /** General purpose trip query document for debugging trip searches diff --git a/client/src/style.css b/client/src/style.css index 74da6d5262c..a6b287cddb3 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -114,7 +114,6 @@ min-width: 300px; } - .left-pane-container .time-zone-info { margin: 10px 20px; font-size: 12px; @@ -379,7 +378,7 @@ input[type='number'] { font-size: 13px; padding: 5px 10px; margin: 5px 0; - background-color: #007BFF; + background-color: #007bff; color: white; border: none; border-radius: 3px; @@ -390,10 +389,9 @@ input[type='number'] { background-color: #0056b3; /* Darker on hover */ } - -.argument-list input[type="text"], -.argument-list input[type="number"], -.argument-list input[type="datetime-local"], +.argument-list input[type='text'], +.argument-list input[type='number'], +.argument-list input[type='datetime-local'], select { font-size: 12px; padding: 0; @@ -404,18 +402,16 @@ select { box-sizing: border-box; } - - -.argument-list input[type="text"], -.argument-list input[type="number"] { +.argument-list input[type='text'], +.argument-list input[type='number'] { width: 50px; } -.argument-list input[type="datetime-local"] { +.argument-list input[type='datetime-local'] { width: 140px; } -input.comma-separated-input[type="text"], -input.comma-separated-input[type="number"] { +input.comma-separated-input[type='text'], +input.comma-separated-input[type='number'] { width: 140px; } @@ -438,7 +434,7 @@ input.comma-separated-input[type="number"] { font-size: 13px; padding: 5px 10px; margin: 5px 0; - background-color: #007BFF; + background-color: #007bff; color: white; border: none; border-radius: 3px; diff --git a/client/src/util/generate-queries.cjs b/client/src/util/generate-queries.cjs index ee666211106..8153b8a6f25 100644 --- a/client/src/util/generate-queries.cjs +++ b/client/src/util/generate-queries.cjs @@ -1,11 +1,10 @@ -const { getNullableType, isObjectType } = require('graphql'); const fs = require('fs'); const path = require('path'); /** * Plugin to generate GraphQL queries dynamically from schema */ -const generateQueriesPlugin = async (schema, documents, config) => { +const generateQueriesPlugin = async (schema) => { const queryType = schema.getQueryType(); if (!queryType) { return '// No Query type found in the schema'; @@ -26,11 +25,12 @@ const generateQueriesPlugin = async (schema, documents, config) => { const queries = []; Object.keys(queryFields).forEach((fieldName) => { - if (fieldName === 'trip') { // Only interested in the trip query + if (fieldName === 'trip') { + // Only interested in the trip query const field = queryFields[fieldName]; // Filter out deprecated arguments using deprecationReason - isDeprecated does not work - const validArgs = field.args.filter(arg => !arg.deprecationReason); + const validArgs = field.args.filter((arg) => !arg.deprecationReason); // Generate the arguments for the query with filtered arguments const args = validArgs.map((arg) => ` $${arg.name}: ${arg.type}`).join('\n'); @@ -38,7 +38,7 @@ const generateQueriesPlugin = async (schema, documents, config) => { // Generate the arguments for the query variables with filtered arguments const argsForQuery = validArgs.map((arg) => ` ${arg.name}: $${arg.name}`).join('\n'); - const query = `import { graphql } from '../../gql'; + const query = `import { graphql } from '../gql'; import { print } from 'graphql/index'; // Generated trip query based on schema.graphql diff --git a/client/src/util/getSchemaUrl.ts b/client/src/util/getSchemaUrl.ts deleted file mode 100644 index 2ef707f6856..00000000000 --- a/client/src/util/getSchemaUrl.ts +++ /dev/null @@ -1,5 +0,0 @@ -const endpoint = import.meta.env.VITE_SCHEMA_URL; - -export const getSchemaUrl = () => { - return endpoint; -}; From 1cf90246edafeb56f174e515d6e0cc570797d900 Mon Sep 17 00:00:00 2001 From: a-limyr Date: Wed, 22 Jan 2025 13:46:14 +0100 Subject: [PATCH 173/195] Updates to ignore prettier formatting of generated file. --- client/.prettierignore | 1 + client/codegen-preprocess.ts | 2 +- client/src/components/SearchBar/GraphiQLRouteButton.tsx | 2 +- client/src/hooks/useTripQuery.ts | 2 +- client/src/util/generate-queries.cjs | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/.prettierignore b/client/.prettierignore index a96d61e932a..ba27ff090d8 100644 --- a/client/.prettierignore +++ b/client/.prettierignore @@ -1,3 +1,4 @@ node_modules/ output/ src/gql/ +src/static/query/tripQuery.tsx diff --git a/client/codegen-preprocess.ts b/client/codegen-preprocess.ts index ee9acb8772f..c93fafab0b2 100644 --- a/client/codegen-preprocess.ts +++ b/client/codegen-preprocess.ts @@ -7,7 +7,7 @@ const config: CodegenConfig = { schema: 'https://otp2debug.dev.entur.org/otp/transmodel/v3/schema.graphql', documents: 'src/**/*.{ts,tsx}', generates: { - 'src/gql/tripQuery.tsx': { + 'src/static/query/tripQuery.tsx': { plugins: [path.resolve(__dirname, './src/util/generate-queries.cjs')], }, }, diff --git a/client/src/components/SearchBar/GraphiQLRouteButton.tsx b/client/src/components/SearchBar/GraphiQLRouteButton.tsx index c115dbc1a52..36f2a0c7121 100644 --- a/client/src/components/SearchBar/GraphiQLRouteButton.tsx +++ b/client/src/components/SearchBar/GraphiQLRouteButton.tsx @@ -1,6 +1,6 @@ import { Button } from 'react-bootstrap'; import { TripQueryVariables } from '../../gql/graphql.ts'; -import { queryAsString } from '../../gql/tripQuery.tsx'; +import { queryAsString } from '../../static/query/tripQuery.tsx'; import graphqlIcon from '../../static/img/graphql-solid.svg'; const graphiQLUrl = import.meta.env.VITE_GRAPHIQL_URL; diff --git a/client/src/hooks/useTripQuery.ts b/client/src/hooks/useTripQuery.ts index 9c043f2e312..f1f8c859bd0 100644 --- a/client/src/hooks/useTripQuery.ts +++ b/client/src/hooks/useTripQuery.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react'; import { request } from 'graphql-request'; import { Location, QueryType, TripQueryVariables } from '../gql/graphql.ts'; import { getApiUrl } from '../util/getApiUrl.ts'; -import { query } from '../gql/tripQuery.tsx'; +import { query } from '../static/query/tripQuery.tsx'; /** General purpose trip query document for debugging trip searches diff --git a/client/src/util/generate-queries.cjs b/client/src/util/generate-queries.cjs index 8153b8a6f25..00366bc6a11 100644 --- a/client/src/util/generate-queries.cjs +++ b/client/src/util/generate-queries.cjs @@ -38,7 +38,7 @@ const generateQueriesPlugin = async (schema) => { // Generate the arguments for the query variables with filtered arguments const argsForQuery = validArgs.map((arg) => ` ${arg.name}: $${arg.name}`).join('\n'); - const query = `import { graphql } from '../gql'; + const query = `import { graphql } from '../../gql'; import { print } from 'graphql/index'; // Generated trip query based on schema.graphql From c5a998dc637edff70c54cb380585a9b8bae5b7b8 Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 22 Jan 2025 15:21:08 +0100 Subject: [PATCH 174/195] factory method implementation for Distance --- .../fares/impl/GtfsFaresV2ServiceTest.java | 13 +++- .../gtfs/mapping/FareLegRuleMapper.java | 28 ++++++++- .../model/RentalVehicleFuel.java | 2 +- .../transit/model/basic/Distance.java | 60 ++++++++++++++++--- .../GbfsFreeVehicleStatusMapper.java | 32 +++++----- .../gtfs/mapping/FareLegRuleMapperTest.java | 5 +- .../routing/core/DistanceTest.java | 30 +++++++--- .../TestFreeFloatingRentalVehicleBuilder.java | 2 +- 8 files changed, 132 insertions(+), 40 deletions(-) diff --git a/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/GtfsFaresV2ServiceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/GtfsFaresV2ServiceTest.java index a7b2694e2e8..c22c8c97a70 100644 --- a/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/GtfsFaresV2ServiceTest.java +++ b/application/src/ext-test/java/org/opentripplanner/ext/fares/impl/GtfsFaresV2ServiceTest.java @@ -331,15 +331,22 @@ class DistanceFares { List distanceRules = List.of( FareLegRule .of(DISTANCE_ID, tenKmProduct) - .withFareDistance(new LinearDistance(Distance.ofKilometers(7), Distance.ofKilometers(10))) + .withFareDistance(new LinearDistance( + Distance.ofKilometersBoxed(7d, ignore -> {}).orElse(null), + Distance.ofKilometersBoxed(10d, ignore -> {}).orElse(null))) .build(), FareLegRule .of(DISTANCE_ID, threeKmProduct) - .withFareDistance(new LinearDistance(Distance.ofKilometers(3), Distance.ofKilometers(6))) + .withFareDistance(new LinearDistance( + Distance.ofKilometersBoxed(3d, ignore -> {}).orElse(null), + Distance.ofKilometersBoxed(6d, ignore -> {}).orElse(null))) .build(), FareLegRule .of(DISTANCE_ID, twoKmProduct) - .withFareDistance(new LinearDistance(Distance.ofMeters(0), Distance.ofMeters(2000))) + .withFareDistance(new LinearDistance( + Distance.ofMetersBoxed(0d, ignore -> {}).orElse(null), + Distance.ofMetersBoxed(2000d, ignore -> {}).orElse(null)) + ) .build() ); diff --git a/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java b/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java index e24aa300406..31fcf95d3cb 100644 --- a/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java +++ b/application/src/main/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapper.java @@ -9,9 +9,13 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.transit.model.basic.Distance; import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public final class FareLegRuleMapper { + private static final Logger LOG = LoggerFactory.getLogger(FareLegRuleMapper.class); + private final FareProductMapper fareProductMapper; private final DataImportIssueStore issueStore; @@ -75,8 +79,28 @@ private static FareDistance createFareDistance( fareLegRule.getMaxDistance().intValue() ); case 1 -> new FareDistance.LinearDistance( - Distance.ofMeters(fareLegRule.getMinDistance()), - Distance.ofMeters(fareLegRule.getMaxDistance()) + Distance + .ofMetersBoxed( + fareLegRule.getMinDistance(), + error -> + LOG.warn( + "Fare leg rule min distance not valid: {} - {}", + fareLegRule.getMinDistance(), + error + ) + ) + .orElse(null), + Distance + .ofMetersBoxed( + fareLegRule.getMaxDistance(), + error -> + LOG.warn( + "Fare leg rule max distance not valid: {} - {}", + fareLegRule.getMaxDistance(), + error + ) + ) + .orElse(null) ); default -> null; }; diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java index 3ddcc40ae99..3c4ae577b00 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java @@ -18,7 +18,7 @@ public class RentalVehicleFuel { public final Ratio percent; /** - * Distance that the vehicle can travel with the vehicle's current fuel. + * Distance that the vehicle can travel with the current fuel. */ @Nullable public final Distance range; diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java index 5a77eeacf34..773a21a8178 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java @@ -1,5 +1,8 @@ package org.opentripplanner.transit.model.basic; +import java.util.Optional; +import java.util.function.Consumer; +import javax.annotation.Nullable; import org.opentripplanner.utils.tostring.ValueObjectToStringBuilder; public class Distance { @@ -10,21 +13,62 @@ public class Distance { /** Returns a Distance object representing the given number of meters */ private Distance(int distanceInMillimeters) { - if (distanceInMillimeters < 0) { - throw new IllegalArgumentException("Distance cannot be negative"); + this.millimeters = distanceInMillimeters; + } + + /** + * This method is similar to {@link #of(double, Consumer)}, but throws an + * {@link IllegalArgumentException} if the distance is negative. + */ + private static Distance of(int distanceInMillimeters) { + return of( + distanceInMillimeters, + errMsg -> { + throw new IllegalArgumentException(errMsg); + } + ) + .orElseThrow(); + } + + private static Optional of( + int distanceInMillimeters, + Consumer validationErrorHandler + ) { + if (distanceInMillimeters >= 0) { + return Optional.of(new Distance(distanceInMillimeters)); + } else { + validationErrorHandler.accept( + "Distance must be greater or equal than 0, but was: " + distanceInMillimeters + ); + return Optional.empty(); } + } - this.millimeters = distanceInMillimeters; + private static Optional ofBoxed( + @Nullable Double value, + Consumer validationErrorHandler, + int multiplier + ) { + if (value == null) { + return Optional.empty(); + } + return of((int) (value * multiplier), validationErrorHandler); } /** Returns a Distance object representing the given number of meters */ - public static Distance ofMeters(double value) throws IllegalArgumentException { - return new Distance((int) (value * MILLIMETERS_PER_M)); + public static Optional ofMetersBoxed( + @Nullable Double value, + Consumer validationErrorHandler + ) { + return ofBoxed(value, validationErrorHandler, MILLIMETERS_PER_M); } /** Returns a Distance object representing the given number of kilometers */ - public static Distance ofKilometers(double value) { - return new Distance((int) (value * MILLIMETERS_PER_KM)); + public static Optional ofKilometersBoxed( + @Nullable Double value, + Consumer validationErrorHandler + ) { + return ofBoxed(value, validationErrorHandler, MILLIMETERS_PER_KM); } /** Returns the distance in meters */ @@ -49,7 +93,7 @@ public int hashCode() { @Override public String toString() { - if (millimeters > MILLIMETERS_PER_KM) { + if (millimeters > MILLIMETERS_PER_KM) { return ValueObjectToStringBuilder .of() .addNum((double) this.millimeters / (double) MILLIMETERS_PER_KM, "km") diff --git a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java index 3390b50e17b..7c242e85bbe 100644 --- a/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java +++ b/application/src/main/java/org/opentripplanner/updater/vehicle_rental/datasources/GbfsFreeVehicleStatusMapper.java @@ -24,7 +24,7 @@ public class GbfsFreeVehicleStatusMapper { private static final Logger LOG = LoggerFactory.getLogger(GbfsFreeVehicleStatusMapper.class); - private static final Throttle FUEL_PERCENT_LOG_THROTTLE = Throttle.ofOneMinute(); + private static final Throttle LOG_THROTTLE = Throttle.ofOneMinute(); private final VehicleRentalSystem system; @@ -66,25 +66,25 @@ public VehicleRentalVehicle mapFreeVehicleStatus(GBFSBike vehicle) { .ofBoxed( vehicle.getCurrentFuelPercent(), validationErrorMessage -> - FUEL_PERCENT_LOG_THROTTLE.throttle(() -> - LOG.warn("'currentFuelPercent' is not valid. Details: " + validationErrorMessage) + LOG_THROTTLE.throttle(() -> + LOG.warn("'currentFuelPercent' is not valid. Details: {}", validationErrorMessage) ) ) .orElse(null); - - Distance rangeMeters = null; - try { - rangeMeters = - vehicle.getCurrentRangeMeters() != null - ? Distance.ofMeters(vehicle.getCurrentRangeMeters()) - : null; - } catch (IllegalArgumentException e) { - LOG.warn( - "Current range meter value not valid: {} - {}", + var rangeMeters = Distance + .ofMetersBoxed( vehicle.getCurrentRangeMeters(), - e.getMessage() - ); - } + error -> { + LOG_THROTTLE.throttle(() -> + LOG.warn( + "Current range meter value not valid: {} - {}", + vehicle.getCurrentRangeMeters(), + error + ) + ); + } + ) + .orElse(null); // if the propulsion type has an engine current_range_meters is required if ( vehicle.getVehicleTypeId() != null && diff --git a/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java b/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java index 890da8264f4..334d30fa94f 100644 --- a/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java +++ b/application/src/test/java/org/opentripplanner/gtfs/mapping/FareLegRuleMapperTest.java @@ -33,7 +33,10 @@ private record TestCase( 1, 5000d, 10000d, - new LinearDistance(Distance.ofKilometers(5), Distance.ofKilometers(10)) + new LinearDistance( + Distance.ofKilometersBoxed(5d, ignore -> {}).orElse(null), + Distance.ofKilometersBoxed(10d, ignore -> {}).orElse(null) + ) ), new TestCase(null, null, null, null) ); diff --git a/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java b/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java index 2543eae70cd..5eb1ce8bc6a 100644 --- a/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java +++ b/application/src/test/java/org/opentripplanner/routing/core/DistanceTest.java @@ -8,12 +8,24 @@ public class DistanceTest { - private static final Distance ONE_THOUSAND_FIVE_HUNDRED_METERS = Distance.ofMeters(1500d); - private static final Distance ONE_POINT_FIVE_KILOMETERS = Distance.ofKilometers(1.5d); - private static final Distance TWO_KILOMETERS = Distance.ofKilometers(2d); - private static final Distance ONE_HUNDRED_METERS = Distance.ofMeters(100d); - private static final Distance POINT_ONE_KILOMETER = Distance.ofKilometers(0.1d); - private static final Distance ONE_HUNDRED_POINT_FIVE_METERS = Distance.ofMeters(100.5d); + private static final Distance ONE_THOUSAND_FIVE_HUNDRED_METERS = Distance + .ofMetersBoxed(1500d, ignore -> {}) + .orElse(null); + private static final Distance ONE_POINT_FIVE_KILOMETERS = Distance + .ofKilometersBoxed(1.5d, ignore -> {}) + .orElse(null); + private static final Distance TWO_KILOMETERS = Distance + .ofKilometersBoxed(2d, ignore -> {}) + .orElse(null); + private static final Distance ONE_HUNDRED_METERS = Distance + .ofMetersBoxed(100d, ignore -> {}) + .orElse(null); + private static final Distance POINT_ONE_KILOMETER = Distance + .ofKilometersBoxed(0.1d, ignore -> {}) + .orElse(null); + private static final Distance ONE_HUNDRED_POINT_FIVE_METERS = Distance + .ofMetersBoxed(100.5d, ignore -> {}) + .orElse(null); @Test void equals() { @@ -25,7 +37,9 @@ void equals() { @Test void testHashCode() { - assertEquals(Distance.ofMeters(5d).hashCode(), Distance.ofMeters(5d).hashCode()); - assertNotEquals(Distance.ofMeters(5d).hashCode(), Double.valueOf(5d).hashCode()); + assertEquals( + Distance.ofMetersBoxed(5d, ignore -> {}).hashCode(), + Distance.ofMetersBoxed(5d, ignore -> {}).hashCode() + ); } } diff --git a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java index b99745cbd97..2b8881715a9 100644 --- a/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java +++ b/application/src/test/java/org/opentripplanner/service/vehiclerental/model/TestFreeFloatingRentalVehicleBuilder.java @@ -105,7 +105,7 @@ public VehicleRentalVehicle build() { vehicle.fuel = new RentalVehicleFuel( currentFuelPercent, - currentRangeMeters != null ? Distance.ofMeters(currentRangeMeters) : null + Distance.ofMetersBoxed(currentRangeMeters, ignore -> {}).orElse(null) ); return vehicle; } From 9e30029ff301a6d3be9ddf10ade7c68427643a90 Mon Sep 17 00:00:00 2001 From: JustCris Date: Wed, 22 Jan 2025 16:55:18 +0100 Subject: [PATCH 175/195] expose Ratio and Distance from RentalVehicleFuel class --- .../apis/gtfs/GtfsGraphQLIndex.java | 2 ++ .../datafetchers/RentalVehicleFuelImpl.java | 29 +++++++++++++++++++ .../model/RentalVehicleFuel.java | 8 ++--- 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleFuelImpl.java diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index d3f64288417..721ad3e3ee7 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -59,6 +59,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.QueryTypeImpl; import org.opentripplanner.apis.gtfs.datafetchers.RealTimeEstimateImpl; import org.opentripplanner.apis.gtfs.datafetchers.RentalPlaceTypeResolver; +import org.opentripplanner.apis.gtfs.datafetchers.RentalVehicleFuelImpl; import org.opentripplanner.apis.gtfs.datafetchers.RentalVehicleImpl; import org.opentripplanner.apis.gtfs.datafetchers.RentalVehicleTypeImpl; import org.opentripplanner.apis.gtfs.datafetchers.RideHailingEstimateImpl; @@ -199,6 +200,7 @@ protected static GraphQLSchema buildSchema() { .type(typeWiring.build(RealTimeEstimateImpl.class)) .type(typeWiring.build(EstimatedTimeImpl.class)) .type(typeWiring.build(EntranceImpl.class)) + .type(typeWiring.build(RentalVehicleFuelImpl.class)) .build(); SchemaGenerator schemaGenerator = new SchemaGenerator(); return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleFuelImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleFuelImpl.java new file mode 100644 index 00000000000..0954267bf00 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleFuelImpl.java @@ -0,0 +1,29 @@ +package org.opentripplanner.apis.gtfs.datafetchers; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; +import org.opentripplanner.service.vehiclerental.model.RentalVehicleFuel; + +public class RentalVehicleFuelImpl implements GraphQLDataFetchers.GraphQLRentalVehicleFuel { + + @Override + public DataFetcher percent() { + return environment -> + getSource(environment).getPercent() != null + ? getSource(environment).getPercent().asDouble() + : null; + } + + @Override + public DataFetcher range() { + return environment -> + getSource(environment).getRange() != null + ? getSource(environment).getRange().toMeters() + : null; + } + + private RentalVehicleFuel getSource(DataFetchingEnvironment environment) { + return environment.getSource(); + } +} diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java index 3c4ae577b00..ac18ecd51cf 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java @@ -29,12 +29,12 @@ public RentalVehicleFuel(@Nullable Ratio fuelPercent, @Nullable Distance range) } @Nullable - public Double getPercent() { - return this.percent != null ? this.percent.asDouble() : null; + public Ratio getPercent() { + return this.percent; } @Nullable - public Integer getRange() { - return this.range != null ? this.range.toMeters() : null; + public Distance getRange() { + return range; } } From 8e6d6f1d14922ff7688a06490ce50cb4460b2139 Mon Sep 17 00:00:00 2001 From: a-limyr Date: Thu, 23 Jan 2025 12:51:57 +0100 Subject: [PATCH 176/195] Fixed link from external to internal schema. --- client/codegen-preprocess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/codegen-preprocess.ts b/client/codegen-preprocess.ts index c93fafab0b2..ec1b1dfce0d 100644 --- a/client/codegen-preprocess.ts +++ b/client/codegen-preprocess.ts @@ -4,7 +4,7 @@ import * as path from 'node:path'; const config: CodegenConfig = { overwrite: true, - schema: 'https://otp2debug.dev.entur.org/otp/transmodel/v3/schema.graphql', + schema: '../application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql', documents: 'src/**/*.{ts,tsx}', generates: { 'src/static/query/tripQuery.tsx': { From b74638fa6f488e0d06057a8673f085f4cad0399c Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 23 Jan 2025 13:00:13 +0000 Subject: [PATCH 177/195] Add changelog entry for #6215 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 9a5f2731733..f6ce040126f 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -78,6 +78,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - 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) +- Add 'transferParametersForMode' build config field [#6215](https://github.com/opentripplanner/OpenTripPlanner/pull/6215) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From e9a8742f928b2a9d604c6c3d5c6d8040c6ac366b Mon Sep 17 00:00:00 2001 From: OTP Serialization Version Bot Date: Thu, 23 Jan 2025 13:01:20 +0000 Subject: [PATCH 178/195] Bump serialization version id for #6215 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f1d1729b936..3c4eaf5a3b9 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ - 177 + 178 32.1 From 28bdf4196d8328ab230b0f77416dfede3596e5fb Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 23 Jan 2025 13:06:24 +0000 Subject: [PATCH 179/195] Add changelog entry for #6383 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index f6ce040126f..1795eb5f2b0 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -79,6 +79,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - 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) - Add 'transferParametersForMode' build config field [#6215](https://github.com/opentripplanner/OpenTripPlanner/pull/6215) +- Add 'maxStopCountForMode' to the router config [#6383](https://github.com/opentripplanner/OpenTripPlanner/pull/6383) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From 131f3016cff2605b1d58c8f0223e44f8258eee6f Mon Sep 17 00:00:00 2001 From: a-limyr Date: Thu, 23 Jan 2025 14:55:48 +0100 Subject: [PATCH 180/195] Added debug layer group toggle, minor style change and removed unused file. --- .../components/MapView/DebugLayerButton.tsx | 38 ----------- .../src/components/MapView/LayerControl.tsx | 67 ++++++++++++++----- client/src/style.css | 2 +- 3 files changed, 50 insertions(+), 57 deletions(-) delete mode 100644 client/src/components/MapView/DebugLayerButton.tsx diff --git a/client/src/components/MapView/DebugLayerButton.tsx b/client/src/components/MapView/DebugLayerButton.tsx deleted file mode 100644 index cd2aa862a00..00000000000 --- a/client/src/components/MapView/DebugLayerButton.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import debugLayerIcon from '../../static/img/debug-layer.svg'; - -import { useState } from 'react'; - -type DebugLayerButtonProps = { - onToggle: (isExpanded: boolean) => void; -}; - -export default function DebugLayerButton({ onToggle }: DebugLayerButtonProps) { - const [isExpanded, setIsExpanded] = useState(false); - - const handleClick = () => { - const nextState = !isExpanded; - setIsExpanded(nextState); - onToggle(nextState); // Notify parent of the state change - }; - - return ( - - ); -} diff --git a/client/src/components/MapView/LayerControl.tsx b/client/src/components/MapView/LayerControl.tsx index 337f9b55753..8c20cc74b12 100644 --- a/client/src/components/MapView/LayerControl.tsx +++ b/client/src/components/MapView/LayerControl.tsx @@ -134,34 +134,65 @@ const LayerControl: React.FC = ({ mapRef, setInteractiveLayer {/* DEBUG (VECTOR) LAYERS */}

Debug Layers

- {Object.entries(layerGroups).map(([groupName, layers]) => ( -
-
{groupName}
- {layers.map((layer) => { - // Grab the map property for whether it’s visible or not: - const isVisible = mapRef?.getMap().getLayoutProperty(layer.id, 'visibility') !== 'none'; - - return ( + {Object.entries(layerGroups).map(([groupName, layers]) => { + // Determine if *all* layers in this group are currently visible. + const allVisible = layers.every( + (layer) => mapRef?.getMap().getLayoutProperty(layer.id, 'visibility') !== 'none', + ); + + // Define a helper to toggle all layers in the group at once. + const toggleGroupVisibility = (checked: boolean) => { + layers.forEach((layer) => { + toggleLayerVisibility(layer.id, checked); + }); + }; + + return ( +
+
- ); - })} -
- ))} + + + {layers.map((layer) => { + // Figure out if the layer is visible or not: + const isVisible = mapRef?.getMap().getLayoutProperty(layer.id, 'visibility') !== 'none'; + + return ( + + ); + })} +
+ ); + })}
); }; diff --git a/client/src/style.css b/client/src/style.css index a6b287cddb3..a3f8946b3ec 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -261,7 +261,7 @@ .sidebar-button.right { position: absolute; right: 0; /* Default position when sidebar is closed */ - background: #ccc; + background: #fff; color: white; border: none; border-radius: 4px; From a7758b9703b6456dca78807fc0b8e249bf00269b Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 23 Jan 2025 16:53:08 +0000 Subject: [PATCH 181/195] Add changelog entry for #6370 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 1795eb5f2b0..352fc295f99 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -80,6 +80,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Generate Raptor transfer cache in parallel [#6326](https://github.com/opentripplanner/OpenTripPlanner/pull/6326) - Add 'transferParametersForMode' build config field [#6215](https://github.com/opentripplanner/OpenTripPlanner/pull/6215) - Add 'maxStopCountForMode' to the router config [#6383](https://github.com/opentripplanner/OpenTripPlanner/pull/6383) +- Add all routing parameters to debug UI [#6370](https://github.com/opentripplanner/OpenTripPlanner/pull/6370) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From 12b5cb647e501a6c3a9f5a9041367eedab890d25 Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Thu, 23 Jan 2025 16:54:19 +0000 Subject: [PATCH 182/195] Upgrade debug client to version 2025/01/2025-01-23T16:53 --- application/src/client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/client/index.html b/application/src/client/index.html index 2f64744e5e5..c399d429d0e 100644 --- a/application/src/client/index.html +++ b/application/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug - - + +
From 3b01c8cc18c4abcf3d502f702dc2d6fbf80fc0df Mon Sep 17 00:00:00 2001 From: Cristian Scapin <59602675+JustCris654@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:09:04 +0100 Subject: [PATCH 183/195] RentalVehicleFuel make fields private Co-authored-by: Leonard Ehrenfried --- .../service/vehiclerental/model/RentalVehicleFuel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java index ac18ecd51cf..ddb8caecaa3 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java @@ -15,13 +15,13 @@ public class RentalVehicleFuel { * Current fuel percentage, expressed from 0 to 1. */ @Nullable - public final Ratio percent; + private final Ratio percent; /** * Distance that the vehicle can travel with the current fuel. */ @Nullable - public final Distance range; + private final Distance range; public RentalVehicleFuel(@Nullable Ratio fuelPercent, @Nullable Distance range) { this.percent = fuelPercent; From c47569f519fbf9f93cbc5bf215efbce10c8fde6e Mon Sep 17 00:00:00 2001 From: JustCris Date: Fri, 24 Jan 2025 14:11:26 +0100 Subject: [PATCH 184/195] RentalVehicleFuel fix getters naming --- .../apis/gtfs/datafetchers/RentalVehicleFuelImpl.java | 8 ++------ .../apis/transmodel/model/stop/RentalVehicleType.java | 2 +- .../service/vehiclerental/model/RentalVehicleFuel.java | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleFuelImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleFuelImpl.java index 0954267bf00..aca43154e02 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleFuelImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalVehicleFuelImpl.java @@ -10,17 +10,13 @@ public class RentalVehicleFuelImpl implements GraphQLDataFetchers.GraphQLRentalV @Override public DataFetcher percent() { return environment -> - getSource(environment).getPercent() != null - ? getSource(environment).getPercent().asDouble() - : null; + getSource(environment).percent() != null ? getSource(environment).percent().asDouble() : null; } @Override public DataFetcher range() { return environment -> - getSource(environment).getRange() != null - ? getSource(environment).getRange().toMeters() - : null; + getSource(environment).range() != null ? getSource(environment).range().toMeters() : null; } private RentalVehicleFuel getSource(DataFetchingEnvironment environment) { diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java index 8694fe0d95d..a142873cd97 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/RentalVehicleType.java @@ -71,7 +71,7 @@ public static GraphQLObjectType create( .name("currentRangeMeters") .type(Scalars.GraphQLFloat) .dataFetcher(environment -> - ((VehicleRentalVehicle) environment.getSource()).getFuel().getRange() + ((VehicleRentalVehicle) environment.getSource()).getFuel().range() ) .build() ) diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java index ddb8caecaa3..0cc8e489b49 100644 --- a/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java +++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/model/RentalVehicleFuel.java @@ -29,12 +29,12 @@ public RentalVehicleFuel(@Nullable Ratio fuelPercent, @Nullable Distance range) } @Nullable - public Ratio getPercent() { + public Ratio percent() { return this.percent; } @Nullable - public Distance getRange() { + public Distance range() { return range; } } From 01850228429669c051d9ddec3918315f0bf23583 Mon Sep 17 00:00:00 2001 From: JustCris Date: Fri, 24 Jan 2025 14:51:14 +0100 Subject: [PATCH 185/195] fix Distance javadoc and equals method --- .../transit/model/basic/Distance.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java index 773a21a8178..04c6c1bbf6f 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java +++ b/application/src/main/java/org/opentripplanner/transit/model/basic/Distance.java @@ -11,7 +11,11 @@ public class Distance { private static final int MILLIMETERS_PER_KM = 1000 * MILLIMETERS_PER_M; private final int millimeters; - /** Returns a Distance object representing the given number of meters */ + /** + * Represents a distance. + * The class ensures that the distance, saved as an integer + * representing the millimeters, is not negative. + */ private Distance(int distanceInMillimeters) { this.millimeters = distanceInMillimeters; } @@ -78,12 +82,12 @@ public int toMeters() { } @Override - public boolean equals(Object other) { - if (other instanceof Distance distance) { - return distance.millimeters == this.millimeters; - } else { + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { return false; } + var other = (Distance) o; + return this.millimeters == other.millimeters; } @Override From 6285003fb38c91909b660f96e6c58fffdfad45ec Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 01:16:46 +0000 Subject: [PATCH 186/195] fix(deps): update highly trusted dependencies (patch) --- application/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 1efe110a583..69dfc596033 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -296,7 +296,7 @@ org.onebusaway onebusaway-gtfs - 5.0.0 + 5.0.2 diff --git a/pom.xml b/pom.xml index 3c4eaf5a3b9..611e60d4c8c 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ 5.6.0 1.5.12 10.1.0 - 1.14.1 + 1.14.3 2.0.15 5.6.0 4.28.3 From a8ad459f9e5b864af282e223d9bf6146fcdb24fa Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 27 Jan 2025 08:17:13 +0100 Subject: [PATCH 187/195] Automerge trusted dependencies [ci skip] --- renovate.json5 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/renovate.json5 b/renovate.json5 index 30a5c6a99c1..dbadeb8ac9c 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -158,7 +158,8 @@ "io.micrometer:micrometer-registry-influx", "com.fasterxml.jackson:{/,}**", "com.fasterxml.jackson.datatype::{/,}**" - ] + ], + "automerge": true }, { "description": "give some projects time to publish a changelog before opening the PR", From f8438b23448e876107ed42e604fa7d5e19666b53 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 28 Jan 2025 09:52:19 +0000 Subject: [PATCH 188/195] Add changelog entry for #6272 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 352fc295f99..88b6fd6847c 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -81,6 +81,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add 'transferParametersForMode' build config field [#6215](https://github.com/opentripplanner/OpenTripPlanner/pull/6215) - Add 'maxStopCountForMode' to the router config [#6383](https://github.com/opentripplanner/OpenTripPlanner/pull/6383) - Add all routing parameters to debug UI [#6370](https://github.com/opentripplanner/OpenTripPlanner/pull/6370) +- Add currentFuelPercent and currentRangeMeters to RentalVehichle in the GTFS GraphQL API [#6272](https://github.com/opentripplanner/OpenTripPlanner/pull/6272) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From 9894c2a8c528fc45d35c26fc81eb3ebd1ced3b78 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 28 Jan 2025 11:39:43 +0100 Subject: [PATCH 189/195] Switch debug client CDN to Github Pages --- .github/workflows/debug-client.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/debug-client.yml b/.github/workflows/debug-client.yml index 3c87e1c1b5c..82777ea22c6 100644 --- a/.github/workflows/debug-client.yml +++ b/.github/workflows/debug-client.yml @@ -41,7 +41,7 @@ jobs: working-directory: client run: | npm install - npm run build -- --base https://cdn.jsdelivr.net/gh/opentripplanner/debug-client-assets@main/${VERSION}/ + npm run build -- --base https://www.opentripplanner.org/debug-client-assets/${VERSION}/ npm run coverage - name: Deploy compiled assets to repo From 519cc45cb57027b0ecd7d29682b1425bd67f50f7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 28 Jan 2025 11:46:13 +0100 Subject: [PATCH 190/195] Add whitespace so that a new client version is built --- client/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/index.html b/client/index.html index f09832636f0..d73361cce9f 100644 --- a/client/index.html +++ b/client/index.html @@ -10,4 +10,4 @@
- + \ No newline at end of file From 0c8c3b26dcd5c18803703734ffcab294224ea98e Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Tue, 28 Jan 2025 11:23:21 +0000 Subject: [PATCH 191/195] Upgrade debug client to version 2025/01/2025-01-28T11:22 --- application/src/client/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/client/index.html b/application/src/client/index.html index c399d429d0e..0e8f974de9e 100644 --- a/application/src/client/index.html +++ b/application/src/client/index.html @@ -5,10 +5,10 @@ OTP Debug - - + +
- + \ No newline at end of file From 8f2a2de4b69744a004ef849a8da16368201e5ccf Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 29 Jan 2025 07:12:54 +0100 Subject: [PATCH 192/195] Update path of package.json [ci skip] --- renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json5 b/renovate.json5 index dbadeb8ac9c..14d025745bb 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -109,7 +109,7 @@ }, { "groupName": "Update GTFS API code generation in a single PR", - "matchFiles": ["src/main/java/org/opentripplanner/apis/gtfs/generated/package.json"], + "matchFiles": ["application/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json"], "reviewers": ["optionsome", "leonardehrenfried"], "schedule": "on the 11th through 12th day of the month" }, From e649a6c8ac91d5a9cd1ff25091f2f6d8af7ee0f0 Mon Sep 17 00:00:00 2001 From: Eivind Morris Bakke Date: Wed, 29 Jan 2025 14:59:23 +0100 Subject: [PATCH 193/195] Add a matcher API for filters in the transit service used for route lookup (#6378) * Creates a filter for the lines GraphQL method in Transmodel. * Improves documentation of CaseInsensitiveStringPrefixMatcher. * Addresses comments in PR review. * Addresses comments in PR review. --- .../opentripplanner/ext/flex/FlexIndex.java | 4 + .../transmodel/TransmodelGraphQLSchema.java | 137 ++++++++-------- .../api/request/FindRoutesRequest.java | 65 ++++++++ .../api/request/FindRoutesRequestBuilder.java | 65 ++++++++ .../CaseInsensitiveStringPrefixMatcher.java | 41 +++++ .../filter/expr/NullSafeWrapperMatcher.java | 42 +++++ .../filter/transit/RouteMatcherFactory.java | 81 ++++++++++ .../service/DefaultTransitService.java | 14 ++ .../transit/service/TransitService.java | 11 ++ .../apis/transmodel/schema.graphql | 11 +- ...aseInsensitiveStringPrefixMatcherTest.java | 16 ++ .../expr/NullSafeWrapperMatcherTest.java | 30 ++++ .../transit/RouteMatcherFactoryTest.java | 146 ++++++++++++++++++ 13 files changed, 588 insertions(+), 75 deletions(-) create mode 100644 application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java create mode 100644 application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequestBuilder.java create mode 100644 application/src/main/java/org/opentripplanner/transit/model/filter/expr/CaseInsensitiveStringPrefixMatcher.java create mode 100644 application/src/main/java/org/opentripplanner/transit/model/filter/expr/NullSafeWrapperMatcher.java create mode 100644 application/src/main/java/org/opentripplanner/transit/model/filter/transit/RouteMatcherFactory.java create mode 100644 application/src/test/java/org/opentripplanner/transit/model/filter/expr/CaseInsensitiveStringPrefixMatcherTest.java create mode 100644 application/src/test/java/org/opentripplanner/transit/model/filter/expr/NullSafeWrapperMatcherTest.java create mode 100644 application/src/test/java/org/opentripplanner/transit/model/filter/transit/RouteMatcherFactoryTest.java diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java index 8097bd05c6e..b0e851fa1d3 100644 --- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java +++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java @@ -64,6 +64,10 @@ public Route getRouteById(FeedScopedId id) { return routeById.get(id); } + public boolean contains(Route route) { + return routeById.containsKey(route.getId()); + } + public Collection getAllFlexRoutes() { return routeById.values(); } diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 0eed3d3fb84..e9056081b12 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -123,10 +123,10 @@ import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; import org.opentripplanner.transit.api.model.FilterValues; import org.opentripplanner.transit.api.request.FindRegularStopsByBoundingBoxRequest; +import org.opentripplanner.transit.api.request.FindRoutesRequest; import org.opentripplanner.transit.api.request.TripRequest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.service.TransitService; import org.slf4j.Logger; @@ -1098,24 +1098,42 @@ private GraphQLSchema create() { GraphQLFieldDefinition .newFieldDefinition() .name("lines") - .description("Get all lines") + .description("Get all _lines_") .withDirective(TransmodelDirectives.TIMING_DATA) .type(new GraphQLNonNull(new GraphQLList(lineType))) .argument( GraphQLArgument .newArgument() .name("ids") + .description( + "Set of ids of _lines_ to fetch. If this is set, no other filters can be set." + ) .type(new GraphQLList(Scalars.GraphQLID)) .build() ) - .argument(GraphQLArgument.newArgument().name("name").type(Scalars.GraphQLString).build()) .argument( - GraphQLArgument.newArgument().name("publicCode").type(Scalars.GraphQLString).build() + GraphQLArgument + .newArgument() + .name("name") + .description( + "Prefix of the _name_ of the _line_ to fetch. This filter is case insensitive." + ) + .type(Scalars.GraphQLString) + .build() + ) + .argument( + GraphQLArgument + .newArgument() + .name("publicCode") + .description("_Public code_ of the _line_ to fetch.") + .type(Scalars.GraphQLString) + .build() ) .argument( GraphQLArgument .newArgument() .name("publicCodes") + .description("Set of _public codes_ to fetch _lines_ for.") .type(new GraphQLList(Scalars.GraphQLString)) .build() ) @@ -1123,6 +1141,7 @@ private GraphQLSchema create() { GraphQLArgument .newArgument() .name("transportModes") + .description("Set of _transport modes_ to fetch _lines_ for.") .type(new GraphQLList(TRANSPORT_MODE)) .build() ) @@ -1130,7 +1149,7 @@ private GraphQLSchema create() { GraphQLArgument .newArgument() .name("authorities") - .description("Set of ids of authorities to fetch lines for.") + .description("Set of ids of _authorities_ to fetch _lines_ for.") .type(new GraphQLList(Scalars.GraphQLString)) .build() ) @@ -1138,83 +1157,57 @@ private GraphQLSchema create() { GraphQLArgument .newArgument() .name("flexibleOnly") - .description("Filter by lines containing flexible / on demand serviceJourneys only.") + .description( + "Filter by _lines_ containing flexible / on demand _service journey_ only." + ) .type(Scalars.GraphQLBoolean) - .defaultValue(false) + .defaultValueProgrammatic(false) .build() ) .dataFetcher(environment -> { - if ((environment.getArgument("ids") instanceof List)) { + if (environment.containsArgument("ids")) { + var ids = mapIDsToDomainNullSafe(environment.getArgument("ids")); + + // flexibleLines gets special treatment because it has a default value. if ( - environment - .getArguments() - .entrySet() - .stream() - .filter(it -> - it.getValue() != null && - !(it.getKey().equals("flexibleOnly") && it.getValue().equals(false)) - ) - .count() != - 1 + Stream + .of("name", "publicCode", "publicCodes", "transportModes", "authorities") + .anyMatch(environment::containsArgument) || + Boolean.TRUE.equals(environment.getArgument("flexibleOnly")) ) { throw new IllegalArgumentException("Unable to combine other filters with ids"); } - return ((List) environment.getArgument("ids")).stream() - .map(TransitIdMapper::mapIDToDomain) - .map(id -> { - return GqlUtil.getTransitService(environment).getRoute(id); - }) - .collect(Collectors.toList()); - } - Stream stream = GqlUtil.getTransitService(environment).listRoutes().stream(); - if ((boolean) environment.getArgument("flexibleOnly")) { - Collection flexRoutes = GqlUtil - .getTransitService(environment) - .getFlexIndex() - .getAllFlexRoutes(); - stream = stream.filter(flexRoutes::contains); - } - if (environment.getArgument("name") != null) { - stream = - stream - .filter(route -> route.getLongName() != null) - .filter(route -> - route - .getLongName() - .toString() - .toLowerCase() - .startsWith(((String) environment.getArgument("name")).toLowerCase()) - ); - } - if (environment.getArgument("publicCode") != null) { - stream = - stream - .filter(route -> route.getShortName() != null) - .filter(route -> - route.getShortName().equals(environment.getArgument("publicCode")) - ); - } - if (environment.getArgument("publicCodes") instanceof List) { - Set publicCodes = Set.copyOf(environment.getArgument("publicCodes")); - stream = - stream - .filter(route -> route.getShortName() != null) - .filter(route -> publicCodes.contains(route.getShortName())); - } - if (environment.getArgument("transportModes") != null) { - Set modes = Set.copyOf(environment.getArgument("transportModes")); - stream = stream.filter(route -> modes.contains(route.getMode())); - } - if ((environment.getArgument("authorities") instanceof Collection)) { - Collection authorityIds = environment.getArgument("authorities"); - stream = - stream.filter(route -> - route.getAgency() != null && - authorityIds.contains(route.getAgency().getId().getId()) - ); + return GqlUtil.getTransitService(environment).getRoutes(ids); } - return stream.collect(Collectors.toList()); + + var name = environment.getArgument("name"); + var publicCode = environment.getArgument("publicCode"); + var publicCodes = FilterValues.ofEmptyIsEverything( + "publicCodes", + environment.>getArgument("publicCodes") + ); + var transportModes = FilterValues.ofEmptyIsEverything( + "transportModes", + environment.>getArgument("transportModes") + ); + var authorities = FilterValues.ofEmptyIsEverything( + "authorities", + environment.>getArgument("authorities") + ); + boolean flexibleOnly = Boolean.TRUE.equals(environment.getArgument("flexibleOnly")); + + FindRoutesRequest findRoutesRequest = FindRoutesRequest + .of() + .withLongName(name) + .withShortName(publicCode) + .withShortNames(publicCodes) + .withTransitModes(transportModes) + .withAgencies(authorities) + .withFlexibleOnly(flexibleOnly) + .build(); + + return GqlUtil.getTransitService(environment).findRoutes(findRoutesRequest); }) .build() ) diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java new file mode 100644 index 00000000000..d0f2153921d --- /dev/null +++ b/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequest.java @@ -0,0 +1,65 @@ +package org.opentripplanner.transit.api.request; + +import org.opentripplanner.transit.api.model.FilterValues; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.network.Route; + +/** + * A request for finding {@link Route}s. + *

+ * This request is used to retrieve Routes that match the provided filter values. + * At least one filter value must be provided. + */ +public class FindRoutesRequest { + + private final boolean flexibleOnly; + private final String longName; + private final String shortName; + private final FilterValues shortNames; + private final FilterValues transitModes; + private final FilterValues agencyIds; + + protected FindRoutesRequest( + boolean flexibleOnly, + String longName, + String shortName, + FilterValues shortNames, + FilterValues transitModes, + FilterValues agencyIds + ) { + this.flexibleOnly = flexibleOnly; + this.longName = longName; + this.shortName = shortName; + this.shortNames = shortNames; + this.transitModes = transitModes; + this.agencyIds = agencyIds; + } + + public static FindRoutesRequestBuilder of() { + return new FindRoutesRequestBuilder(); + } + + public boolean flexibleOnly() { + return flexibleOnly; + } + + public String longName() { + return longName; + } + + public String shortName() { + return shortName; + } + + public FilterValues shortNames() { + return shortNames; + } + + public FilterValues transitModes() { + return transitModes; + } + + public FilterValues agencies() { + return agencyIds; + } +} diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequestBuilder.java b/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequestBuilder.java new file mode 100644 index 00000000000..66b03904abe --- /dev/null +++ b/application/src/main/java/org/opentripplanner/transit/api/request/FindRoutesRequestBuilder.java @@ -0,0 +1,65 @@ +package org.opentripplanner.transit.api.request; + +import java.util.List; +import javax.annotation.Nullable; +import org.opentripplanner.transit.api.model.FilterValues; +import org.opentripplanner.transit.model.basic.TransitMode; + +public class FindRoutesRequestBuilder { + + private boolean flexibleOnly; + private String longName; + private String shortName; + private FilterValues shortNames = FilterValues.ofEmptyIsEverything( + "shortNames", + List.of() + ); + private FilterValues transitModes = FilterValues.ofEmptyIsEverything( + "transitModes", + List.of() + ); + private FilterValues agencies = FilterValues.ofEmptyIsEverything("agencies", List.of()); + + protected FindRoutesRequestBuilder() {} + + public FindRoutesRequestBuilder withAgencies(FilterValues agencies) { + this.agencies = agencies; + return this; + } + + public FindRoutesRequestBuilder withFlexibleOnly(boolean flexibleOnly) { + this.flexibleOnly = flexibleOnly; + return this; + } + + public FindRoutesRequestBuilder withLongName(@Nullable String longName) { + this.longName = longName; + return this; + } + + public FindRoutesRequestBuilder withShortName(@Nullable String shortName) { + this.shortName = shortName; + return this; + } + + public FindRoutesRequestBuilder withShortNames(FilterValues shortNames) { + this.shortNames = shortNames; + return this; + } + + public FindRoutesRequestBuilder withTransitModes(FilterValues transitModes) { + this.transitModes = transitModes; + return this; + } + + public FindRoutesRequest build() { + return new FindRoutesRequest( + flexibleOnly, + longName, + shortName, + shortNames, + transitModes, + agencies + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/transit/model/filter/expr/CaseInsensitiveStringPrefixMatcher.java b/application/src/main/java/org/opentripplanner/transit/model/filter/expr/CaseInsensitiveStringPrefixMatcher.java new file mode 100644 index 00000000000..befa4517426 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/transit/model/filter/expr/CaseInsensitiveStringPrefixMatcher.java @@ -0,0 +1,41 @@ +package org.opentripplanner.transit.model.filter.expr; + +import java.util.function.Function; + +/** + * A matcher that checks if a string field starts with a given value. + *

+ * @param The type of the entity being matched. + */ +public class CaseInsensitiveStringPrefixMatcher implements Matcher { + + private final String typeName; + private final String value; + private final Function valueProvider; + + /** + * @param typeName The typeName appears in the toString for easier debugging. + * @param value - The String that may be a prefix. + * @param valueProvider - A function that maps the entity being matched to the String being + * checked for a prefix match. + */ + public CaseInsensitiveStringPrefixMatcher( + String typeName, + String value, + Function valueProvider + ) { + this.typeName = typeName; + this.value = value; + this.valueProvider = valueProvider; + } + + @Override + public boolean match(T entity) { + return valueProvider.apply(entity).toLowerCase().startsWith(value.toLowerCase()); + } + + @Override + public String toString() { + return typeName + " has prefix: " + value; + } +} diff --git a/application/src/main/java/org/opentripplanner/transit/model/filter/expr/NullSafeWrapperMatcher.java b/application/src/main/java/org/opentripplanner/transit/model/filter/expr/NullSafeWrapperMatcher.java new file mode 100644 index 00000000000..4e972ac69c6 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/transit/model/filter/expr/NullSafeWrapperMatcher.java @@ -0,0 +1,42 @@ +package org.opentripplanner.transit.model.filter.expr; + +import java.util.function.Function; + +/** + * A matcher that validates that a value is not null before applying another matcher. A useful case + * is when you want to check that a String field is not null before applying a {@link CaseInsensitiveStringPrefixMatcher}. + *

+ * @param The type of the entity being matched. + * @param The type of the value that the matcher will test for not null. + */ +public class NullSafeWrapperMatcher implements Matcher { + + private final String typeName; + private final Function valueProvider; + private final Matcher valueMatcher; + + /** + * @param typeName The typeName appears in the toString for easier debugging. + * @param valueProvider The function that maps the entity being matched by this matcher (T) to + * the value being checked for non-null. + */ + public NullSafeWrapperMatcher( + String typeName, + Function valueProvider, + Matcher valueMatcher + ) { + this.typeName = typeName; + this.valueProvider = valueProvider; + this.valueMatcher = valueMatcher; + } + + @Override + public boolean match(T entity) { + return valueProvider.apply(entity) != null && valueMatcher.match(entity); + } + + @Override + public String toString() { + return typeName + " is not null"; + } +} diff --git a/application/src/main/java/org/opentripplanner/transit/model/filter/transit/RouteMatcherFactory.java b/application/src/main/java/org/opentripplanner/transit/model/filter/transit/RouteMatcherFactory.java new file mode 100644 index 00000000000..c673a193a50 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/transit/model/filter/transit/RouteMatcherFactory.java @@ -0,0 +1,81 @@ +package org.opentripplanner.transit.model.filter.transit; + +import java.util.function.Predicate; +import org.opentripplanner.transit.api.request.FindRoutesRequest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.filter.expr.CaseInsensitiveStringPrefixMatcher; +import org.opentripplanner.transit.model.filter.expr.EqualityMatcher; +import org.opentripplanner.transit.model.filter.expr.ExpressionBuilder; +import org.opentripplanner.transit.model.filter.expr.GenericUnaryMatcher; +import org.opentripplanner.transit.model.filter.expr.Matcher; +import org.opentripplanner.transit.model.filter.expr.NullSafeWrapperMatcher; +import org.opentripplanner.transit.model.network.Route; + +public class RouteMatcherFactory { + + public static Matcher of( + FindRoutesRequest request, + Predicate isFlexRoutePredicate + ) { + ExpressionBuilder expr = ExpressionBuilder.of(); + + if (request.flexibleOnly()) { + expr.matches(isFlexRoute(isFlexRoutePredicate)); + } + expr.atLeastOneMatch(request.agencies(), RouteMatcherFactory::agencies); + expr.atLeastOneMatch(request.transitModes(), RouteMatcherFactory::transitModes); + if (request.shortName() != null) { + expr.matches(shortName(request.shortName())); + } + expr.atLeastOneMatch(request.shortNames(), RouteMatcherFactory::shortNames); + if (request.longName() != null) { + expr.matches(longName(request.longName())); + } + + return expr.build(); + } + + static Matcher agencies(String agencyId) { + return new NullSafeWrapperMatcher<>( + "agency", + Route::getAgency, + new EqualityMatcher<>("agencyId", agencyId, route -> route.getAgency().getId().getId()) + ); + } + + static Matcher transitModes(TransitMode transitMode) { + return new EqualityMatcher<>("transitMode", transitMode, Route::getMode); + } + + static Matcher shortName(String publicCode) { + return new NullSafeWrapperMatcher<>( + "shortName", + Route::getShortName, + new EqualityMatcher<>("shortName", publicCode, Route::getShortName) + ); + } + + static Matcher shortNames(String publicCode) { + return new NullSafeWrapperMatcher<>( + "shortNames", + Route::getShortName, + new EqualityMatcher<>("shortNames", publicCode, Route::getShortName) + ); + } + + static Matcher isFlexRoute(Predicate isFlexRoute) { + return new GenericUnaryMatcher<>("isFlexRoute", isFlexRoute); + } + + static Matcher longName(String name) { + return new NullSafeWrapperMatcher<>( + "longName", + Route::getLongName, + new CaseInsensitiveStringPrefixMatcher<>( + "name", + name, + route -> route.getLongName().toString() + ) + ); + } +} diff --git a/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java index d27fe138ef4..7c59030605d 100644 --- a/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java +++ b/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java @@ -14,6 +14,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -36,12 +37,14 @@ import org.opentripplanner.routing.stoptimes.ArrivalDeparture; import org.opentripplanner.routing.stoptimes.StopTimesHelper; import org.opentripplanner.transit.api.request.FindRegularStopsByBoundingBoxRequest; +import org.opentripplanner.transit.api.request.FindRoutesRequest; import org.opentripplanner.transit.api.request.TripOnServiceDateRequest; import org.opentripplanner.transit.api.request.TripRequest; import org.opentripplanner.transit.model.basic.Notice; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.filter.expr.Matcher; import org.opentripplanner.transit.model.filter.transit.RegularStopMatcherFactory; +import org.opentripplanner.transit.model.filter.transit.RouteMatcherFactory; import org.opentripplanner.transit.model.filter.transit.TripMatcherFactory; import org.opentripplanner.transit.model.filter.transit.TripOnServiceDateMatcherFactory; import org.opentripplanner.transit.model.framework.AbstractTransitEntity; @@ -193,6 +196,17 @@ public Route getRoute(FeedScopedId id) { return timetableRepositoryIndex.getRouteForId(id); } + @Override + public Collection getRoutes(Collection ids) { + return ids.stream().map(this::getRoute).filter(Objects::nonNull).toList(); + } + + @Override + public Collection findRoutes(FindRoutesRequest request) { + Matcher matcher = RouteMatcherFactory.of(request, this.getFlexIndex()::contains); + return listRoutes().stream().filter(matcher::match).toList(); + } + /** * Add a route to the transit model. * Used only in unit tests. diff --git a/application/src/main/java/org/opentripplanner/transit/service/TransitService.java b/application/src/main/java/org/opentripplanner/transit/service/TransitService.java index 6e005b355d1..00cfcc6673d 100644 --- a/application/src/main/java/org/opentripplanner/transit/service/TransitService.java +++ b/application/src/main/java/org/opentripplanner/transit/service/TransitService.java @@ -25,6 +25,7 @@ import org.opentripplanner.routing.services.TransitAlertService; import org.opentripplanner.routing.stoptimes.ArrivalDeparture; import org.opentripplanner.transit.api.request.FindRegularStopsByBoundingBoxRequest; +import org.opentripplanner.transit.api.request.FindRoutesRequest; import org.opentripplanner.transit.api.request.TripOnServiceDateRequest; import org.opentripplanner.transit.api.request.TripRequest; import org.opentripplanner.transit.model.basic.Notice; @@ -105,6 +106,11 @@ public interface TransitService { */ Route getRoute(FeedScopedId id); + /** + * Return all routes for a given set of ids, including routes created by real-time updates. + */ + Collection getRoutes(Collection ids); + /** * Return the routes using the given stop, not including real-time updates. */ @@ -334,4 +340,9 @@ List findTripTimeOnDate( Collection findRegularStopsByBoundingBox( FindRegularStopsByBoundingBoxRequest request ); + + /** + * Returns a list of {@link Route}s that match the filtering defined in the request. + */ + Collection findRoutes(FindRoutesRequest request); } diff --git a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index c01115a2120..611f6e38156 100644 --- a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -651,16 +651,21 @@ type QueryType { leg(id: ID!): Leg @timingData "Get a single line based on its id" line(id: ID!): Line @timingData - "Get all lines" + "Get all _lines_" lines( - "Set of ids of authorities to fetch lines for." + "Set of ids of _authorities_ to fetch _lines_ for." authorities: [String], - "Filter by lines containing flexible / on demand serviceJourneys only." + "Filter by _lines_ containing flexible / on demand _service journey_ only." flexibleOnly: Boolean = false, + "Set of ids of _lines_ to fetch. If this is set, no other filters can be set." ids: [ID], + "Prefix of the _name_ of the _line_ to fetch. This filter is case insensitive." name: String, + "_Public code_ of the _line_ to fetch." publicCode: String, + "Set of _public codes_ to fetch _lines_ for." publicCodes: [String], + "Set of _transport modes_ to fetch _lines_ for." transportModes: [TransportMode] ): [Line]! @timingData "Get all places (quays, stop places, car parks etc. with coordinates) within the specified radius from a location. The returned type has two fields place and distance. The search is done by walking so the distance is according to the network of walkables." diff --git a/application/src/test/java/org/opentripplanner/transit/model/filter/expr/CaseInsensitiveStringPrefixMatcherTest.java b/application/src/test/java/org/opentripplanner/transit/model/filter/expr/CaseInsensitiveStringPrefixMatcherTest.java new file mode 100644 index 00000000000..112fbaa7002 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/transit/model/filter/expr/CaseInsensitiveStringPrefixMatcherTest.java @@ -0,0 +1,16 @@ +package org.opentripplanner.transit.model.filter.expr; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class CaseInsensitiveStringPrefixMatcherTest { + + @Test + void testMatches() { + var matcher = new CaseInsensitiveStringPrefixMatcher<>("prefix", "foo", s -> s.toString()); + assertTrue(matcher.match("foo")); + assertTrue(matcher.match("foobar")); + assertFalse(matcher.match("bar")); + } +} diff --git a/application/src/test/java/org/opentripplanner/transit/model/filter/expr/NullSafeWrapperMatcherTest.java b/application/src/test/java/org/opentripplanner/transit/model/filter/expr/NullSafeWrapperMatcherTest.java new file mode 100644 index 00000000000..0b2f008a2fc --- /dev/null +++ b/application/src/test/java/org/opentripplanner/transit/model/filter/expr/NullSafeWrapperMatcherTest.java @@ -0,0 +1,30 @@ +package org.opentripplanner.transit.model.filter.expr; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class NullSafeWrapperMatcherTest { + + @Test + void testMatches() { + var matcher = new NullSafeWrapperMatcher<>( + "string", + s -> s, + new CaseInsensitiveStringPrefixMatcher<>("string", "namePrefix", s -> s.toString()) + ); + assertTrue(matcher.match("namePrefix and more")); + assertFalse(matcher.match("not namePrefix")); + assertFalse(matcher.match(null)); + } + + @Test + void testFailsWithoutNullSafeWrapperMatcher() { + var matcher = new CaseInsensitiveStringPrefixMatcher<>( + "string", + "here's a string", + s -> s.toString() + ); + assertThrows(NullPointerException.class, () -> matcher.match(null)); + } +} diff --git a/application/src/test/java/org/opentripplanner/transit/model/filter/transit/RouteMatcherFactoryTest.java b/application/src/test/java/org/opentripplanner/transit/model/filter/transit/RouteMatcherFactoryTest.java new file mode 100644 index 00000000000..ce9230ac0a6 --- /dev/null +++ b/application/src/test/java/org/opentripplanner/transit/model/filter/transit/RouteMatcherFactoryTest.java @@ -0,0 +1,146 @@ +package org.opentripplanner.transit.model.filter.transit; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Locale; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.transit.api.model.FilterValues; +import org.opentripplanner.transit.api.request.FindRoutesRequest; +import org.opentripplanner.transit.model._data.TimetableRepositoryForTest; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.filter.expr.Matcher; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.organization.Agency; + +class RouteMatcherFactoryTest { + + private Route route1; + private Route route2; + + @BeforeEach + void setUp() { + route1 = + Route + .of(new FeedScopedId("feedId", "routeId")) + .withAgency(TimetableRepositoryForTest.agency("AGENCY")) + .withMode(TransitMode.BUS) + .withShortName("ROUTE1") + .withLongName(I18NString.of("ROUTE1LONG")) + .build(); + route2 = + Route + .of(new FeedScopedId("otherFeedId", "otherRouteId")) + .withAgency(TimetableRepositoryForTest.agency("OTHER_AGENCY")) + .withMode(TransitMode.RAIL) + .withShortName("ROUTE2") + .withLongName(I18NString.of("ROUTE2LONG")) + .build(); + } + + @Test + void testAgencies() { + FindRoutesRequest request = FindRoutesRequest + .of() + .withAgencies(FilterValues.ofEmptyIsEverything("agencies", List.of("AGENCY"))) + .build(); + + Matcher matcher = RouteMatcherFactory.of(request, r -> false); + assertTrue(matcher.match(route1)); + assertFalse(matcher.match(route2)); + } + + @Test + void testTransitModes() { + FindRoutesRequest request = FindRoutesRequest + .of() + .withTransitModes(FilterValues.ofEmptyIsEverything("transitModes", List.of(TransitMode.BUS))) + .build(); + + Matcher matcher = RouteMatcherFactory.of(request, r -> false); + assertTrue(matcher.match(route1)); + assertFalse(matcher.match(route2)); + } + + @Test + void testShortLongName() { + FindRoutesRequest request = FindRoutesRequest.of().withShortName("ROUTE1").build(); + + Matcher matcher = RouteMatcherFactory.of(request, r -> false); + assertTrue(matcher.match(route1)); + assertFalse(matcher.match(route2)); + } + + @Test + void testShortNames() { + FindRoutesRequest request = FindRoutesRequest + .of() + .withShortNames(FilterValues.ofEmptyIsEverything("publicCodes", List.of("ROUTE1", "ROUTE3"))) + .build(); + + Matcher matcher = RouteMatcherFactory.of(request, r -> false); + assertTrue(matcher.match(route1)); + assertFalse(matcher.match(route2)); + } + + @Test + void testIsFlexRoute() { + FindRoutesRequest request = FindRoutesRequest.of().withFlexibleOnly(true).build(); + + Set flexRoutes = Set.of(route1); + + Matcher matcher = RouteMatcherFactory.of(request, flexRoutes::contains); + assertTrue(matcher.match(route1)); + assertFalse(matcher.match(route2)); + } + + @Test + void testLongNameExactMatch() { + FindRoutesRequest request = FindRoutesRequest.of().withLongName("ROUTE1LONG").build(); + + Matcher matcher = RouteMatcherFactory.of(request, r -> false); + assertTrue(matcher.match(route1)); + assertFalse(matcher.match(route2)); + } + + @Test + void testLongNamePrefixMatch() { + FindRoutesRequest request = FindRoutesRequest.of().withLongName("ROUTE1").build(); + + Matcher matcher = RouteMatcherFactory.of(request, r -> false); + assertTrue(matcher.match(route1)); + assertFalse(matcher.match(route2)); + } + + @Test + void testLongNameCaseInsensitivePrefixMatch() { + FindRoutesRequest request = FindRoutesRequest.of().withLongName("route1").build(); + + Matcher matcher = RouteMatcherFactory.of(request, r -> false); + assertTrue(matcher.match(route1)); + assertFalse(matcher.match(route2)); + } + + @Test + void testAll() { + FindRoutesRequest request = FindRoutesRequest + .of() + .withAgencies(FilterValues.ofEmptyIsEverything("agencies", List.of("AGENCY"))) + .withTransitModes(FilterValues.ofEmptyIsEverything("transitModes", List.of(TransitMode.BUS))) + .withShortName("ROUTE1") + .withShortNames(FilterValues.ofEmptyIsEverything("publicCodes", List.of("ROUTE1", "ROUTE3"))) + .withFlexibleOnly(true) + .withLongName("ROUTE1") + .build(); + + Set flexRoutes = Set.of(route1); + + Matcher matcher = RouteMatcherFactory.of(request, flexRoutes::contains); + assertTrue(matcher.match(route1)); + assertFalse(matcher.match(route2)); + } +} From 7ab6d05751e69af338639ec572336c3445cce8e9 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Wed, 29 Jan 2025 13:59:40 +0000 Subject: [PATCH 194/195] Add changelog entry for #6378 [ci skip] --- doc/user/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md index 88b6fd6847c..70c6ffcebf9 100644 --- a/doc/user/Changelog.md +++ b/doc/user/Changelog.md @@ -82,6 +82,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add 'maxStopCountForMode' to the router config [#6383](https://github.com/opentripplanner/OpenTripPlanner/pull/6383) - Add all routing parameters to debug UI [#6370](https://github.com/opentripplanner/OpenTripPlanner/pull/6370) - Add currentFuelPercent and currentRangeMeters to RentalVehichle in the GTFS GraphQL API [#6272](https://github.com/opentripplanner/OpenTripPlanner/pull/6272) +- Add a matcher API for filters in the transit service used for route lookup [#6378](https://github.com/opentripplanner/OpenTripPlanner/pull/6378) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.6.0 (2024-09-18) From c8fbf0296cf2421966e8def812394a9384787946 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 29 Jan 2025 21:17:28 +0100 Subject: [PATCH 195/195] Fix typo --- doc/templates/DebugUiConfiguration.md | 2 +- doc/user/DebugUiConfiguration.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/templates/DebugUiConfiguration.md b/doc/templates/DebugUiConfiguration.md index a54b1db8d2a..51491b36cef 100644 --- a/doc/templates/DebugUiConfiguration.md +++ b/doc/templates/DebugUiConfiguration.md @@ -8,7 +8,7 @@ # Debug UI configuration The Debug UI is the standard interface that is bundled with OTP and available by visiting -[`http://localhost:8080`](http://localhost:8080). This page list the configuration options available +[`http://localhost:8080`](http://localhost:8080). This page lists the configuration options available by placing a file `debug-ui-config.json` into OTP's working directory. diff --git a/doc/user/DebugUiConfiguration.md b/doc/user/DebugUiConfiguration.md index a1657796fe0..d0a9c65c9a0 100644 --- a/doc/user/DebugUiConfiguration.md +++ b/doc/user/DebugUiConfiguration.md @@ -8,7 +8,7 @@ # Debug UI configuration The Debug UI is the standard interface that is bundled with OTP and available by visiting -[`http://localhost:8080`](http://localhost:8080). This page list the configuration options available +[`http://localhost:8080`](http://localhost:8080). This page lists the configuration options available by placing a file `debug-ui-config.json` into OTP's working directory.