From 3ca1a0c7914ccf2e5aa77b47af5a0ce1eae3ee24 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 23 May 2024 15:20:27 +0200 Subject: [PATCH 001/148] Implement query for filtering by service days --- .../apis/gtfs/GraphQLScalars.java | 5 ++ .../apis/gtfs/GtfsGraphQLIndex.java | 8 +-- .../apis/gtfs/datafetchers/RouteImpl.java | 45 ++++++++++++++++- .../apis/gtfs/generated/GraphQLTypes.java | 49 +++++++++++++++++++ .../apis/gtfs/generated/graphql-codegen.yml | 1 + .../apis/transmodel/support/GqlUtil.java | 4 +- .../graphql/scalar}/DateScalarFactory.java | 20 +++++--- .../opentripplanner/apis/gtfs/schema.graphqls | 12 ++++- .../graphql/scalar/DateScalarFactoryTest.java | 23 +++++++++ .../gtfs/expectations/routes-extended.json | 42 ++++++++++------ .../apis/gtfs/queries/routes-extended.graphql | 3 ++ 11 files changed, 180 insertions(+), 32 deletions(-) rename src/main/java/org/opentripplanner/{apis/transmodel/model/scalars => framework/graphql/scalar}/DateScalarFactory.java (72%) create mode 100644 src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index 5ab24c89543..a3034cad273 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -15,6 +15,7 @@ import java.time.format.DateTimeFormatter; import javax.annotation.Nonnull; import org.locationtech.jts.geom.Geometry; +import org.opentripplanner.framework.graphql.scalar.DateScalarFactory; import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory; import org.opentripplanner.framework.json.ObjectMappers; import org.opentripplanner.framework.model.Grams; @@ -111,6 +112,10 @@ public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralExce ) .build(); + public static final GraphQLScalarType LOCAL_DATE_SCALAR = DateScalarFactory.createDateScalar( + "LocalDate" + ); + public static final GraphQLScalarType GEOJSON_SCALAR = GraphQLScalarType .newScalar() .name("GeoJson") diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 53d53b9bc4b..20a37d6b940 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -22,8 +22,6 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.opentripplanner.apis.gtfs.datafetchers.AgencyImpl; @@ -84,7 +82,6 @@ import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation; import org.opentripplanner.framework.application.OTPFeature; -import org.opentripplanner.framework.concurrent.OtpRequestThreadFactory; import org.opentripplanner.framework.graphql.GraphQLResponseSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -95,10 +92,6 @@ class GtfsGraphQLIndex { private static final GraphQLSchema indexSchema = buildSchema(); - static final ExecutorService threadPool = Executors.newCachedThreadPool( - OtpRequestThreadFactory.of("gtfs-api-%d") - ); - protected static GraphQLSchema buildSchema() { try { URL url = Objects.requireNonNull(GtfsGraphQLIndex.class.getResource("schema.graphqls")); @@ -112,6 +105,7 @@ protected static GraphQLSchema buildSchema() { .scalar(GraphQLScalars.GRAPHQL_ID_SCALAR) .scalar(GraphQLScalars.GRAMS_SCALAR) .scalar(GraphQLScalars.OFFSET_DATETIME_SCALAR) + .scalar(GraphQLScalars.LOCAL_DATE_SCALAR) .scalar(ExtendedScalars.GraphQLLong) .type("Node", type -> type.typeResolver(new NodeTypeResolver())) .type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver())) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java index 9f9b3c60b31..4b21728b8b4 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java @@ -3,6 +3,7 @@ import graphql.relay.Relay; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -174,10 +175,32 @@ public DataFetcher mode() { @Override public DataFetcher> patterns() { - return environment -> - getTransitService(environment).getPatternsForRoute(getSource(environment)); + return environment -> { + final TransitService transitService = getTransitService(environment); + var patterns = transitService.getPatternsForRoute(getSource(environment)); + + var args = new GraphQLTypes.GraphQLRoutePatternsArgs(environment.getArguments()); + + var serviceDays = args.getGraphQLServiceDays(); + if ( + serviceDays != null && + (serviceDays.getGraphQLStart() != null || serviceDays.getGraphQLEnd() != null) + ) { + var start = args.getGraphQLServiceDays().getGraphQLStart(); + return patterns + .stream() + .filter(pattern -> + hasServicesOnDate(patterns, transitService, start) + ) + .toList(); + } else { + return patterns; + } + }; } + + @Override public DataFetcher shortName() { return environment -> getSource(environment).getShortName(); @@ -241,4 +264,22 @@ private TransitService getTransitService(DataFetchingEnvironment environment) { private Route getSource(DataFetchingEnvironment environment) { return environment.getSource(); } + + private static boolean hasServicesOnDate(Collection patterns, TransitService transitService, LocalDate start) { + return patterns + .stream() + .anyMatch(p -> + p + .scheduledTripsAsStream() + .anyMatch(trip -> { + var dates = transitService + .getCalendarService() + .getServiceDatesForServiceId(trip.getServiceId()); + + return dates + .stream() + .anyMatch(date -> date.isEqual(start) || date.isAfter(start)); + }) + ); + } } 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 ecc038fec18..de625f15c7b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -2751,6 +2751,26 @@ public void setGraphQLLanguage(String language) { } } + public static class GraphQLRoutePatternsArgs { + + private GraphQLServiceDayFilterInput serviceDays; + + public GraphQLRoutePatternsArgs(Map args) { + if (args != null) { + this.serviceDays = + new GraphQLServiceDayFilterInput((Map) args.get("serviceDays")); + } + } + + public GraphQLServiceDayFilterInput getGraphQLServiceDays() { + return this.serviceDays; + } + + public void setGraphQLServiceDays(GraphQLServiceDayFilterInput serviceDays) { + this.serviceDays = serviceDays; + } + } + /** Entities that are relevant for routes that can contain alerts */ public enum GraphQLRouteAlertType { AGENCY, @@ -2772,6 +2792,35 @@ public enum GraphQLRoutingErrorCode { WALKING_BETTER_THAN_TRANSIT, } + public static class GraphQLServiceDayFilterInput { + + private java.time.LocalDate end; + private java.time.LocalDate start; + + public GraphQLServiceDayFilterInput(Map args) { + if (args != null) { + this.end = (java.time.LocalDate) args.get("end"); + this.start = (java.time.LocalDate) args.get("start"); + } + } + + public java.time.LocalDate getGraphQLEnd() { + return this.end; + } + + public java.time.LocalDate getGraphQLStart() { + return this.start; + } + + public void setGraphQLEnd(java.time.LocalDate end) { + this.end = end; + } + + public void setGraphQLStart(java.time.LocalDate start) { + this.start = start; + } + } + public static class GraphQLStopAlertsArgs { private List types; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index 29c2bff0257..9309e60224f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -28,6 +28,7 @@ config: Grams: org.opentripplanner.framework.model.Grams OffsetDateTime: java.time.OffsetDateTime Duration: java.time.Duration + LocalDate: java.time.LocalDate mappers: AbsoluteDirection: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLAbsoluteDirection#GraphQLAbsoluteDirection Agency: org.opentripplanner.transit.model.organization.Agency#Agency diff --git a/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java b/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java index 1f0722eb991..05a8eaa45fa 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java @@ -15,12 +15,12 @@ import java.util.Locale; import org.opentripplanner.apis.transmodel.TransmodelRequestContext; import org.opentripplanner.apis.transmodel.mapping.TransitIdMapper; -import org.opentripplanner.apis.transmodel.model.scalars.DateScalarFactory; import org.opentripplanner.apis.transmodel.model.scalars.DateTimeScalarFactory; import org.opentripplanner.apis.transmodel.model.scalars.DoubleFunctionFactory; import org.opentripplanner.apis.transmodel.model.scalars.LocalTimeScalarFactory; import org.opentripplanner.apis.transmodel.model.scalars.TimeScalarFactory; import org.opentripplanner.framework.graphql.GraphQLUtils; +import org.opentripplanner.framework.graphql.scalar.DateScalarFactory; import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory; import org.opentripplanner.routing.graphfinder.GraphFinder; import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; @@ -45,7 +45,7 @@ public class GqlUtil { public GqlUtil(ZoneId timeZone) { this.dateTimeScalar = DateTimeScalarFactory.createMillisecondsSinceEpochAsDateTimeStringScalar(timeZone); - this.dateScalar = DateScalarFactory.createDateScalar(); + this.dateScalar = DateScalarFactory.createDateScalar("Date"); this.doubleFunctionScalar = DoubleFunctionFactory.createDoubleFunctionScalar(); this.localTimeScalar = LocalTimeScalarFactory.createLocalTimeScalar(); this.timeScalar = TimeScalarFactory.createSecondsSinceMidnightAsTimeObject(); diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateScalarFactory.java b/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java similarity index 72% rename from src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateScalarFactory.java rename to src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java index 6d45018ed2a..c19add1e0cb 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/model/scalars/DateScalarFactory.java +++ b/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.transmodel.model.scalars; +package org.opentripplanner.framework.graphql.scalar; import graphql.language.StringValue; import graphql.schema.Coercing; @@ -8,6 +8,7 @@ import graphql.schema.GraphQLScalarType; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; public class DateScalarFactory { @@ -15,14 +16,21 @@ public class DateScalarFactory { private static final String DOCUMENTATION = "Local date using the ISO 8601 format: `YYYY-MM-DD`. Example: `2020-05-17`."; - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; + private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder() + .optionalStart() + .append(DateTimeFormatter.ofPattern("yyyyMMdd")) + .optionalEnd() + .optionalStart() + .append(DateTimeFormatter.ISO_LOCAL_DATE) + .optionalStart() + .toFormatter(); private DateScalarFactory() {} - public static GraphQLScalarType createDateScalar() { + public static GraphQLScalarType createDateScalar(String scalarName) { return GraphQLScalarType .newScalar() - .name("Date") + .name(scalarName) .description(DOCUMENTATION) .coercing( new Coercing() { @@ -33,7 +41,7 @@ public String serialize(Object input) throws CoercingSerializeException { } throw new CoercingSerializeException( - "Only LocalDate is supported to serialize but found " + input + "Only %s is supported to serialize but found %s".formatted(scalarName, input) ); } @@ -43,7 +51,7 @@ public LocalDate parseValue(Object input) throws CoercingParseValueException { return LocalDate.from(FORMATTER.parse((String) input)); } catch (DateTimeParseException e) { throw new CoercingParseValueException( - "Expected type 'Date' but was '" + input + "'." + "Expected type '%s' but was '%s'.".formatted(scalarName, input) ); } } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 393d5f014db..44426296c29 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2542,6 +2542,13 @@ enum Qualifier { HAIL } +scalar LocalDate + +input ServiceDayFilterInput { + start: LocalDate + end: LocalDate +} + type QueryType { """Fetches an object given its ID""" node( @@ -3421,7 +3428,10 @@ type Route implements Node { bikesAllowed: BikesAllowed """List of patterns which operate on this route""" - patterns: [Pattern] + patterns( + "Filter patterns by the service day they operate on." + serviceDays: ServiceDayFilterInput + ): [Pattern] """List of stops on this route""" stops: [Stop] diff --git a/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java b/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java new file mode 100644 index 00000000000..afc4a41f408 --- /dev/null +++ b/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java @@ -0,0 +1,23 @@ +package org.opentripplanner.framework.graphql.scalar; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import graphql.schema.GraphQLScalarType; +import java.time.LocalDate; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class DateScalarFactoryTest { + + private static final GraphQLScalarType SCALAR = DateScalarFactory.createDateScalar("Date"); + + @ParameterizedTest + @ValueSource(strings = { "2024-05-23", "20240523" }) + void parse(String input) { + var result = SCALAR.getCoercing().parseValue(input); + assertInstanceOf(LocalDate.class, result); + var date = (LocalDate) result; + assertEquals(LocalDate.of(2024, 5, 23), date); + } +} diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/routes-extended.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/routes-extended.json index 8739eab0045..8856972ce4e 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/routes-extended.json +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/routes-extended.json @@ -11,7 +11,8 @@ }, "mode" : "CARPOOL", "sortOrder" : 12, - "bikesAllowed" : "ALLOWED" + "bikesAllowed" : "ALLOWED", + "patterns" : [ ] }, { "longName" : "Long name for SUBWAY", @@ -23,7 +24,8 @@ }, "mode" : "SUBWAY", "sortOrder" : 2, - "bikesAllowed" : "NO_INFORMATION" + "bikesAllowed" : "NO_INFORMATION", + "patterns" : [ ] }, { "longName" : "Long name for BUS", @@ -35,7 +37,8 @@ }, "mode" : "BUS", "sortOrder" : 3, - "bikesAllowed" : "ALLOWED" + "bikesAllowed" : "ALLOWED", + "patterns" : [ ] }, { "longName" : "Long name for FERRY", @@ -47,7 +50,8 @@ }, "mode" : "FERRY", "sortOrder" : 5, - "bikesAllowed" : "NO_INFORMATION" + "bikesAllowed" : "NO_INFORMATION", + "patterns" : [ ] }, { "longName" : "Long name for COACH", @@ -59,7 +63,8 @@ }, "mode" : "COACH", "sortOrder" : 1, - "bikesAllowed" : "NOT_ALLOWED" + "bikesAllowed" : "NOT_ALLOWED", + "patterns" : [ ] }, { "longName" : "Long name for TRAM", @@ -71,7 +76,8 @@ }, "mode" : "TRAM", "sortOrder" : 4, - "bikesAllowed" : "NOT_ALLOWED" + "bikesAllowed" : "NOT_ALLOWED", + "patterns" : [ ] }, { "longName" : "Long name for CABLE_CAR", @@ -83,7 +89,8 @@ }, "mode" : "CABLE_CAR", "sortOrder" : 7, - "bikesAllowed" : "NOT_ALLOWED" + "bikesAllowed" : "NOT_ALLOWED", + "patterns" : [ ] }, { "longName" : "Long name for FUNICULAR", @@ -95,7 +102,8 @@ }, "mode" : "FUNICULAR", "sortOrder" : 9, - "bikesAllowed" : "ALLOWED" + "bikesAllowed" : "ALLOWED", + "patterns" : [ ] }, { "longName" : "Long name for RAIL", @@ -107,7 +115,8 @@ }, "mode" : "RAIL", "sortOrder" : null, - "bikesAllowed" : "ALLOWED" + "bikesAllowed" : "ALLOWED", + "patterns" : [ ] }, { "longName" : "Long name for MONORAIL", @@ -119,7 +128,8 @@ }, "mode" : "MONORAIL", "sortOrder" : 11, - "bikesAllowed" : "NO_INFORMATION" + "bikesAllowed" : "NO_INFORMATION", + "patterns" : [ ] }, { "longName" : "Long name for GONDOLA", @@ -131,7 +141,8 @@ }, "mode" : "GONDOLA", "sortOrder" : 8, - "bikesAllowed" : "NO_INFORMATION" + "bikesAllowed" : "NO_INFORMATION", + "patterns" : [ ] }, { "longName" : "Long name for TROLLEYBUS", @@ -143,7 +154,8 @@ }, "mode" : "TROLLEYBUS", "sortOrder" : 10, - "bikesAllowed" : "NOT_ALLOWED" + "bikesAllowed" : "NOT_ALLOWED", + "patterns" : [ ] }, { "longName" : "Long name for AIRPLANE", @@ -155,7 +167,8 @@ }, "mode" : "AIRPLANE", "sortOrder" : 6, - "bikesAllowed" : "ALLOWED" + "bikesAllowed" : "ALLOWED", + "patterns" : [ ] }, { "longName" : "Long name for TAXI", @@ -167,7 +180,8 @@ }, "mode" : "TAXI", "sortOrder" : 13, - "bikesAllowed" : "NOT_ALLOWED" + "bikesAllowed" : "NOT_ALLOWED", + "patterns" : [ ] } ] } diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql index 50d89980442..5da7db77286 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql @@ -10,5 +10,8 @@ mode sortOrder bikesAllowed + patterns(serviceDays: {start: "2024-05-23" end: "2024-05-30"}) { + name + } } } \ No newline at end of file From c3cb2d663b0fecca30ab94408956bfea5defb060 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 24 May 2024 13:18:40 +0200 Subject: [PATCH 002/148] Move filtering logic into class --- .../apis/gtfs/PatternByServiceDaysFilter.java | 78 +++++++++++++++++++ .../apis/gtfs/datafetchers/QueryTypeImpl.java | 26 +++---- .../apis/gtfs/datafetchers/RouteImpl.java | 37 +-------- .../apis/gtfs/generated/GraphQLTypes.java | 15 ++++ .../opentripplanner/apis/gtfs/schema.graphqls | 2 + 5 files changed, 112 insertions(+), 46 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java new file mode 100644 index 00000000000..ee42964ae03 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java @@ -0,0 +1,78 @@ +package org.opentripplanner.apis.gtfs; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.Objects; +import java.util.stream.Stream; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLServiceDayFilterInput; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.service.TransitService; + +public class PatternByServiceDaysFilter { + + private final TransitService transitService; + private final LocalDate startInclusive; + private final LocalDate endInclusive; + + PatternByServiceDaysFilter( + TransitService transitService, + LocalDate startInclusive, + LocalDate endInclusive + ) { + Objects.requireNonNull(transitService); + this.transitService = transitService; + // optional + this.startInclusive = startInclusive; + this.endInclusive = endInclusive; + } + + public PatternByServiceDaysFilter( + TransitService transitService, + GraphQLServiceDayFilterInput graphQLServiceDays + ) { + this(transitService, graphQLServiceDays.getGraphQLStart(), graphQLServiceDays.getGraphQLEnd()); + } + + public Collection filterPatterns(Collection tripPatterns) { + return tripPatterns.stream().filter(this::hasServicesOnDate).toList(); + } + + public Stream filterRoutes(Stream routeStream) { + return routeStream.filter(r -> { + var patterns = transitService.getPatternsForRoute(r); + return !this.filterPatterns(patterns).isEmpty(); + }); + } + + private boolean hasServicesOnDate(TripPattern pattern) { + return pattern + .scheduledTripsAsStream() + .anyMatch(trip -> { + var dates = transitService + .getCalendarService() + .getServiceDatesForServiceId(trip.getServiceId()); + + var matchesStartInclusive = dates + .stream() + .anyMatch(date -> + date == null || date.isEqual(startInclusive) || date.isAfter(startInclusive) + ); + + var matchesEndInclusive = dates + .stream() + .anyMatch(date -> + date == null || date.isEqual(endInclusive) || date.isBefore(endInclusive) + ); + + return matchesStartInclusive && matchesEndInclusive; + }); + } + + public static boolean hasServiceDayFilter(GraphQLServiceDayFilterInput serviceDays) { + return ( + serviceDays != null && + (serviceDays.getGraphQLStart() != null || serviceDays.getGraphQLEnd() != null) + ); + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index a4f5cb00e2f..38bf604171c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -13,20 +13,14 @@ import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import graphql.schema.DataFetchingEnvironmentImpl; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Envelope; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.GraphQLUtils; +import org.opentripplanner.apis.gtfs.PatternByServiceDaysFilter; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLQueryTypeStopsByRadiusArgs; @@ -46,11 +40,7 @@ import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.error.RoutingValidationException; import org.opentripplanner.routing.fares.FareService; -import org.opentripplanner.routing.graphfinder.GraphFinder; -import org.opentripplanner.routing.graphfinder.NearbyStop; -import org.opentripplanner.routing.graphfinder.PatternAtStop; -import org.opentripplanner.routing.graphfinder.PlaceAtDistance; -import org.opentripplanner.routing.graphfinder.PlaceType; +import org.opentripplanner.routing.graphfinder.*; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; @@ -604,6 +594,16 @@ public DataFetcher> routes() { GraphQLUtils.startsWith(route.getLongName(), name, environment.getLocale()) ); } + + if ( + PatternByServiceDaysFilter.hasServiceDayFilter(args.getGraphQLLimitByPatternServiceDays()) + ) { + var filter = new PatternByServiceDaysFilter( + transitService, + args.getGraphQLLimitByPatternServiceDays() + ); + routeStream = filter.filterRoutes(routeStream); + } return routeStream.toList(); }; } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java index 4b21728b8b4..17b3cc56c87 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java @@ -3,13 +3,13 @@ import graphql.relay.Relay; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; -import java.time.LocalDate; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.GraphQLUtils; +import org.opentripplanner.apis.gtfs.PatternByServiceDaysFilter; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLBikesAllowed; @@ -181,26 +181,15 @@ public DataFetcher> patterns() { var args = new GraphQLTypes.GraphQLRoutePatternsArgs(environment.getArguments()); - var serviceDays = args.getGraphQLServiceDays(); - if ( - serviceDays != null && - (serviceDays.getGraphQLStart() != null || serviceDays.getGraphQLEnd() != null) - ) { - var start = args.getGraphQLServiceDays().getGraphQLStart(); - return patterns - .stream() - .filter(pattern -> - hasServicesOnDate(patterns, transitService, start) - ) - .toList(); + if (PatternByServiceDaysFilter.hasServiceDayFilter(args.getGraphQLServiceDays())) { + var filter = new PatternByServiceDaysFilter(transitService, args.getGraphQLServiceDays()); + return filter.filterPatterns(patterns); } else { return patterns; } }; } - - @Override public DataFetcher shortName() { return environment -> getSource(environment).getShortName(); @@ -264,22 +253,4 @@ private TransitService getTransitService(DataFetchingEnvironment environment) { private Route getSource(DataFetchingEnvironment environment) { return environment.getSource(); } - - private static boolean hasServicesOnDate(Collection patterns, TransitService transitService, LocalDate start) { - return patterns - .stream() - .anyMatch(p -> - p - .scheduledTripsAsStream() - .anyMatch(trip -> { - var dates = transitService - .getCalendarService() - .getServiceDatesForServiceId(trip.getServiceId()); - - return dates - .stream() - .anyMatch(date -> date.isEqual(start) || date.isAfter(start)); - }) - ); - } } 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 de625f15c7b..fcd64389f59 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -2266,6 +2266,7 @@ public static class GraphQLQueryTypeRoutesArgs { private List feeds; private List ids; + private GraphQLServiceDayFilterInput limitByPatternServiceDays; private String name; private List transportModes; @@ -2273,6 +2274,10 @@ public GraphQLQueryTypeRoutesArgs(Map args) { if (args != null) { this.feeds = (List) args.get("feeds"); this.ids = (List) args.get("ids"); + this.limitByPatternServiceDays = + new GraphQLServiceDayFilterInput( + (Map) args.get("limitByPatternServiceDays") + ); this.name = (String) args.get("name"); if (args.get("transportModes") != null) { this.transportModes = @@ -2292,6 +2297,10 @@ public List getGraphQLIds() { return this.ids; } + public GraphQLServiceDayFilterInput getGraphQLLimitByPatternServiceDays() { + return this.limitByPatternServiceDays; + } + public String getGraphQLName() { return this.name; } @@ -2308,6 +2317,12 @@ public void setGraphQLIds(List ids) { this.ids = ids; } + public void setGraphQLLimitByPatternServiceDays( + GraphQLServiceDayFilterInput limitByPatternServiceDays + ) { + this.limitByPatternServiceDays = limitByPatternServiceDays; + } + public void setGraphQLName(String name) { this.name = name; } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 44426296c29..c4ed7ff40d3 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2707,6 +2707,8 @@ type QueryType { """Only include routes, which use one of these modes""" transportModes: [Mode] + + limitByPatternServiceDays: ServiceDayFilterInput ): [Route] """ From 775a77618804adf2be6097d91d16a193a9437401 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 24 May 2024 14:37:45 +0200 Subject: [PATCH 003/148] Add test for range handling --- .../apis/gtfs/PatternByServiceDaysFilter.java | 10 +++- .../gtfs/PatternByServiceDaysFilterTest.java | 51 +++++++++++++++++++ .../graphql/scalar/DateScalarFactoryTest.java | 8 +++ 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java index ee42964ae03..b953699656a 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.Objects; import java.util.stream.Stream; +import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLServiceDayFilterInput; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; @@ -17,14 +18,19 @@ public class PatternByServiceDaysFilter { PatternByServiceDaysFilter( TransitService transitService, - LocalDate startInclusive, - LocalDate endInclusive + @Nullable LocalDate startInclusive, + @Nullable LocalDate endInclusive ) { Objects.requireNonNull(transitService); this.transitService = transitService; + // optional this.startInclusive = startInclusive; this.endInclusive = endInclusive; + + if (startInclusive != null && endInclusive != null && startInclusive.isAfter(endInclusive)) { + throw new IllegalArgumentException("start must be before end"); + } } public PatternByServiceDaysFilter( diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java new file mode 100644 index 00000000000..8c480119722 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java @@ -0,0 +1,51 @@ +package org.opentripplanner.apis.gtfs; + +import static java.time.LocalDate.parse; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; + +class PatternByServiceDaysFilterTest { + + private static final TransitService EMPTY_SERVICE = new DefaultTransitService(new TransitModel()); + + static List invalidRange() { + return List.of( + Arguments.of(parse("2024-05-02"), parse("2024-05-01")), + Arguments.of(parse("2024-05-03"), parse("2024-05-01")) + ); + } + + @ParameterizedTest + @MethodSource("invalidRange") + void invalidRange(LocalDate start, LocalDate end) { + assertThrows( + IllegalArgumentException.class, + () -> new PatternByServiceDaysFilter(EMPTY_SERVICE, start, end) + ); + } + + static List validRange() { + return List.of( + Arguments.of(parse("2024-05-02"), parse("2024-05-02")), + Arguments.of(parse("2024-05-02"), parse("2024-05-03")), + Arguments.of(null, parse("2024-05-03")), + Arguments.of(parse("2024-05-03"), null), + Arguments.of(null, null) + ); + } + + @ParameterizedTest + @MethodSource("validRange") + void validRange(LocalDate start, LocalDate end) { + assertDoesNotThrow(() -> new PatternByServiceDaysFilter(EMPTY_SERVICE, start, end)); + } +} diff --git a/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java b/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java index afc4a41f408..afc86cdf9ba 100644 --- a/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java +++ b/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java @@ -2,7 +2,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import graphql.schema.CoercingParseValueException; import graphql.schema.GraphQLScalarType; import java.time.LocalDate; import org.junit.jupiter.params.ParameterizedTest; @@ -20,4 +22,10 @@ void parse(String input) { var date = (LocalDate) result; assertEquals(LocalDate.of(2024, 5, 23), date); } + + @ParameterizedTest + @ValueSource(strings = { "2024-05", "2024", "2024-99-04", "202405-23" }) + void failParsing(String input) { + assertThrows(CoercingParseValueException.class, () -> SCALAR.getCoercing().parseValue(input)); + } } From 7e66dfd009b394878837ac578b7a7b30db63ce98 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 24 May 2024 15:47:44 +0200 Subject: [PATCH 004/148] Add tests --- .../apis/gtfs/PatternByServiceDaysFilter.java | 15 +-- .../gtfs/PatternByServiceDaysFilterTest.java | 94 +++++++++++++++++++ 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java index b953699656a..a53b7842257 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java @@ -59,19 +59,14 @@ private boolean hasServicesOnDate(TripPattern pattern) { .getCalendarService() .getServiceDatesForServiceId(trip.getServiceId()); - var matchesStartInclusive = dates + return dates .stream() .anyMatch(date -> - date == null || date.isEqual(startInclusive) || date.isAfter(startInclusive) + ( + startInclusive == null || date.isEqual(startInclusive) || date.isAfter(startInclusive) + ) && + (endInclusive == null || date.isEqual(endInclusive) || date.isBefore(endInclusive)) ); - - var matchesEndInclusive = dates - .stream() - .anyMatch(date -> - date == null || date.isEqual(endInclusive) || date.isBefore(endInclusive) - ); - - return matchesStartInclusive && matchesEndInclusive; }); } diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java index 8c480119722..76b4a3bb526 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java @@ -2,20 +2,57 @@ import static java.time.LocalDate.parse; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.time.LocalDate; +import java.util.Collection; import java.util.List; +import java.util.Set; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.model.calendar.CalendarService; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.network.StopPattern; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.timetable.ScheduledTripTimes; +import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; class PatternByServiceDaysFilterTest { private static final TransitService EMPTY_SERVICE = new DefaultTransitService(new TransitModel()); + private static final Route ROUTE_1 = TransitModelForTest.route("1").build(); + private static final Trip TRIP = TransitModelForTest.trip("t1").withRoute(ROUTE_1).build(); + private static final TransitModelForTest MODEL = new TransitModelForTest(StopModel.of()); + private static final RegularStop STOP_1 = MODEL.stop("1").build(); + private static final StopPattern STOP_PATTERN = TransitModelForTest.stopPattern(STOP_1, STOP_1); + private static final TripPattern PATTERN_1 = pattern(); + private static final List NOT_REMOVED = List.of(PATTERN_1); + private static final List REMOVED = List.of(); + + private static TripPattern pattern() { + var pattern = TransitModelForTest + .tripPattern("1", ROUTE_1) + .withStopPattern(STOP_PATTERN) + .build(); + + var tt = ScheduledTripTimes + .of() + .withTrip(TRIP) + .withArrivalTimes("10:00 10:05") + .withDepartureTimes("10:00 10:05") + .build(); + pattern.add(tt); + return pattern; + } static List invalidRange() { return List.of( @@ -48,4 +85,61 @@ static List validRange() { void validRange(LocalDate start, LocalDate end) { assertDoesNotThrow(() -> new PatternByServiceDaysFilter(EMPTY_SERVICE, start, end)); } + + static List patternRanges() { + return List.of( + Arguments.of(null, null, NOT_REMOVED), + Arguments.of(null, parse("2024-05-03"), NOT_REMOVED), + Arguments.of(null, parse("2024-05-01"), NOT_REMOVED), + Arguments.of(parse("2024-05-03"), null, NOT_REMOVED), + Arguments.of(parse("2024-05-02"), parse("2024-05-02"), REMOVED), + Arguments.of(parse("2024-05-02"), parse("2024-05-03"), REMOVED), + Arguments.of(parse("2025-01-01"), null, REMOVED), + Arguments.of(parse("2025-01-01"), parse("2025-01-02"), REMOVED), + Arguments.of(null, parse("2023-12-31"), REMOVED), + Arguments.of(parse("2023-12-31"), parse("2024-04-30"), REMOVED) + ); + } + + @ParameterizedTest + @MethodSource("patternRanges") + void filterPatterns(LocalDate start, LocalDate end, List expectedPatterns) { + var model = new TransitModel(); + var service = mockService(model); + + var filter = new PatternByServiceDaysFilter(service, start, end); + + var result = filter.filterPatterns(NOT_REMOVED); + + assertEquals(expectedPatterns, result); + } + + private static TransitService mockService(TransitModel model) { + return new DefaultTransitService(model) { + @Override + public Collection getPatternsForRoute(Route route) { + return Set.of(PATTERN_1); + } + + @Override + public CalendarService getCalendarService() { + return new CalendarService() { + @Override + public Set getServiceIds() { + return Set.of(); + } + + @Override + public Set getServiceDatesForServiceId(FeedScopedId serviceId) { + return Set.of(parse("2024-05-01"), parse("2024-06-01")); + } + + @Override + public Set getServiceIdsOnDate(LocalDate date) { + return Set.of(); + } + }; + } + }; + } } From cbe912dd1fcc9361840105983c04880ef1ebba87 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 24 May 2024 16:45:56 +0200 Subject: [PATCH 005/148] Flesh out tests --- .../apis/gtfs/PatternByServiceDaysFilter.java | 12 +++-- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 17 +++++-- .../gtfs/PatternByServiceDaysFilterTest.java | 46 ++++++++++++++----- 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java index a53b7842257..6d6a5e3d8e1 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java @@ -44,11 +44,13 @@ public Collection filterPatterns(Collection tripPatter return tripPatterns.stream().filter(this::hasServicesOnDate).toList(); } - public Stream filterRoutes(Stream routeStream) { - return routeStream.filter(r -> { - var patterns = transitService.getPatternsForRoute(r); - return !this.filterPatterns(patterns).isEmpty(); - }); + public Collection filterRoutes(Stream routeStream) { + return routeStream + .filter(r -> { + var patterns = transitService.getPatternsForRoute(r); + return !this.filterPatterns(patterns).isEmpty(); + }) + .toList(); } private boolean hasServicesOnDate(TripPattern pattern) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 38bf604171c..d5f99860e5d 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -13,7 +13,14 @@ import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import graphql.schema.DataFetchingEnvironmentImpl; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; import org.locationtech.jts.geom.Coordinate; @@ -40,7 +47,11 @@ import org.opentripplanner.routing.core.FareType; import org.opentripplanner.routing.error.RoutingValidationException; import org.opentripplanner.routing.fares.FareService; -import org.opentripplanner.routing.graphfinder.*; +import org.opentripplanner.routing.graphfinder.GraphFinder; +import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.routing.graphfinder.PatternAtStop; +import org.opentripplanner.routing.graphfinder.PlaceAtDistance; +import org.opentripplanner.routing.graphfinder.PlaceType; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; import org.opentripplanner.service.vehiclerental.VehicleRentalService; @@ -602,7 +613,7 @@ public DataFetcher> routes() { transitService, args.getGraphQLLimitByPatternServiceDays() ); - routeStream = filter.filterRoutes(routeStream); + routeStream = filter.filterRoutes(routeStream).stream(); } return routeStream.toList(); }; diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java index 76b4a3bb526..32f6a78b2ee 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java @@ -4,6 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opentripplanner.apis.gtfs.PatternByServiceDaysFilterTest.FilterExpectation.NOT_REMOVED; +import static org.opentripplanner.apis.gtfs.PatternByServiceDaysFilterTest.FilterExpectation.REMOVED; import java.time.LocalDate; import java.util.Collection; @@ -35,8 +37,11 @@ class PatternByServiceDaysFilterTest { private static final RegularStop STOP_1 = MODEL.stop("1").build(); private static final StopPattern STOP_PATTERN = TransitModelForTest.stopPattern(STOP_1, STOP_1); private static final TripPattern PATTERN_1 = pattern(); - private static final List NOT_REMOVED = List.of(PATTERN_1); - private static final List REMOVED = List.of(); + + enum FilterExpectation { + REMOVED, + NOT_REMOVED, + } private static TripPattern pattern() { var pattern = TransitModelForTest @@ -86,7 +91,7 @@ void validRange(LocalDate start, LocalDate end) { assertDoesNotThrow(() -> new PatternByServiceDaysFilter(EMPTY_SERVICE, start, end)); } - static List patternRanges() { + static List ranges() { return List.of( Arguments.of(null, null, NOT_REMOVED), Arguments.of(null, parse("2024-05-03"), NOT_REMOVED), @@ -102,20 +107,39 @@ static List patternRanges() { } @ParameterizedTest - @MethodSource("patternRanges") - void filterPatterns(LocalDate start, LocalDate end, List expectedPatterns) { - var model = new TransitModel(); - var service = mockService(model); + @MethodSource("ranges") + void filterPatterns(LocalDate start, LocalDate end, FilterExpectation expectation) { + var service = mockService(); + var filter = new PatternByServiceDaysFilter(service, start, end); + + var filterInput = List.of(PATTERN_1); + var filterOutput = filter.filterPatterns(filterInput); + if (expectation == NOT_REMOVED) { + assertEquals(filterOutput, filterInput); + } else { + assertEquals(List.of(), filterOutput); + } + } + + @ParameterizedTest + @MethodSource("ranges") + void filterRoutes(LocalDate start, LocalDate end, FilterExpectation expectation) { + var service = mockService(); var filter = new PatternByServiceDaysFilter(service, start, end); - var result = filter.filterPatterns(NOT_REMOVED); + var filterInput = List.of(ROUTE_1); + var filterOutput = filter.filterRoutes(filterInput.stream()); - assertEquals(expectedPatterns, result); + if (expectation == NOT_REMOVED) { + assertEquals(filterOutput, filterInput); + } else { + assertEquals(List.of(), filterOutput); + } } - private static TransitService mockService(TransitModel model) { - return new DefaultTransitService(model) { + private static TransitService mockService() { + return new DefaultTransitService(new TransitModel()) { @Override public Collection getPatternsForRoute(Route route) { return Set.of(PATTERN_1); From d6b568fe6210f67c9fa37df9ffcf383e0938bb8b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 24 May 2024 17:22:22 +0200 Subject: [PATCH 006/148] Rename service day to service date --- ....java => PatternByServiceDatesFilter.java} | 33 +++++++++++++---- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 8 ++--- .../apis/gtfs/datafetchers/RouteImpl.java | 6 ++-- .../apis/gtfs/generated/GraphQLTypes.java | 36 +++++++++---------- .../opentripplanner/apis/gtfs/schema.graphqls | 24 ++++++++++--- ...a => PatternByServiceDatesFilterTest.java} | 14 ++++---- 6 files changed, 77 insertions(+), 44 deletions(-) rename src/main/java/org/opentripplanner/apis/gtfs/{PatternByServiceDaysFilter.java => PatternByServiceDatesFilter.java} (65%) rename src/test/java/org/opentripplanner/apis/gtfs/{PatternByServiceDaysFilterTest.java => PatternByServiceDatesFilterTest.java} (90%) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java similarity index 65% rename from src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java rename to src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index 6d6a5e3d8e1..313c44f2647 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -5,18 +5,30 @@ import java.util.Objects; import java.util.stream.Stream; import javax.annotation.Nullable; -import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLServiceDayFilterInput; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLServiceDateFilterInput; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.service.TransitService; -public class PatternByServiceDaysFilter { +/** + * Encapsulates the logic to filter patterns by the service dates that they operate on. It also + * has a method to filter routes by checking if their patterns operate on the required days + */ +public class PatternByServiceDatesFilter { private final TransitService transitService; private final LocalDate startInclusive; private final LocalDate endInclusive; - PatternByServiceDaysFilter( + /** + * + * @param transitService + * @param startInclusive The inclusive start date to check the patterns for. If null then no start + * date is defined and this will therefore match all dates. + * @param endInclusive The inclusive end date to check the patterns for. If null then no end date + * is defined this will therefore match all dates. + */ + PatternByServiceDatesFilter( TransitService transitService, @Nullable LocalDate startInclusive, @Nullable LocalDate endInclusive @@ -33,17 +45,24 @@ public class PatternByServiceDaysFilter { } } - public PatternByServiceDaysFilter( + public PatternByServiceDatesFilter( TransitService transitService, - GraphQLServiceDayFilterInput graphQLServiceDays + GraphQLServiceDateFilterInput filterInput ) { - this(transitService, graphQLServiceDays.getGraphQLStart(), graphQLServiceDays.getGraphQLEnd()); + this(transitService, filterInput.getGraphQLStart(), filterInput.getGraphQLEnd()); } + /** + * Filter the patterns by the service dates that it operates on. + */ public Collection filterPatterns(Collection tripPatterns) { return tripPatterns.stream().filter(this::hasServicesOnDate).toList(); } + /** + * Filter the routes by listing all their patterns' service dates and checking if they + * operate on the specified dates. + */ public Collection filterRoutes(Stream routeStream) { return routeStream .filter(r -> { @@ -72,7 +91,7 @@ private boolean hasServicesOnDate(TripPattern pattern) { }); } - public static boolean hasServiceDayFilter(GraphQLServiceDayFilterInput serviceDays) { + public static boolean hasServiceDayFilter(GraphQLServiceDateFilterInput serviceDays) { return ( serviceDays != null && (serviceDays.getGraphQLStart() != null || serviceDays.getGraphQLEnd() != null) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index d5f99860e5d..d5033af7da7 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -27,7 +27,7 @@ import org.locationtech.jts.geom.Envelope; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.GraphQLUtils; -import org.opentripplanner.apis.gtfs.PatternByServiceDaysFilter; +import org.opentripplanner.apis.gtfs.PatternByServiceDatesFilter; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLQueryTypeStopsByRadiusArgs; @@ -607,11 +607,11 @@ public DataFetcher> routes() { } if ( - PatternByServiceDaysFilter.hasServiceDayFilter(args.getGraphQLLimitByPatternServiceDays()) + PatternByServiceDatesFilter.hasServiceDayFilter(args.getGraphQLLimitByPatternServiceDates()) ) { - var filter = new PatternByServiceDaysFilter( + var filter = new PatternByServiceDatesFilter( transitService, - args.getGraphQLLimitByPatternServiceDays() + args.getGraphQLLimitByPatternServiceDates() ); routeStream = filter.filterRoutes(routeStream).stream(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java index 17b3cc56c87..d9b76ab72ef 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java @@ -9,7 +9,7 @@ import java.util.stream.Collectors; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.GraphQLUtils; -import org.opentripplanner.apis.gtfs.PatternByServiceDaysFilter; +import org.opentripplanner.apis.gtfs.PatternByServiceDatesFilter; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLBikesAllowed; @@ -181,8 +181,8 @@ public DataFetcher> patterns() { var args = new GraphQLTypes.GraphQLRoutePatternsArgs(environment.getArguments()); - if (PatternByServiceDaysFilter.hasServiceDayFilter(args.getGraphQLServiceDays())) { - var filter = new PatternByServiceDaysFilter(transitService, args.getGraphQLServiceDays()); + if (PatternByServiceDatesFilter.hasServiceDayFilter(args.getGraphQLServiceDates())) { + var filter = new PatternByServiceDatesFilter(transitService, args.getGraphQLServiceDates()); return filter.filterPatterns(patterns); } else { return patterns; 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 fcd64389f59..04da89080c3 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -2266,7 +2266,7 @@ public static class GraphQLQueryTypeRoutesArgs { private List feeds; private List ids; - private GraphQLServiceDayFilterInput limitByPatternServiceDays; + private GraphQLServiceDateFilterInput limitByPatternServiceDates; private String name; private List transportModes; @@ -2274,9 +2274,9 @@ public GraphQLQueryTypeRoutesArgs(Map args) { if (args != null) { this.feeds = (List) args.get("feeds"); this.ids = (List) args.get("ids"); - this.limitByPatternServiceDays = - new GraphQLServiceDayFilterInput( - (Map) args.get("limitByPatternServiceDays") + this.limitByPatternServiceDates = + new GraphQLServiceDateFilterInput( + (Map) args.get("limitByPatternServiceDates") ); this.name = (String) args.get("name"); if (args.get("transportModes") != null) { @@ -2297,8 +2297,8 @@ public List getGraphQLIds() { return this.ids; } - public GraphQLServiceDayFilterInput getGraphQLLimitByPatternServiceDays() { - return this.limitByPatternServiceDays; + public GraphQLServiceDateFilterInput getGraphQLLimitByPatternServiceDates() { + return this.limitByPatternServiceDates; } public String getGraphQLName() { @@ -2317,10 +2317,10 @@ public void setGraphQLIds(List ids) { this.ids = ids; } - public void setGraphQLLimitByPatternServiceDays( - GraphQLServiceDayFilterInput limitByPatternServiceDays + public void setGraphQLLimitByPatternServiceDates( + GraphQLServiceDateFilterInput limitByPatternServiceDates ) { - this.limitByPatternServiceDays = limitByPatternServiceDays; + this.limitByPatternServiceDates = limitByPatternServiceDates; } public void setGraphQLName(String name) { @@ -2768,21 +2768,21 @@ public void setGraphQLLanguage(String language) { public static class GraphQLRoutePatternsArgs { - private GraphQLServiceDayFilterInput serviceDays; + private GraphQLServiceDateFilterInput serviceDates; public GraphQLRoutePatternsArgs(Map args) { if (args != null) { - this.serviceDays = - new GraphQLServiceDayFilterInput((Map) args.get("serviceDays")); + this.serviceDates = + new GraphQLServiceDateFilterInput((Map) args.get("serviceDates")); } } - public GraphQLServiceDayFilterInput getGraphQLServiceDays() { - return this.serviceDays; + public GraphQLServiceDateFilterInput getGraphQLServiceDates() { + return this.serviceDates; } - public void setGraphQLServiceDays(GraphQLServiceDayFilterInput serviceDays) { - this.serviceDays = serviceDays; + public void setGraphQLServiceDates(GraphQLServiceDateFilterInput serviceDates) { + this.serviceDates = serviceDates; } } @@ -2807,12 +2807,12 @@ public enum GraphQLRoutingErrorCode { WALKING_BETTER_THAN_TRANSIT, } - public static class GraphQLServiceDayFilterInput { + public static class GraphQLServiceDateFilterInput { private java.time.LocalDate end; private java.time.LocalDate start; - public GraphQLServiceDayFilterInput(Map args) { + public GraphQLServiceDateFilterInput(Map args) { if (args != null) { this.end = (java.time.LocalDate) args.get("end"); this.start = (java.time.LocalDate) args.get("start"); diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index c4ed7ff40d3..c495b821965 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1663,6 +1663,11 @@ type RideHailingEstimate { productName: String } +""" +An ISO-8601-formatted local date, i.e. `2024-05-24` for the 24th of May, 2024. +""" +scalar LocalDate + """ An ISO-8601-formatted datetime with offset, i.e. `2023-06-13T14:30+03:00` for 2:30pm on June 13th 2023 at Helsinki's offset from UTC at that time. @@ -2542,10 +2547,18 @@ enum Qualifier { HAIL } -scalar LocalDate -input ServiceDayFilterInput { +"""Filters entity by their service dates.""" +input ServiceDateFilterInput { + """ + Inclusive start date of the filter. If `null` this means that no `start` filter is applied and all + dates that are before or on `end` are selected. + """ start: LocalDate + """ + Inclusive end date of the filter. If `null` this means that no end filter is applied and all + entities that are after or on `start` are selected. + """ end: LocalDate } @@ -2708,7 +2721,8 @@ type QueryType { """Only include routes, which use one of these modes""" transportModes: [Mode] - limitByPatternServiceDays: ServiceDayFilterInput + """Only include routes whose pattern operates on at least one service date specified by this filter.""" + limitByPatternServiceDates: ServiceDateFilterInput ): [Route] """ @@ -3431,8 +3445,8 @@ type Route implements Node { """List of patterns which operate on this route""" patterns( - "Filter patterns by the service day they operate on." - serviceDays: ServiceDayFilterInput + "Filter patterns by the service dates they operate on." + serviceDates: ServiceDateFilterInput ): [Pattern] """List of stops on this route""" diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java similarity index 90% rename from src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java rename to src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index 32f6a78b2ee..2ef9b22f335 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDaysFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -4,8 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.opentripplanner.apis.gtfs.PatternByServiceDaysFilterTest.FilterExpectation.NOT_REMOVED; -import static org.opentripplanner.apis.gtfs.PatternByServiceDaysFilterTest.FilterExpectation.REMOVED; +import static org.opentripplanner.apis.gtfs.PatternByServiceDatesFilterTest.FilterExpectation.NOT_REMOVED; +import static org.opentripplanner.apis.gtfs.PatternByServiceDatesFilterTest.FilterExpectation.REMOVED; import java.time.LocalDate; import java.util.Collection; @@ -28,7 +28,7 @@ import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; -class PatternByServiceDaysFilterTest { +class PatternByServiceDatesFilterTest { private static final TransitService EMPTY_SERVICE = new DefaultTransitService(new TransitModel()); private static final Route ROUTE_1 = TransitModelForTest.route("1").build(); @@ -71,7 +71,7 @@ static List invalidRange() { void invalidRange(LocalDate start, LocalDate end) { assertThrows( IllegalArgumentException.class, - () -> new PatternByServiceDaysFilter(EMPTY_SERVICE, start, end) + () -> new PatternByServiceDatesFilter(EMPTY_SERVICE, start, end) ); } @@ -88,7 +88,7 @@ static List validRange() { @ParameterizedTest @MethodSource("validRange") void validRange(LocalDate start, LocalDate end) { - assertDoesNotThrow(() -> new PatternByServiceDaysFilter(EMPTY_SERVICE, start, end)); + assertDoesNotThrow(() -> new PatternByServiceDatesFilter(EMPTY_SERVICE, start, end)); } static List ranges() { @@ -110,7 +110,7 @@ static List ranges() { @MethodSource("ranges") void filterPatterns(LocalDate start, LocalDate end, FilterExpectation expectation) { var service = mockService(); - var filter = new PatternByServiceDaysFilter(service, start, end); + var filter = new PatternByServiceDatesFilter(service, start, end); var filterInput = List.of(PATTERN_1); var filterOutput = filter.filterPatterns(filterInput); @@ -126,7 +126,7 @@ void filterPatterns(LocalDate start, LocalDate end, FilterExpectation expectatio @MethodSource("ranges") void filterRoutes(LocalDate start, LocalDate end, FilterExpectation expectation) { var service = mockService(); - var filter = new PatternByServiceDaysFilter(service, start, end); + var filter = new PatternByServiceDatesFilter(service, start, end); var filterInput = List.of(ROUTE_1); var filterOutput = filter.filterRoutes(filterInput.stream()); From 3f0d492ebc7081f33540f7e487bd4826b9ea7d9c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 24 May 2024 17:38:08 +0200 Subject: [PATCH 007/148] Add example for LocalDate --- magidoc.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/magidoc.mjs b/magidoc.mjs index 4fea5e4e127..73f1a4d8343 100644 --- a/magidoc.mjs +++ b/magidoc.mjs @@ -36,7 +36,8 @@ To learn how to deactivate it, read the queryGenerationFactories: { 'Polyline': '<>', 'GeoJson': '<>', - 'OffsetDateTime': '2024-02-05T18:04:23+01:00' + 'OffsetDateTime': '2024-02-05T18:04:23+01:00', + 'LocalDate': '2024-05-24', }, } }, From 28b078d0ed817067c790f124a2b9e3b76e8786b0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 24 May 2024 17:39:16 +0200 Subject: [PATCH 008/148] Update test --- .../opentripplanner/apis/gtfs/queries/routes-extended.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql index 5da7db77286..0251245d183 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql @@ -10,7 +10,7 @@ mode sortOrder bikesAllowed - patterns(serviceDays: {start: "2024-05-23" end: "2024-05-30"}) { + patterns(serviceDates: {start: "2024-05-23" end: "2024-05-30"}) { name } } From e6971b4d786e3c94b02e52aca1512c81079ac887 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 27 May 2024 13:02:49 +0200 Subject: [PATCH 009/148] Add more test cases --- .../gtfs/PatternByServiceDatesFilter.java | 2 +- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 4 ++- .../apis/gtfs/datafetchers/RouteImpl.java | 2 +- .../gtfs/PatternByServiceDatesFilterTest.java | 30 +++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index 313c44f2647..c7a0314fbb5 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -91,7 +91,7 @@ private boolean hasServicesOnDate(TripPattern pattern) { }); } - public static boolean hasServiceDayFilter(GraphQLServiceDateFilterInput serviceDays) { + public static boolean hasServiceDateFilter(GraphQLServiceDateFilterInput serviceDays) { return ( serviceDays != null && (serviceDays.getGraphQLStart() != null || serviceDays.getGraphQLEnd() != null) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index d5033af7da7..0936d98cf74 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -607,7 +607,9 @@ public DataFetcher> routes() { } if ( - PatternByServiceDatesFilter.hasServiceDayFilter(args.getGraphQLLimitByPatternServiceDates()) + PatternByServiceDatesFilter.hasServiceDateFilter( + args.getGraphQLLimitByPatternServiceDates() + ) ) { var filter = new PatternByServiceDatesFilter( transitService, diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java index d9b76ab72ef..ac4a485706b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java @@ -181,7 +181,7 @@ public DataFetcher> patterns() { var args = new GraphQLTypes.GraphQLRoutePatternsArgs(environment.getArguments()); - if (PatternByServiceDatesFilter.hasServiceDayFilter(args.getGraphQLServiceDates())) { + if (PatternByServiceDatesFilter.hasServiceDateFilter(args.getGraphQLServiceDates())) { var filter = new PatternByServiceDatesFilter(transitService, args.getGraphQLServiceDates()); return filter.filterPatterns(patterns); } else { diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index 2ef9b22f335..09582d67d3c 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -3,17 +3,22 @@ import static java.time.LocalDate.parse; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.apis.gtfs.PatternByServiceDatesFilterTest.FilterExpectation.NOT_REMOVED; import static org.opentripplanner.apis.gtfs.PatternByServiceDatesFilterTest.FilterExpectation.REMOVED; import java.time.LocalDate; +import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Set; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLServiceDateFilterInput; import org.opentripplanner.model.calendar.CalendarService; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -37,6 +42,7 @@ class PatternByServiceDatesFilterTest { private static final RegularStop STOP_1 = MODEL.stop("1").build(); private static final StopPattern STOP_PATTERN = TransitModelForTest.stopPattern(STOP_1, STOP_1); private static final TripPattern PATTERN_1 = pattern(); + private static final LocalDate DATE = LocalDate.parse("2024-05-27"); enum FilterExpectation { REMOVED, @@ -166,4 +172,28 @@ public Set getServiceIdsOnDate(LocalDate date) { } }; } + + public static List noFilterCases() { + var list = new ArrayList(); + list.add(null); + list.add(new GraphQLServiceDateFilterInput(Map.of())); + return list; + } + + @ParameterizedTest + @MethodSource("noFilterCases") + void hasNoServiceDateFilter(GraphQLServiceDateFilterInput input) { + assertFalse(PatternByServiceDatesFilter.hasServiceDateFilter(input)); + } + + public static List> hasFilterCases() { + return List.of(Map.of("start", DATE), Map.of("end", DATE), Map.of("start", DATE, "end", DATE)); + } + + @ParameterizedTest + @MethodSource("hasFilterCases") + void hasServiceDateFilter(Map params) { + var input = new GraphQLServiceDateFilterInput(params); + assertTrue(PatternByServiceDatesFilter.hasServiceDateFilter(input)); + } } From 244da3f314334ee22803325053765208f863d356 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 27 May 2024 14:36:36 +0200 Subject: [PATCH 010/148] Update test case for edge case --- .../apis/gtfs/PatternByServiceDatesFilterTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index 09582d67d3c..7e0cae144ed 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -103,6 +103,7 @@ static List ranges() { Arguments.of(null, parse("2024-05-03"), NOT_REMOVED), Arguments.of(null, parse("2024-05-01"), NOT_REMOVED), Arguments.of(parse("2024-05-03"), null, NOT_REMOVED), + Arguments.of(parse("2024-05-01"), null, NOT_REMOVED), Arguments.of(parse("2024-05-02"), parse("2024-05-02"), REMOVED), Arguments.of(parse("2024-05-02"), parse("2024-05-03"), REMOVED), Arguments.of(parse("2025-01-01"), null, REMOVED), From 994d0b5f36bf2ea51a0ec0ac28792975a1388fc3 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 4 Jun 2024 15:26:01 +0200 Subject: [PATCH 011/148] Be strict about accepting RFC3339 --- .../framework/graphql/scalar/DateScalarFactory.java | 10 +--------- .../graphql/scalar/DateScalarFactoryTest.java | 4 ++-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java b/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java index c19add1e0cb..9b22357f38a 100644 --- a/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java +++ b/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java @@ -8,7 +8,6 @@ import graphql.schema.GraphQLScalarType; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; public class DateScalarFactory { @@ -16,14 +15,7 @@ public class DateScalarFactory { private static final String DOCUMENTATION = "Local date using the ISO 8601 format: `YYYY-MM-DD`. Example: `2020-05-17`."; - private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder() - .optionalStart() - .append(DateTimeFormatter.ofPattern("yyyyMMdd")) - .optionalEnd() - .optionalStart() - .append(DateTimeFormatter.ISO_LOCAL_DATE) - .optionalStart() - .toFormatter(); + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; private DateScalarFactory() {} diff --git a/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java b/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java index afc86cdf9ba..d7a3023ab2b 100644 --- a/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java +++ b/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java @@ -15,7 +15,7 @@ class DateScalarFactoryTest { private static final GraphQLScalarType SCALAR = DateScalarFactory.createDateScalar("Date"); @ParameterizedTest - @ValueSource(strings = { "2024-05-23", "20240523" }) + @ValueSource(strings = { "2024-05-23" }) void parse(String input) { var result = SCALAR.getCoercing().parseValue(input); assertInstanceOf(LocalDate.class, result); @@ -24,7 +24,7 @@ void parse(String input) { } @ParameterizedTest - @ValueSource(strings = { "2024-05", "2024", "2024-99-04", "202405-23" }) + @ValueSource(strings = { "2024-05", "2024", "2024-99-04", "202405-23", "20240523" }) void failParsing(String input) { assertThrows(CoercingParseValueException.class, () -> SCALAR.getCoercing().parseValue(input)); } From 782964a500117b0d96ac614e7f6306bee779416e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 4 Jun 2024 16:06:45 +0200 Subject: [PATCH 012/148] Update documenation about service date --- .../opentripplanner/apis/gtfs/schema.graphqls | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index c495b821965..b21d7c863a4 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2548,7 +2548,13 @@ enum Qualifier { } -"""Filters entity by their service dates.""" +""" +Filters entity by their service dates. + +**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. +""" input ServiceDateFilterInput { """ Inclusive start date of the filter. If `null` this means that no `start` filter is applied and all @@ -2721,7 +2727,13 @@ type QueryType { """Only include routes, which use one of these modes""" transportModes: [Mode] - """Only include routes whose pattern operates on at least one service date specified by this filter.""" + """ + Only include routes whose pattern operates on at least one service date specified by this filter. + + **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. + """ limitByPatternServiceDates: ServiceDateFilterInput ): [Route] @@ -3445,7 +3457,13 @@ type Route implements Node { """List of patterns which operate on this route""" patterns( - "Filter patterns by the service dates they operate on." + """ + Filter patterns by the service dates they operate on. + + **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. + """ serviceDates: ServiceDateFilterInput ): [Pattern] From 1734b8a61334f4bcf3de140be163fd3f93910296 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 10 Jun 2024 14:21:39 +0200 Subject: [PATCH 013/148] Clarify which date formats are allowed --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index b21d7c863a4..3d1d87a5233 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1665,8 +1665,10 @@ type RideHailingEstimate { """ An ISO-8601-formatted local date, i.e. `2024-05-24` for the 24th of May, 2024. + +ISO-8601 allows many different date formats, however only the most common one - `yyyy-MM-dd` - is accepted. """ -scalar LocalDate +scalar LocalDate @specifiedBy(url: "https://www.iso.org/standard/70907.html") """ An ISO-8601-formatted datetime with offset, i.e. `2023-06-13T14:30+03:00` for 2:30pm on June 13th 2023 at Helsinki's offset from UTC at that time. From 9cfc1d7b8f2bd8f3a36504e70a02465705376cd3 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 10 Jun 2024 14:30:30 +0200 Subject: [PATCH 014/148] Allow overriding of description --- .../opentripplanner/apis/gtfs/GraphQLScalars.java | 3 ++- .../apis/transmodel/support/GqlUtil.java | 2 +- .../graphql/scalar/DateScalarFactory.java | 14 +++++++++++--- .../graphql/scalar/DateScalarFactoryTest.java | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index a3034cad273..a04e24e391b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -113,7 +113,8 @@ public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralExce .build(); public static final GraphQLScalarType LOCAL_DATE_SCALAR = DateScalarFactory.createDateScalar( - "LocalDate" + "LocalDate", + null ); public static final GraphQLScalarType GEOJSON_SCALAR = GraphQLScalarType diff --git a/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java b/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java index 05a8eaa45fa..e94253cf33f 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java @@ -45,7 +45,7 @@ public class GqlUtil { public GqlUtil(ZoneId timeZone) { this.dateTimeScalar = DateTimeScalarFactory.createMillisecondsSinceEpochAsDateTimeStringScalar(timeZone); - this.dateScalar = DateScalarFactory.createDateScalar("Date"); + this.dateScalar = DateScalarFactory.createDateScalar("Date", DateScalarFactory.DESCRIPTION); this.doubleFunctionScalar = DoubleFunctionFactory.createDoubleFunctionScalar(); this.localTimeScalar = LocalTimeScalarFactory.createLocalTimeScalar(); this.timeScalar = TimeScalarFactory.createSecondsSinceMidnightAsTimeObject(); diff --git a/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java b/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java index 9b22357f38a..590dea013a3 100644 --- a/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java +++ b/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java @@ -9,21 +9,29 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import javax.annotation.Nullable; public class DateScalarFactory { - private static final String DOCUMENTATION = + public static final String DESCRIPTION = "Local date using the ISO 8601 format: `YYYY-MM-DD`. Example: `2020-05-17`."; private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; private DateScalarFactory() {} - public static GraphQLScalarType createDateScalar(String scalarName) { + /** + * @param description Nullable description that allows caller to pass in null which leads to the + * description from schema.graphqls to be used. + */ + public static GraphQLScalarType createDateScalar( + String scalarName, + @Nullable String description + ) { return GraphQLScalarType .newScalar() .name(scalarName) - .description(DOCUMENTATION) + .description(description) .coercing( new Coercing() { @Override diff --git a/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java b/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java index d7a3023ab2b..02e4b37d7d6 100644 --- a/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java +++ b/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java @@ -12,7 +12,7 @@ class DateScalarFactoryTest { - private static final GraphQLScalarType SCALAR = DateScalarFactory.createDateScalar("Date"); + private static final GraphQLScalarType SCALAR = DateScalarFactory.createDateScalar("Date", null); @ParameterizedTest @ValueSource(strings = { "2024-05-23" }) From 591405c5b9f8646ee88fc532889028582ea91ec8 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 10 Jun 2024 14:43:47 +0200 Subject: [PATCH 015/148] Instantiate real service instead of mocking it --- .../gtfs/PatternByServiceDatesFilterTest.java | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index 7e0cae144ed..adf7213a5ac 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.apis.gtfs.PatternByServiceDatesFilterTest.FilterExpectation.NOT_REMOVED; import static org.opentripplanner.apis.gtfs.PatternByServiceDatesFilterTest.FilterExpectation.REMOVED; +import static org.opentripplanner.transit.model._data.TransitModelForTest.id; import java.time.LocalDate; import java.util.ArrayList; @@ -20,6 +21,8 @@ import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLServiceDateFilterInput; import org.opentripplanner.model.calendar.CalendarService; +import org.opentripplanner.model.calendar.CalendarServiceData; +import org.opentripplanner.model.calendar.impl.CalendarServiceImpl; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; @@ -37,7 +40,12 @@ class PatternByServiceDatesFilterTest { private static final TransitService EMPTY_SERVICE = new DefaultTransitService(new TransitModel()); private static final Route ROUTE_1 = TransitModelForTest.route("1").build(); - private static final Trip TRIP = TransitModelForTest.trip("t1").withRoute(ROUTE_1).build(); + private static final FeedScopedId SERVICE_ID = id("service"); + private static final Trip TRIP = TransitModelForTest + .trip("t1") + .withRoute(ROUTE_1) + .withServiceId(SERVICE_ID) + .build(); private static final TransitModelForTest MODEL = new TransitModelForTest(StopModel.of()); private static final RegularStop STOP_1 = MODEL.stop("1").build(); private static final StopPattern STOP_PATTERN = TransitModelForTest.stopPattern(STOP_1, STOP_1); @@ -146,6 +154,9 @@ void filterRoutes(LocalDate start, LocalDate end, FilterExpectation expectation) } private static TransitService mockService() { + var data = new CalendarServiceData(); + data.putServiceDatesForServiceId(SERVICE_ID, List.of(parse("2024-05-01"), parse("2024-06-01"))); + var service = new CalendarServiceImpl(data); return new DefaultTransitService(new TransitModel()) { @Override public Collection getPatternsForRoute(Route route) { @@ -154,22 +165,7 @@ public Collection getPatternsForRoute(Route route) { @Override public CalendarService getCalendarService() { - return new CalendarService() { - @Override - public Set getServiceIds() { - return Set.of(); - } - - @Override - public Set getServiceDatesForServiceId(FeedScopedId serviceId) { - return Set.of(parse("2024-05-01"), parse("2024-06-01")); - } - - @Override - public Set getServiceIdsOnDate(LocalDate date) { - return Set.of(); - } - }; + return service; } }; } From 5d29a448b96537f3d74da4f67be57832b60737f2 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 10 Jun 2024 14:58:04 +0200 Subject: [PATCH 016/148] Rename argument provider methods --- .../apis/gtfs/PatternByServiceDatesFilterTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index adf7213a5ac..4e5779d41f6 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -73,7 +73,7 @@ private static TripPattern pattern() { return pattern; } - static List invalidRange() { + static List invalidRangeCases() { return List.of( Arguments.of(parse("2024-05-02"), parse("2024-05-01")), Arguments.of(parse("2024-05-03"), parse("2024-05-01")) @@ -81,7 +81,7 @@ static List invalidRange() { } @ParameterizedTest - @MethodSource("invalidRange") + @MethodSource("invalidRangeCases") void invalidRange(LocalDate start, LocalDate end) { assertThrows( IllegalArgumentException.class, @@ -89,7 +89,7 @@ void invalidRange(LocalDate start, LocalDate end) { ); } - static List validRange() { + static List validRangeCases() { return List.of( Arguments.of(parse("2024-05-02"), parse("2024-05-02")), Arguments.of(parse("2024-05-02"), parse("2024-05-03")), @@ -100,7 +100,7 @@ static List validRange() { } @ParameterizedTest - @MethodSource("validRange") + @MethodSource("validRangeCases") void validRange(LocalDate start, LocalDate end) { assertDoesNotThrow(() -> new PatternByServiceDatesFilter(EMPTY_SERVICE, start, end)); } From b618e716e073516f93beee42f4ee8916557815a3 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 11 Jun 2024 15:24:43 +0200 Subject: [PATCH 017/148] Pass in functions rather than the whole transit service --- .../gtfs/PatternByServiceDatesFilter.java | 33 ++++++++------ .../apis/gtfs/datafetchers/QueryTypeImpl.java | 4 +- .../apis/gtfs/datafetchers/RouteImpl.java | 2 +- .../gtfs/PatternByServiceDatesFilterTest.java | 43 ++++++------------- 4 files changed, 35 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index c7a0314fbb5..ee9b206bc09 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -3,11 +3,13 @@ import java.time.LocalDate; import java.util.Collection; import java.util.Objects; +import java.util.function.Function; import java.util.stream.Stream; import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLServiceDateFilterInput; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.service.TransitService; /** @@ -16,26 +18,26 @@ */ public class PatternByServiceDatesFilter { - private final TransitService transitService; private final LocalDate startInclusive; private final LocalDate endInclusive; + private final Function> getPatternsForRoute; + private final Function> getServiceDatesForTrip; /** * - * @param transitService * @param startInclusive The inclusive start date to check the patterns for. If null then no start * date is defined and this will therefore match all dates. * @param endInclusive The inclusive end date to check the patterns for. If null then no end date * is defined this will therefore match all dates. */ PatternByServiceDatesFilter( - TransitService transitService, @Nullable LocalDate startInclusive, - @Nullable LocalDate endInclusive + @Nullable LocalDate endInclusive, + Function> getPatternsForRoute, + Function> getServiceDatesForTrip ) { - Objects.requireNonNull(transitService); - this.transitService = transitService; - + this.getPatternsForRoute = Objects.requireNonNull(getPatternsForRoute); + this.getServiceDatesForTrip = Objects.requireNonNull(getServiceDatesForTrip); // optional this.startInclusive = startInclusive; this.endInclusive = endInclusive; @@ -46,10 +48,15 @@ public class PatternByServiceDatesFilter { } public PatternByServiceDatesFilter( - TransitService transitService, - GraphQLServiceDateFilterInput filterInput + GraphQLServiceDateFilterInput filterInput, + TransitService transitService ) { - this(transitService, filterInput.getGraphQLStart(), filterInput.getGraphQLEnd()); + this( + filterInput.getGraphQLStart(), + filterInput.getGraphQLEnd(), + transitService::getPatternsForRoute, + trip -> transitService.getCalendarService().getServiceDatesForServiceId(trip.getServiceId()) + ); } /** @@ -66,7 +73,7 @@ public Collection filterPatterns(Collection tripPatter public Collection filterRoutes(Stream routeStream) { return routeStream .filter(r -> { - var patterns = transitService.getPatternsForRoute(r); + var patterns = getPatternsForRoute.apply(r); return !this.filterPatterns(patterns).isEmpty(); }) .toList(); @@ -76,9 +83,7 @@ private boolean hasServicesOnDate(TripPattern pattern) { return pattern .scheduledTripsAsStream() .anyMatch(trip -> { - var dates = transitService - .getCalendarService() - .getServiceDatesForServiceId(trip.getServiceId()); + var dates = getServiceDatesForTrip.apply(trip); return dates .stream() diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 0936d98cf74..6aafa780bf7 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -612,8 +612,8 @@ public DataFetcher> routes() { ) ) { var filter = new PatternByServiceDatesFilter( - transitService, - args.getGraphQLLimitByPatternServiceDates() + args.getGraphQLLimitByPatternServiceDates(), + transitService ); routeStream = filter.filterRoutes(routeStream).stream(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java index ac4a485706b..596936dfdb5 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java @@ -182,7 +182,7 @@ public DataFetcher> patterns() { var args = new GraphQLTypes.GraphQLRoutePatternsArgs(environment.getArguments()); if (PatternByServiceDatesFilter.hasServiceDateFilter(args.getGraphQLServiceDates())) { - var filter = new PatternByServiceDatesFilter(transitService, args.getGraphQLServiceDates()); + var filter = new PatternByServiceDatesFilter(args.getGraphQLServiceDates(), transitService); return filter.filterPatterns(patterns); } else { return patterns; diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index 4e5779d41f6..be67f3c90eb 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -12,17 +12,12 @@ import java.time.LocalDate; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Set; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLServiceDateFilterInput; -import org.opentripplanner.model.calendar.CalendarService; -import org.opentripplanner.model.calendar.CalendarServiceData; -import org.opentripplanner.model.calendar.impl.CalendarServiceImpl; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; @@ -31,14 +26,10 @@ import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.timetable.ScheduledTripTimes; import org.opentripplanner.transit.model.timetable.Trip; -import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.StopModel; -import org.opentripplanner.transit.service.TransitModel; -import org.opentripplanner.transit.service.TransitService; class PatternByServiceDatesFilterTest { - private static final TransitService EMPTY_SERVICE = new DefaultTransitService(new TransitModel()); private static final Route ROUTE_1 = TransitModelForTest.route("1").build(); private static final FeedScopedId SERVICE_ID = id("service"); private static final Trip TRIP = TransitModelForTest @@ -85,7 +76,7 @@ static List invalidRangeCases() { void invalidRange(LocalDate start, LocalDate end) { assertThrows( IllegalArgumentException.class, - () -> new PatternByServiceDatesFilter(EMPTY_SERVICE, start, end) + () -> new PatternByServiceDatesFilter(start, end, r -> List.of(), d -> List.of()) ); } @@ -102,7 +93,9 @@ static List validRangeCases() { @ParameterizedTest @MethodSource("validRangeCases") void validRange(LocalDate start, LocalDate end) { - assertDoesNotThrow(() -> new PatternByServiceDatesFilter(EMPTY_SERVICE, start, end)); + assertDoesNotThrow(() -> + new PatternByServiceDatesFilter(start, end, r -> List.of(), d -> List.of()) + ); } static List ranges() { @@ -124,8 +117,7 @@ static List ranges() { @ParameterizedTest @MethodSource("ranges") void filterPatterns(LocalDate start, LocalDate end, FilterExpectation expectation) { - var service = mockService(); - var filter = new PatternByServiceDatesFilter(service, start, end); + var filter = mockFilter(start, end); var filterInput = List.of(PATTERN_1); var filterOutput = filter.filterPatterns(filterInput); @@ -140,8 +132,7 @@ void filterPatterns(LocalDate start, LocalDate end, FilterExpectation expectatio @ParameterizedTest @MethodSource("ranges") void filterRoutes(LocalDate start, LocalDate end, FilterExpectation expectation) { - var service = mockService(); - var filter = new PatternByServiceDatesFilter(service, start, end); + var filter = mockFilter(start, end); var filterInput = List.of(ROUTE_1); var filterOutput = filter.filterRoutes(filterInput.stream()); @@ -153,21 +144,13 @@ void filterRoutes(LocalDate start, LocalDate end, FilterExpectation expectation) } } - private static TransitService mockService() { - var data = new CalendarServiceData(); - data.putServiceDatesForServiceId(SERVICE_ID, List.of(parse("2024-05-01"), parse("2024-06-01"))); - var service = new CalendarServiceImpl(data); - return new DefaultTransitService(new TransitModel()) { - @Override - public Collection getPatternsForRoute(Route route) { - return Set.of(PATTERN_1); - } - - @Override - public CalendarService getCalendarService() { - return service; - } - }; + private static PatternByServiceDatesFilter mockFilter(LocalDate start, LocalDate end) { + return new PatternByServiceDatesFilter( + start, + end, + route -> List.of(PATTERN_1), + trip -> List.of(parse("2024-05-01"), parse("2024-06-01")) + ); } public static List noFilterCases() { From 4ad42e77ac33da5de3d85adc04a7c391af24757b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 11 Jun 2024 15:32:57 +0200 Subject: [PATCH 018/148] Resolve merge conflicts --- .../apis/gtfs/generated/GraphQLTypes.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) 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 3c187ca3bbe..fee9eb96702 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -3448,6 +3448,7 @@ public static class GraphQLQueryTypeRoutesArgs { private List feeds; private List ids; + private GraphQLServiceDateFilterInput limitByPatternServiceDates; private String name; private List transportModes; @@ -3455,6 +3456,10 @@ public GraphQLQueryTypeRoutesArgs(Map args) { if (args != null) { this.feeds = (List) args.get("feeds"); this.ids = (List) args.get("ids"); + this.limitByPatternServiceDates = + new GraphQLServiceDateFilterInput( + (Map) args.get("limitByPatternServiceDates") + ); this.name = (String) args.get("name"); if (args.get("transportModes") != null) { this.transportModes = @@ -3474,6 +3479,10 @@ public List getGraphQLIds() { return this.ids; } + public GraphQLServiceDateFilterInput getGraphQLLimitByPatternServiceDates() { + return this.limitByPatternServiceDates; + } + public String getGraphQLName() { return this.name; } @@ -3490,6 +3499,12 @@ public void setGraphQLIds(List ids) { this.ids = ids; } + public void setGraphQLLimitByPatternServiceDates( + GraphQLServiceDateFilterInput limitByPatternServiceDates + ) { + this.limitByPatternServiceDates = limitByPatternServiceDates; + } + public void setGraphQLName(String name) { this.name = name; } @@ -3933,6 +3948,26 @@ public void setGraphQLLanguage(String language) { } } + public static class GraphQLRoutePatternsArgs { + + private GraphQLServiceDateFilterInput serviceDates; + + public GraphQLRoutePatternsArgs(Map args) { + if (args != null) { + this.serviceDates = + new GraphQLServiceDateFilterInput((Map) args.get("serviceDates")); + } + } + + public GraphQLServiceDateFilterInput getGraphQLServiceDates() { + return this.serviceDates; + } + + public void setGraphQLServiceDates(GraphQLServiceDateFilterInput serviceDates) { + this.serviceDates = serviceDates; + } + } + /** Entities that are relevant for routes that can contain alerts */ public enum GraphQLRouteAlertType { AGENCY, @@ -4094,6 +4129,35 @@ public void setGraphQLDestinationScooterPolicy( } } + public static class GraphQLServiceDateFilterInput { + + private java.time.LocalDate end; + private java.time.LocalDate start; + + public GraphQLServiceDateFilterInput(Map args) { + if (args != null) { + this.end = (java.time.LocalDate) args.get("end"); + this.start = (java.time.LocalDate) args.get("start"); + } + } + + public java.time.LocalDate getGraphQLEnd() { + return this.end; + } + + public java.time.LocalDate getGraphQLStart() { + return this.start; + } + + public void setGraphQLEnd(java.time.LocalDate end) { + this.end = end; + } + + public void setGraphQLStart(java.time.LocalDate start) { + this.start = start; + } + } + public static class GraphQLStopAlertsArgs { private List types; From 3c3bd18df7264042cb5059c2845b98013a714ecb Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 11 Jun 2024 15:45:47 +0200 Subject: [PATCH 019/148] Throw exception if both start/end are null --- .../apis/gtfs/PatternByServiceDatesFilter.java | 6 +++++- .../apis/gtfs/PatternByServiceDatesFilterTest.java | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index ee9b206bc09..0867e6ead54 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -42,7 +42,11 @@ public class PatternByServiceDatesFilter { this.startInclusive = startInclusive; this.endInclusive = endInclusive; - if (startInclusive != null && endInclusive != null && startInclusive.isAfter(endInclusive)) { + if (startInclusive == null && endInclusive == null) { + throw new IllegalArgumentException("startInclusive and endInclusive cannot be both null"); + } else if ( + startInclusive != null && endInclusive != null && startInclusive.isAfter(endInclusive) + ) { throw new IllegalArgumentException("start must be before end"); } } diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index be67f3c90eb..b9ad1c4973a 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -66,6 +66,7 @@ private static TripPattern pattern() { static List invalidRangeCases() { return List.of( + Arguments.of(null, null), Arguments.of(parse("2024-05-02"), parse("2024-05-01")), Arguments.of(parse("2024-05-03"), parse("2024-05-01")) ); @@ -85,8 +86,7 @@ static List validRangeCases() { Arguments.of(parse("2024-05-02"), parse("2024-05-02")), Arguments.of(parse("2024-05-02"), parse("2024-05-03")), Arguments.of(null, parse("2024-05-03")), - Arguments.of(parse("2024-05-03"), null), - Arguments.of(null, null) + Arguments.of(parse("2024-05-03"), null) ); } @@ -100,7 +100,6 @@ void validRange(LocalDate start, LocalDate end) { static List ranges() { return List.of( - Arguments.of(null, null, NOT_REMOVED), Arguments.of(null, parse("2024-05-03"), NOT_REMOVED), Arguments.of(null, parse("2024-05-01"), NOT_REMOVED), Arguments.of(parse("2024-05-03"), null, NOT_REMOVED), From 0a8d03cf8cbc2b1a3a185a9b94a002f2812403d4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 11 Jun 2024 15:54:10 +0200 Subject: [PATCH 020/148] Rename and deprecate --- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 11 ++------ .../apis/gtfs/generated/GraphQLTypes.java | 27 +++++++++---------- .../opentripplanner/apis/gtfs/schema.graphqls | 4 +-- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 6713f790149..7a644da4be8 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -611,15 +611,8 @@ public DataFetcher> routes() { ); } - if ( - PatternByServiceDatesFilter.hasServiceDateFilter( - args.getGraphQLLimitByPatternServiceDates() - ) - ) { - var filter = new PatternByServiceDatesFilter( - args.getGraphQLLimitByPatternServiceDates(), - transitService - ); + if (PatternByServiceDatesFilter.hasServiceDateFilter(args.getGraphQLServiceDates())) { + var filter = new PatternByServiceDatesFilter(args.getGraphQLServiceDates(), transitService); routeStream = filter.filterRoutes(routeStream).stream(); } return routeStream.toList(); 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 fee9eb96702..9f6a237f7d9 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; @@ -3448,19 +3449,17 @@ public static class GraphQLQueryTypeRoutesArgs { private List feeds; private List ids; - private GraphQLServiceDateFilterInput limitByPatternServiceDates; private String name; + private GraphQLServiceDateFilterInput serviceDates; private List transportModes; public GraphQLQueryTypeRoutesArgs(Map args) { if (args != null) { this.feeds = (List) args.get("feeds"); this.ids = (List) args.get("ids"); - this.limitByPatternServiceDates = - new GraphQLServiceDateFilterInput( - (Map) args.get("limitByPatternServiceDates") - ); this.name = (String) args.get("name"); + this.serviceDates = + new GraphQLServiceDateFilterInput((Map) args.get("serviceDates")); if (args.get("transportModes") != null) { this.transportModes = ((List) args.get("transportModes")).stream() @@ -3479,14 +3478,14 @@ public List getGraphQLIds() { return this.ids; } - public GraphQLServiceDateFilterInput getGraphQLLimitByPatternServiceDates() { - return this.limitByPatternServiceDates; - } - public String getGraphQLName() { return this.name; } + public GraphQLServiceDateFilterInput getGraphQLServiceDates() { + return this.serviceDates; + } + public List getGraphQLTransportModes() { return this.transportModes; } @@ -3499,16 +3498,14 @@ public void setGraphQLIds(List ids) { this.ids = ids; } - public void setGraphQLLimitByPatternServiceDates( - GraphQLServiceDateFilterInput limitByPatternServiceDates - ) { - this.limitByPatternServiceDates = limitByPatternServiceDates; - } - public void setGraphQLName(String name) { this.name = name; } + public void setGraphQLServiceDates(GraphQLServiceDateFilterInput serviceDates) { + this.serviceDates = serviceDates; + } + public void setGraphQLTransportModes(List transportModes) { this.transportModes = transportModes; } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 64ea183fadc..8ca0ac189cb 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3606,7 +3606,7 @@ type QueryType { """Get all routes""" routes( """Only return routes with these ids""" - ids: [String] + ids: [String] @deprecated(reason: "Since it is hard to reason about the ID filter being combined with others in this resolver, it will be moved to a separate one.") """Only return routes with these feedIds""" feeds: [String] @@ -3624,7 +3624,7 @@ type QueryType { 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. """ - limitByPatternServiceDates: ServiceDateFilterInput + serviceDates: ServiceDateFilterInput ): [Route] """ From baa1a75e2fea4f4b7977151d1e34545f3abed165 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 10 Jun 2024 17:51:58 +0200 Subject: [PATCH 021/148] Create geocoder with Dagger --- .../ext/geocoder/GeocoderResource.java | 17 +---------------- .../ext/geocoder/LuceneIndex.java | 2 ++ .../org/opentripplanner/apis/APIEndpoints.java | 2 -- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java index f5d1f950632..39a6177ef68 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java @@ -3,7 +3,6 @@ import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; @@ -28,21 +27,7 @@ public class GeocoderResource { private final OtpServerRequestContext serverContext; public GeocoderResource(@Context OtpServerRequestContext requestContext) { - serverContext = requestContext; - } - - /** - * This class is only here for backwards-compatibility. It will be removed in the future. - */ - @Path("/routers/{ignoreRouterId}/geocode") - public static class GeocoderResourceOldPath extends GeocoderResource { - - public GeocoderResourceOldPath( - @Context OtpServerRequestContext serverContext, - @PathParam("ignoreRouterId") String ignore - ) { - super(serverContext); - } + luceneIndex = Objects.requireNonNull(requestContext.lucenceIndex()); } /** diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index e0ea8ba36b9..90b17f849de 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -69,6 +69,8 @@ public LuceneIndex(TransitService transitService) { this.transitService = transitService; this.stopClusterMapper = new StopClusterMapper(transitService); + LOG.info("Creating geocoder lucene index"); + this.analyzer = new PerFieldAnalyzerWrapper( new StandardAnalyzer(), diff --git a/src/main/java/org/opentripplanner/apis/APIEndpoints.java b/src/main/java/org/opentripplanner/apis/APIEndpoints.java index fe8db5b3911..b6ad08ea4d9 100644 --- a/src/main/java/org/opentripplanner/apis/APIEndpoints.java +++ b/src/main/java/org/opentripplanner/apis/APIEndpoints.java @@ -61,8 +61,6 @@ private APIEndpoints() { addIfEnabled(SandboxAPIMapboxVectorTilesApi, VectorTilesResource.class); addIfEnabled(SandboxAPIParkAndRideApi, ParkAndRideResource.class); addIfEnabled(SandboxAPIGeocoder, GeocoderResource.class); - // scheduled to be removed and only here for backwards compatibility - addIfEnabled(SandboxAPIGeocoder, GeocoderResource.GeocoderResourceOldPath.class); // scheduled to be removed addIfEnabled(APIBikeRental, BikeRental.class); From f554b8f645c17fe109bd9d7a98a9f1ce2e1ea8ad Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 10 Jun 2024 16:08:53 +0200 Subject: [PATCH 022/148] Add lucene index to OTP server context --- .../ext/geocoder/GeocoderResource.java | 14 +++++++------- .../ext/geocoder/LuceneIndex.java | 13 ------------- .../opentripplanner/routing/graph/Graph.java | 10 ---------- .../standalone/api/OtpServerRequestContext.java | 4 ++++ .../configure/ConstructApplication.java | 2 -- .../configure/ConstructApplicationModule.java | 7 +++++-- .../server/DefaultServerRequestContext.java | 17 ++++++++++++++--- .../org/opentripplanner/TestServerContext.java | 2 +- .../mapping/TripRequestMapperTest.java | 4 +--- .../transit/speed_test/SpeedTest.java | 2 +- 10 files changed, 33 insertions(+), 42 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java index 39a6177ef68..26aabb6bc29 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java @@ -24,10 +24,10 @@ @Produces(MediaType.APPLICATION_JSON) public class GeocoderResource { - private final OtpServerRequestContext serverContext; + private final LuceneIndex luceneIndex; public GeocoderResource(@Context OtpServerRequestContext requestContext) { - luceneIndex = Objects.requireNonNull(requestContext.lucenceIndex()); + luceneIndex = requestContext.lucenceIndex(); } /** @@ -56,7 +56,7 @@ public Response textSearch( @GET @Path("stopClusters") public Response stopClusters(@QueryParam("query") String query) { - var clusters = LuceneIndex.forServer(serverContext).queryStopClusters(query).toList(); + var clusters = luceneIndex.queryStopClusters(query).toList(); return Response.status(Response.Status.OK).entity(clusters).build(); } @@ -81,8 +81,8 @@ private List query( } private Collection queryStopLocations(String query, boolean autocomplete) { - return LuceneIndex - .forServer(serverContext) + return + luceneIndex .queryStopLocations(query, autocomplete) .map(sl -> new SearchResult( @@ -96,8 +96,8 @@ private Collection queryStopLocations(String query, boolean autoco } private Collection queryStations(String query, boolean autocomplete) { - return LuceneIndex - .forServer(serverContext) + return + luceneIndex .queryStopLocationGroups(query, autocomplete) .map(sc -> new SearchResult( diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index 90b17f849de..f7702ad760c 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -42,7 +42,6 @@ import org.apache.lucene.store.ByteBuffersDirectory; import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.framework.i18n.I18NString; -import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.site.StopLocationsGroup; @@ -146,18 +145,6 @@ public LuceneIndex(TransitService transitService) { } } - public static synchronized LuceneIndex forServer(OtpServerRequestContext serverContext) { - var graph = serverContext.graph(); - var existingIndex = graph.getLuceneIndex(); - if (existingIndex != null) { - return existingIndex; - } - - var newIndex = new LuceneIndex(serverContext.transitService()); - graph.setLuceneIndex(newIndex); - return newIndex; - } - public Stream queryStopLocations(String query, boolean autocomplete) { return matchingDocuments(StopLocation.class, query, autocomplete) .map(document -> transitService.getStopLocation(FeedScopedId.parse(document.get(ID)))); diff --git a/src/main/java/org/opentripplanner/routing/graph/Graph.java b/src/main/java/org/opentripplanner/routing/graph/Graph.java index 4b6c9938317..926d524bad2 100644 --- a/src/main/java/org/opentripplanner/routing/graph/Graph.java +++ b/src/main/java/org/opentripplanner/routing/graph/Graph.java @@ -16,7 +16,6 @@ import javax.annotation.Nullable; import org.locationtech.jts.geom.Geometry; import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayParameterBindings; -import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.framework.geometry.CompactElevationProfile; import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.model.calendar.openinghours.OpeningHoursCalendarService; @@ -136,7 +135,6 @@ public class Graph implements Serializable { * creating the data overlay context when routing. */ public DataOverlayParameterBindings dataOverlayParameterBindings; - private LuceneIndex luceneIndex; @Inject public Graph( @@ -384,14 +382,6 @@ public void setFareService(FareService fareService) { this.fareService = fareService; } - public LuceneIndex getLuceneIndex() { - return luceneIndex; - } - - public void setLuceneIndex(LuceneIndex luceneIndex) { - this.luceneIndex = luceneIndex; - } - private void indexIfNotIndexed(StopModel stopModel) { if (streetIndex == null) { index(stopModel); diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index 6552d82770f..3a577611617 100644 --- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -8,6 +8,7 @@ import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext; import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.flex.FlexParameters; +import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.ridehailing.RideHailingService; import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; import org.opentripplanner.framework.application.OTPFeature; @@ -136,4 +137,7 @@ default DataOverlayContext dataOverlayContext(RouteRequest request) { ) ); } + + @Nullable + LuceneIndex lucenceIndex(); } diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index b1bc6888753..db4a4af7fbd 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -5,7 +5,6 @@ import org.opentripplanner.apis.transmodel.TransmodelAPI; import org.opentripplanner.datastore.api.DataSource; import org.opentripplanner.ext.emissions.EmissionsDataModel; -import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.stopconsolidation.StopConsolidationRepository; import org.opentripplanner.framework.application.LogMDCSupport; import org.opentripplanner.framework.application.OTPFeature; @@ -181,7 +180,6 @@ private void setupTransitRoutingServer() { if (OTPFeature.SandboxAPIGeocoder.isOn()) { LOG.info("Creating debug client geocoder lucene index"); - LuceneIndex.forServer(createServerContext()); } } diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java index eb244ce726c..6c830054c49 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java @@ -7,6 +7,7 @@ import javax.annotation.Nullable; import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.emissions.EmissionsService; +import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.interactivelauncher.api.LauncherRequestDecorator; import org.opentripplanner.ext.ridehailing.RideHailingService; import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; @@ -40,7 +41,8 @@ OtpServerRequestContext providesServerContext( StreetLimitationParametersService streetLimitationParametersService, @Nullable TraverseVisitor traverseVisitor, EmissionsService emissionsService, - LauncherRequestDecorator launcherRequestDecorator + LauncherRequestDecorator launcherRequestDecorator, + @Nullable LuceneIndex luceneIndex ) { var defaultRequest = launcherRequestDecorator.intercept(routerConfig.routingRequestDefaults()); @@ -60,7 +62,8 @@ OtpServerRequestContext providesServerContext( rideHailingServices, stopConsolidationService, streetLimitationParametersService, - traverseVisitor + traverseVisitor, + luceneIndex ); } diff --git a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index 7a4ccea9247..6b822506fd9 100644 --- a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -7,6 +7,7 @@ import org.opentripplanner.astar.spi.TraverseVisitor; import org.opentripplanner.ext.emissions.EmissionsService; import org.opentripplanner.ext.flex.FlexParameters; +import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.ridehailing.RideHailingService; import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; import org.opentripplanner.inspector.raster.TileRendererManager; @@ -49,6 +50,7 @@ public class DefaultServerRequestContext implements OtpServerRequestContext { private final EmissionsService emissionsService; private final StopConsolidationService stopConsolidationService; private final StreetLimitationParametersService streetLimitationParametersService; + private final LuceneIndex luceneIndex; /** * Make sure all mutable components are copied/cloned before calling this constructor. @@ -70,7 +72,8 @@ private DefaultServerRequestContext( StopConsolidationService stopConsolidationService, StreetLimitationParametersService streetLimitationParametersService, FlexParameters flexParameters, - TraverseVisitor traverseVisitor + TraverseVisitor traverseVisitor, + @Nullable LuceneIndex luceneIndex ) { this.graph = graph; this.transitService = transitService; @@ -89,6 +92,7 @@ private DefaultServerRequestContext( this.emissionsService = emissionsService; this.stopConsolidationService = stopConsolidationService; this.streetLimitationParametersService = streetLimitationParametersService; + this.luceneIndex = luceneIndex; } /** @@ -110,7 +114,8 @@ public static DefaultServerRequestContext create( List rideHailingServices, @Nullable StopConsolidationService stopConsolidationService, StreetLimitationParametersService streetLimitationParametersService, - @Nullable TraverseVisitor traverseVisitor + @Nullable TraverseVisitor traverseVisitor, + @Nullable LuceneIndex luceneIndex ) { return new DefaultServerRequestContext( graph, @@ -129,7 +134,7 @@ public static DefaultServerRequestContext create( stopConsolidationService, streetLimitationParametersService, flexParameters, - traverseVisitor + traverseVisitor, luceneIndex ); } @@ -235,6 +240,12 @@ public VectorTileConfig vectorTileConfig() { return vectorTileConfig; } + @Nullable + @Override + public LuceneIndex lucenceIndex() { + return luceneIndex; + } + @Override public EmissionsService emissionsService() { return emissionsService; diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index 2f4ded1121d..fe265f30fdc 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -55,7 +55,7 @@ public static OtpServerRequestContext createServerContext( List.of(), null, createStreetLimitationParametersService(), - null + null, null ); creatTransitLayerForRaptor(transitModel, routerConfig.transitTuningConfig()); return context; diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index de6b48c5ef4..d18b94e8a20 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -15,12 +15,10 @@ import io.micrometer.core.instrument.Metrics; import java.time.Duration; import java.time.LocalDate; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Optional; import java.util.function.Function; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; @@ -146,7 +144,7 @@ void setup() { List.of(), null, new DefaultStreetLimitationParametersService(new StreetLimitationParameters()), - null + null, null ), null, transitService diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index e045f49c01d..41fb0311eb5 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -121,7 +121,7 @@ public SpeedTest( List.of(), null, TestServerContext.createStreetLimitationParametersService(), - null + null, null ); // Creating transitLayerForRaptor should be integrated into the TransitModel, but for now // we do it manually here From 9e21b04911d9761f70e080da0bc2ffc5e5e694b1 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 10 Jun 2024 18:14:32 +0200 Subject: [PATCH 023/148] Wire up Geocoder with Dagger --- .../ext/geocoder/GeocoderResource.java | 6 ++-- .../ext/geocoder/LuceneIndex.java | 4 +++ .../geocoder/configure/GeocoderModule.java | 28 +++++++++++++++++++ .../ConstructApplicationFactory.java | 6 ++++ .../server/DefaultServerRequestContext.java | 3 +- .../opentripplanner/TestServerContext.java | 3 +- .../mapping/TripRequestMapperTest.java | 3 +- .../transit/speed_test/SpeedTest.java | 3 +- 8 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java index 26aabb6bc29..c9cd199c557 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java @@ -81,8 +81,7 @@ private List query( } private Collection queryStopLocations(String query, boolean autocomplete) { - return - luceneIndex + return luceneIndex .queryStopLocations(query, autocomplete) .map(sl -> new SearchResult( @@ -96,8 +95,7 @@ private Collection queryStopLocations(String query, boolean autoco } private Collection queryStations(String query, boolean autocomplete) { - return - luceneIndex + return luceneIndex .queryStopLocationGroups(query, autocomplete) .map(sc -> new SearchResult( diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index f7702ad760c..e4de7b4e5c4 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -46,9 +46,13 @@ import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.site.StopLocationsGroup; import org.opentripplanner.transit.service.TransitService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LuceneIndex implements Serializable { + private static final Logger LOG = LoggerFactory.getLogger(LuceneIndex.class); + private static final String TYPE = "type"; private static final String ID = "id"; private static final String SECONDARY_IDS = "secondary_ids"; diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java b/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java new file mode 100644 index 00000000000..07f14103aad --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java @@ -0,0 +1,28 @@ +package org.opentripplanner.ext.geocoder.configure; + +import dagger.Module; +import dagger.Provides; +import jakarta.inject.Singleton; +import javax.annotation.Nullable; +import org.opentripplanner.ext.geocoder.LuceneIndex; +import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.transit.service.TransitService; + +/** + * This module converts the ride hailing configurations into ride hailing services to be used by the + * application context. + */ +@Module +public class GeocoderModule { + + @Provides + @Singleton + @Nullable + LuceneIndex luceneIndex(TransitService service) { + if (OTPFeature.SandboxAPIGeocoder.isOn()) { + return new LuceneIndex(service); + } else { + return null; + } + } +} diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java index fe95fe0447d..b307776ef52 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationFactory.java @@ -6,6 +6,8 @@ import javax.annotation.Nullable; import org.opentripplanner.ext.emissions.EmissionsDataModel; import org.opentripplanner.ext.emissions.EmissionsServiceModule; +import org.opentripplanner.ext.geocoder.LuceneIndex; +import org.opentripplanner.ext.geocoder.configure.GeocoderModule; import org.opentripplanner.ext.interactivelauncher.configuration.InteractiveLauncherModule; import org.opentripplanner.ext.ridehailing.configure.RideHailingServicesModule; import org.opentripplanner.ext.stopconsolidation.StopConsolidationRepository; @@ -56,6 +58,7 @@ StopConsolidationServiceModule.class, InteractiveLauncherModule.class, StreetLimitationParametersServiceModule.class, + GeocoderModule.class, } ) public interface ConstructApplicationFactory { @@ -87,6 +90,9 @@ public interface ConstructApplicationFactory { StreetLimitationParameters streetLimitationParameters(); + @Nullable + LuceneIndex luceneIndex(); + @Component.Builder interface Builder { @BindsInstance diff --git a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java index 6b822506fd9..0e81193d787 100644 --- a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java +++ b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java @@ -134,7 +134,8 @@ public static DefaultServerRequestContext create( stopConsolidationService, streetLimitationParametersService, flexParameters, - traverseVisitor, luceneIndex + traverseVisitor, + luceneIndex ); } diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java index fe265f30fdc..90dca6ff840 100644 --- a/src/test/java/org/opentripplanner/TestServerContext.java +++ b/src/test/java/org/opentripplanner/TestServerContext.java @@ -55,7 +55,8 @@ public static OtpServerRequestContext createServerContext( List.of(), null, createStreetLimitationParametersService(), - null, null + null, + null ); creatTransitLayerForRaptor(transitModel, routerConfig.transitTuningConfig()); return context; diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java index d18b94e8a20..14d608626b3 100644 --- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java @@ -144,7 +144,8 @@ void setup() { List.of(), null, new DefaultStreetLimitationParametersService(new StreetLimitationParameters()), - null, null + null, + null ), null, transitService diff --git a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java index 41fb0311eb5..85a33281f81 100644 --- a/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java +++ b/src/test/java/org/opentripplanner/transit/speed_test/SpeedTest.java @@ -121,7 +121,8 @@ public SpeedTest( List.of(), null, TestServerContext.createStreetLimitationParametersService(), - null, null + null, + null ); // Creating transitLayerForRaptor should be integrated into the TransitModel, but for now // we do it manually here From 5a7b2efd3d4adb5348fada0d69818a202734bdce Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 12 Jun 2024 15:14:25 +0200 Subject: [PATCH 024/148] Add consolidated stops to geocoder --- .../ext/geocoder/LuceneIndexTest.java | 10 ++- .../ext/geocoder/LuceneIndex.java | 9 ++- .../ext/geocoder/StopClusterMapper.java | 66 +++++++++++++++++-- .../geocoder/configure/GeocoderModule.java | 11 ++-- .../StopConsolidationService.java | 2 + .../DefaultStopConsolidationService.java | 5 ++ .../configure/ConstructApplication.java | 3 +- 7 files changed, 92 insertions(+), 14 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index cee6cf3a2d8..3e6c2b15195 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -18,6 +18,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.opentripplanner.ext.stopconsolidation.internal.DefaultStopConsolidationRepository; +import org.opentripplanner.ext.stopconsolidation.internal.DefaultStopConsolidationService; import org.opentripplanner.model.FeedInfo; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.basic.TransitMode; @@ -160,8 +162,12 @@ public FeedInfo getFeedInfo(String feedId) { ); } }; - index = new LuceneIndex(transitService); - mapper = new StopClusterMapper(transitService); + var stopConsolidationService = new DefaultStopConsolidationService( + new DefaultStopConsolidationRepository(), + transitModel + ); + index = new LuceneIndex(transitService, stopConsolidationService); + mapper = new StopClusterMapper(transitService, stopConsolidationService); } @Test diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index e4de7b4e5c4..1c01c1c36ff 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -11,6 +11,7 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Stream; +import javax.annotation.Nullable; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.en.EnglishAnalyzer; import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper; @@ -40,6 +41,7 @@ import org.apache.lucene.search.suggest.document.FuzzyCompletionQuery; import org.apache.lucene.search.suggest.document.SuggestIndexSearcher; import org.apache.lucene.store.ByteBuffersDirectory; +import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -68,9 +70,12 @@ public class LuceneIndex implements Serializable { private final SuggestIndexSearcher searcher; private final StopClusterMapper stopClusterMapper; - public LuceneIndex(TransitService transitService) { + public LuceneIndex( + TransitService transitService, + @Nullable StopConsolidationService stopConsolidationService + ) { this.transitService = transitService; - this.stopClusterMapper = new StopClusterMapper(transitService); + this.stopClusterMapper = new StopClusterMapper(transitService, stopConsolidationService); LOG.info("Creating geocoder lucene index"); diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java index d9f388ea0e8..4253041a88a 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java @@ -3,6 +3,7 @@ import static org.opentripplanner.ext.geocoder.StopCluster.LocationType.STATION; import static org.opentripplanner.ext.geocoder.StopCluster.LocationType.STOP; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Iterables; import java.util.Collection; import java.util.List; @@ -10,6 +11,8 @@ import java.util.Optional; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; +import org.opentripplanner.ext.stopconsolidation.model.StopReplacement; import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.framework.geometry.WgsCoordinate; import org.opentripplanner.framework.i18n.I18NString; @@ -28,9 +31,14 @@ class StopClusterMapper { private final TransitService transitService; + private final StopConsolidationService stopConsolidationService; - StopClusterMapper(TransitService transitService) { + StopClusterMapper( + TransitService transitService, + StopConsolidationService stopConsolidationService + ) { this.transitService = transitService; + this.stopConsolidationService = stopConsolidationService; } /** @@ -45,16 +53,67 @@ Iterable generateStopClusters( Collection stopLocations, Collection stopLocationsGroups ) { + var stopClusters = buildStopClusters(stopLocations); + var stationClusters = buildStationClusters(stopLocationsGroups); + var consolidatedStopClusters = buildConsolidatedStopClusters(); + + return Iterables.concat(stopClusters, stationClusters, consolidatedStopClusters); + } + + private Iterable buildConsolidatedStopClusters() { + var multiMap = stopConsolidationService + .replacements() + .stream() + .collect( + ImmutableListMultimap.toImmutableListMultimap( + StopReplacement::primary, + StopReplacement::secondary + ) + ); + return multiMap + .keySet() + .stream() + .map(primary -> { + var secondaryIds = multiMap.get(primary); + var secondaries = secondaryIds.stream().map(transitService::getStopLocation).toList(); + var codes = ListUtils.combine( + ListUtils.ofNullable(primary.getCode()), + getCodes(secondaries) + ); + var names = ListUtils.combine( + ListUtils.ofNullable(primary.getName()), + getNames(secondaries) + ); + + return new LuceneStopCluster( + primary.getId().toString(), + secondaryIds.stream().map(id -> id.toString()).toList(), + names, + codes, + toCoordinate(primary.getCoordinate()) + ); + }) + .toList(); + } + + private static List buildStationClusters( + Collection stopLocationsGroups + ) { + return stopLocationsGroups.stream().map(StopClusterMapper::map).toList(); + } + + private List buildStopClusters(Collection stopLocations) { List stops = stopLocations .stream() // remove stop locations without a parent station .filter(sl -> sl.getParentStation() == null) + .filter(sl -> !stopConsolidationService.isPartOfConsolidatedStop(sl)) // stops without a name (for example flex areas) are useless for searching, so we remove them, too .filter(sl -> sl.getName() != null) .toList(); // if they are very close to each other and have the same name, only one is chosen (at random) - var deduplicatedStops = stops + return stops .stream() .collect( Collectors.groupingBy(sl -> @@ -66,9 +125,6 @@ Iterable generateStopClusters( .map(group -> map(group).orElse(null)) .filter(Objects::nonNull) .toList(); - var stations = stopLocationsGroups.stream().map(StopClusterMapper::map).toList(); - - return Iterables.concat(deduplicatedStops, stations); } private static LuceneStopCluster map(StopLocationsGroup g) { diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java b/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java index 07f14103aad..17949e5cb8a 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java @@ -5,12 +5,12 @@ import jakarta.inject.Singleton; import javax.annotation.Nullable; import org.opentripplanner.ext.geocoder.LuceneIndex; +import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.transit.service.TransitService; /** - * This module converts the ride hailing configurations into ride hailing services to be used by the - * application context. + * This module builds the lucene geocoder based on the whether the feature flag is */ @Module public class GeocoderModule { @@ -18,9 +18,12 @@ public class GeocoderModule { @Provides @Singleton @Nullable - LuceneIndex luceneIndex(TransitService service) { + LuceneIndex luceneIndex( + TransitService service, + @Nullable StopConsolidationService stopConsolidationService + ) { if (OTPFeature.SandboxAPIGeocoder.isOn()) { - return new LuceneIndex(service); + return new LuceneIndex(service, stopConsolidationService); } else { return null; } diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java index 11ad4be69ff..68efe8744cc 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java @@ -43,4 +43,6 @@ public interface StopConsolidationService { * For a given stop id return the primary stop if it is part of a consolidated stop group. */ Optional primaryStop(FeedScopedId id); + + boolean isPartOfConsolidatedStop(StopLocation sl); } diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java index 216489512f5..9f31e366be5 100644 --- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java +++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java @@ -67,6 +67,11 @@ public boolean isSecondaryStop(StopLocation stop) { return repo.groups().stream().anyMatch(r -> r.secondaries().contains(stop.getId())); } + @Override + public boolean isPartOfConsolidatedStop(StopLocation sl) { + return isSecondaryStop(sl) || isPrimaryStop(sl); + } + @Override public boolean isActive() { return !repo.groups().isEmpty(); diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index db4a4af7fbd..f2c279e7609 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -179,7 +179,8 @@ private void setupTransitRoutingServer() { } if (OTPFeature.SandboxAPIGeocoder.isOn()) { - LOG.info("Creating debug client geocoder lucene index"); + LOG.info("Initializing geocoder"); + this.factory.luceneIndex(); } } From d826aeb1e3b7feb824905173267e6f24952947c4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 12 Jun 2024 16:07:06 +0200 Subject: [PATCH 025/148] Filter nullable codes --- .../org/opentripplanner/ext/geocoder/StopClusterMapper.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java index 4253041a88a..003225f9781 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java @@ -75,7 +75,11 @@ private Iterable buildConsolidatedStopClusters() { .stream() .map(primary -> { var secondaryIds = multiMap.get(primary); - var secondaries = secondaryIds.stream().map(transitService::getStopLocation).toList(); + var secondaries = secondaryIds + .stream() + .map(transitService::getStopLocation) + .filter(Objects::nonNull) + .toList(); var codes = ListUtils.combine( ListUtils.ofNullable(primary.getCode()), getCodes(secondaries) From ba12693a795de12237166fdeb4098c53c910a97e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 12 Jun 2024 16:18:48 +0200 Subject: [PATCH 026/148] Remove logging --- src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java | 2 -- .../org/opentripplanner/ext/geocoder/StopClusterMapper.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index 1c01c1c36ff..f5b4a6e59e9 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -77,8 +77,6 @@ public LuceneIndex( this.transitService = transitService; this.stopClusterMapper = new StopClusterMapper(transitService, stopConsolidationService); - LOG.info("Creating geocoder lucene index"); - this.analyzer = new PerFieldAnalyzerWrapper( new StandardAnalyzer(), diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java index 003225f9781..98a617b809f 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/StopClusterMapper.java @@ -35,7 +35,7 @@ class StopClusterMapper { StopClusterMapper( TransitService transitService, - StopConsolidationService stopConsolidationService + @Nullable StopConsolidationService stopConsolidationService ) { this.transitService = transitService; this.stopConsolidationService = stopConsolidationService; From 8258a1e03af4675554cc2ecfc2e52e99dd04c7ad Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 13 Jun 2024 13:21:39 +0200 Subject: [PATCH 027/148] Add Google's truth test assertion library, add test --- pom.xml | 6 ++ .../ext/geocoder/StopClusterMapperTest.java | 57 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/ext-test/java/org/opentripplanner/ext/geocoder/StopClusterMapperTest.java diff --git a/pom.xml b/pom.xml index 6f202b592f8..266bbf6ab61 100644 --- a/pom.xml +++ b/pom.xml @@ -690,6 +690,12 @@ ${junit.version} test + + com.google.truth + truth + 1.4.2 + test + com.tngtech.archunit archunit diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/StopClusterMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/StopClusterMapperTest.java new file mode 100644 index 00000000000..578f7d4118f --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/StopClusterMapperTest.java @@ -0,0 +1,57 @@ +package org.opentripplanner.ext.geocoder; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.ext.stopconsolidation.internal.DefaultStopConsolidationRepository; +import org.opentripplanner.ext.stopconsolidation.internal.DefaultStopConsolidationService; +import org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopGroup; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.StopModel; +import org.opentripplanner.transit.service.TransitModel; + +class StopClusterMapperTest { + + private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); + private static final RegularStop STOP_A = TEST_MODEL.stop("A").build(); + private static final RegularStop STOP_B = TEST_MODEL.stop("B").build(); + private static final RegularStop STOP_C = TEST_MODEL.stop("C").build(); + private static final List STOPS = List.of(STOP_A, STOP_B, STOP_C); + private static final StopModel STOP_MODEL = TEST_MODEL + .stopModelBuilder() + .withRegularStops(STOPS) + .build(); + private static final TransitModel TRANSIT_MODEL = new TransitModel( + STOP_MODEL, + new Deduplicator() + ); + private static final List LOCATIONS = STOPS + .stream() + .map(StopLocation.class::cast) + .toList(); + + @Test + void clusterConsolidatedStops() { + var repo = new DefaultStopConsolidationRepository(); + repo.addGroups(List.of(new ConsolidatedStopGroup(STOP_A.getId(), List.of(STOP_B.getId())))); + + var service = new DefaultStopConsolidationService(repo, TRANSIT_MODEL); + var mapper = new StopClusterMapper(new DefaultTransitService(TRANSIT_MODEL), service); + + var clusters = mapper.generateStopClusters(LOCATIONS, List.of()); + + var expected = new LuceneStopCluster( + STOP_A.getId().toString(), + List.of(STOP_B.getId().toString()), + List.of(STOP_A.getName(), STOP_B.getName()), + List.of(STOP_A.getCode(), STOP_B.getCode()), + new StopCluster.Coordinate(STOP_A.getLat(), STOP_A.getLon()) + ); + assertThat(clusters).contains(expected); + } +} From d5ea5af9eb804e72c53f871b1091f0f215cf21b3 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 17 Jun 2024 11:20:36 +0200 Subject: [PATCH 028/148] Extract two separate methods for Transmodel and GTFS scalar versions --- .../apis/gtfs/GraphQLScalars.java | 5 +-- .../apis/transmodel/support/GqlUtil.java | 2 +- .../graphql/scalar/DateScalarFactory.java | 17 ++++++-- .../graphql/scalar/DateScalarFactoryTest.java | 39 +++++++++++++++---- 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index e7b0b491f0d..8ec3172db52 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -236,10 +236,7 @@ private static Optional validateCost(int cost) { ) .build(); - public static final GraphQLScalarType LOCAL_DATE_SCALAR = DateScalarFactory.createDateScalar( - "LocalDate", - null - ); + public static final GraphQLScalarType LOCAL_DATE_SCALAR = DateScalarFactory.createGtfsDateScalar(); public static final GraphQLScalarType GEOJSON_SCALAR = GraphQLScalarType .newScalar() diff --git a/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java b/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java index e94253cf33f..16083085500 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/GqlUtil.java @@ -45,7 +45,7 @@ public class GqlUtil { public GqlUtil(ZoneId timeZone) { this.dateTimeScalar = DateTimeScalarFactory.createMillisecondsSinceEpochAsDateTimeStringScalar(timeZone); - this.dateScalar = DateScalarFactory.createDateScalar("Date", DateScalarFactory.DESCRIPTION); + this.dateScalar = DateScalarFactory.createTransmodelDateScalar(); this.doubleFunctionScalar = DoubleFunctionFactory.createDoubleFunctionScalar(); this.localTimeScalar = LocalTimeScalarFactory.createLocalTimeScalar(); this.timeScalar = TimeScalarFactory.createSecondsSinceMidnightAsTimeObject(); diff --git a/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java b/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java index 590dea013a3..931a98fc5d9 100644 --- a/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java +++ b/src/main/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactory.java @@ -13,18 +13,29 @@ public class DateScalarFactory { - public static final String DESCRIPTION = + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; + public static final String TRANSMODEL_DESCRIPTION = "Local date using the ISO 8601 format: `YYYY-MM-DD`. Example: `2020-05-17`."; - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; + private static final String TRANSMODEL_NAME = "Date"; + private static final String GTFS_NAME = "LocalDate"; private DateScalarFactory() {} + public static GraphQLScalarType createTransmodelDateScalar() { + return createDateScalar(TRANSMODEL_NAME, TRANSMODEL_DESCRIPTION); + } + + public static GraphQLScalarType createGtfsDateScalar() { + // description comes from schema.graphqls + return createDateScalar(GTFS_NAME, null); + } + /** * @param description Nullable description that allows caller to pass in null which leads to the * description from schema.graphqls to be used. */ - public static GraphQLScalarType createDateScalar( + private static GraphQLScalarType createDateScalar( String scalarName, @Nullable String description ) { diff --git a/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java b/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java index 02e4b37d7d6..db95f0ba8ce 100644 --- a/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java +++ b/src/test/java/org/opentripplanner/framework/graphql/scalar/DateScalarFactoryTest.java @@ -7,25 +7,48 @@ import graphql.schema.CoercingParseValueException; import graphql.schema.GraphQLScalarType; import java.time.LocalDate; +import java.util.List; +import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class DateScalarFactoryTest { - private static final GraphQLScalarType SCALAR = DateScalarFactory.createDateScalar("Date", null); + private static final GraphQLScalarType GTFS_SCALAR = DateScalarFactory.createGtfsDateScalar(); + private static final GraphQLScalarType TRANSMODEL_SCALAR = DateScalarFactory.createTransmodelDateScalar(); + private static final List INVALID_DATES = List.of( + "2024-05", + "2024", + "2024-99-04", + "202405-23", + "20240523" + ); + + static Stream succesfulCases() { + return Stream.of(GTFS_SCALAR, TRANSMODEL_SCALAR).map(s -> Arguments.of(s, "2024-05-23")); + } @ParameterizedTest - @ValueSource(strings = { "2024-05-23" }) - void parse(String input) { - var result = SCALAR.getCoercing().parseValue(input); + @MethodSource("succesfulCases") + void parse(GraphQLScalarType scalar, String input) { + var result = scalar.getCoercing().parseValue(input); assertInstanceOf(LocalDate.class, result); var date = (LocalDate) result; assertEquals(LocalDate.of(2024, 5, 23), date); } + static Stream invalidCases() { + return INVALID_DATES + .stream() + .flatMap(date -> + Stream.of(Arguments.of(GTFS_SCALAR, date), Arguments.of(TRANSMODEL_SCALAR, date)) + ); + } + @ParameterizedTest - @ValueSource(strings = { "2024-05", "2024", "2024-99-04", "202405-23", "20240523" }) - void failParsing(String input) { - assertThrows(CoercingParseValueException.class, () -> SCALAR.getCoercing().parseValue(input)); + @MethodSource("invalidCases") + void failParsing(GraphQLScalarType scalar, String input) { + assertThrows(CoercingParseValueException.class, () -> scalar.getCoercing().parseValue(input)); } } From 54b2fe248a34d909cb77a7e2450d0258455727d5 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 17 Jun 2024 11:32:42 +0200 Subject: [PATCH 029/148] Refer to API documentation --- .../apis/gtfs/PatternByServiceDatesFilter.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index 0867e6ead54..88dacb22afd 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -24,11 +24,9 @@ public class PatternByServiceDatesFilter { private final Function> getServiceDatesForTrip; /** - * - * @param startInclusive The inclusive start date to check the patterns for. If null then no start - * date is defined and this will therefore match all dates. - * @param endInclusive The inclusive end date to check the patterns for. If null then no end date - * is defined this will therefore match all dates. + * This method is not private to enable unit testing. + *

+ * See the API documentation for a discussion of {@code startInclusive} and {@code endInclusive}. */ PatternByServiceDatesFilter( @Nullable LocalDate startInclusive, @@ -38,7 +36,7 @@ public class PatternByServiceDatesFilter { ) { this.getPatternsForRoute = Objects.requireNonNull(getPatternsForRoute); this.getServiceDatesForTrip = Objects.requireNonNull(getServiceDatesForTrip); - // optional + // optional, but one must be defined this.startInclusive = startInclusive; this.endInclusive = endInclusive; From 665ce7ac129049849f5fed1ef05312fe8b874089 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 17 Jun 2024 12:07:58 +0200 Subject: [PATCH 030/148] Move small helper method into separate class --- .../apis/gtfs/GraphQLUtils.java | 14 ++++++++ .../gtfs/PatternByServiceDatesFilter.java | 7 ---- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 2 +- .../apis/gtfs/datafetchers/RouteImpl.java | 2 +- .../gtfs/PatternByServiceDatesFilterTest.java | 30 ---------------- .../framework/graphql/GraphQLUtilsTest.java | 34 +++++++++++++++++++ 6 files changed, 50 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java index 3fb339daa32..9aa1235879c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java @@ -2,6 +2,7 @@ import java.time.Instant; import java.util.Locale; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLFilterPlaceType; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLFormFactor; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLInputField; @@ -109,4 +110,17 @@ public static boolean startsWith(String str, String name, Locale locale) { public static boolean startsWith(I18NString str, String name, Locale locale) { return str != null && str.toString(locale).toLowerCase(locale).startsWith(name); } + + /** + * Checks if a service date filter input has at least one filter set. If both start and end are + * null then no filtering is necessary and this method returns null. + */ + public static boolean hasServiceDateFilter( + GraphQLTypes.GraphQLServiceDateFilterInput serviceDays + ) { + return ( + serviceDays != null && + (serviceDays.getGraphQLStart() != null || serviceDays.getGraphQLEnd() != null) + ); + } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index 88dacb22afd..41e2b812420 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -97,11 +97,4 @@ private boolean hasServicesOnDate(TripPattern pattern) { ); }); } - - public static boolean hasServiceDateFilter(GraphQLServiceDateFilterInput serviceDays) { - return ( - serviceDays != null && - (serviceDays.getGraphQLStart() != null || serviceDays.getGraphQLEnd() != null) - ); - } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index 7a644da4be8..c83669a386b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -611,7 +611,7 @@ public DataFetcher> routes() { ); } - if (PatternByServiceDatesFilter.hasServiceDateFilter(args.getGraphQLServiceDates())) { + if (GraphQLUtils.hasServiceDateFilter(args.getGraphQLServiceDates())) { var filter = new PatternByServiceDatesFilter(args.getGraphQLServiceDates(), transitService); routeStream = filter.filterRoutes(routeStream).stream(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java index 596936dfdb5..5eeb662a6d1 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java @@ -181,7 +181,7 @@ public DataFetcher> patterns() { var args = new GraphQLTypes.GraphQLRoutePatternsArgs(environment.getArguments()); - if (PatternByServiceDatesFilter.hasServiceDateFilter(args.getGraphQLServiceDates())) { + if (GraphQLUtils.hasServiceDateFilter(args.getGraphQLServiceDates())) { var filter = new PatternByServiceDatesFilter(args.getGraphQLServiceDates(), transitService); return filter.filterPatterns(patterns); } else { diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index b9ad1c4973a..1893516ab58 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -3,21 +3,16 @@ import static java.time.LocalDate.parse; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.apis.gtfs.PatternByServiceDatesFilterTest.FilterExpectation.NOT_REMOVED; import static org.opentripplanner.apis.gtfs.PatternByServiceDatesFilterTest.FilterExpectation.REMOVED; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; -import java.util.Map; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLServiceDateFilterInput; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; @@ -41,7 +36,6 @@ class PatternByServiceDatesFilterTest { private static final RegularStop STOP_1 = MODEL.stop("1").build(); private static final StopPattern STOP_PATTERN = TransitModelForTest.stopPattern(STOP_1, STOP_1); private static final TripPattern PATTERN_1 = pattern(); - private static final LocalDate DATE = LocalDate.parse("2024-05-27"); enum FilterExpectation { REMOVED, @@ -151,28 +145,4 @@ private static PatternByServiceDatesFilter mockFilter(LocalDate start, LocalDate trip -> List.of(parse("2024-05-01"), parse("2024-06-01")) ); } - - public static List noFilterCases() { - var list = new ArrayList(); - list.add(null); - list.add(new GraphQLServiceDateFilterInput(Map.of())); - return list; - } - - @ParameterizedTest - @MethodSource("noFilterCases") - void hasNoServiceDateFilter(GraphQLServiceDateFilterInput input) { - assertFalse(PatternByServiceDatesFilter.hasServiceDateFilter(input)); - } - - public static List> hasFilterCases() { - return List.of(Map.of("start", DATE), Map.of("end", DATE), Map.of("start", DATE, "end", DATE)); - } - - @ParameterizedTest - @MethodSource("hasFilterCases") - void hasServiceDateFilter(Map params) { - var input = new GraphQLServiceDateFilterInput(params); - assertTrue(PatternByServiceDatesFilter.hasServiceDateFilter(input)); - } } diff --git a/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java b/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java index b72cb6e5a0d..8119b9256c8 100644 --- a/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java @@ -3,19 +3,29 @@ import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder; import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import graphql.ExecutionInput; import graphql.execution.ExecutionContext; import graphql.execution.ExecutionId; import graphql.schema.DataFetchingEnvironmentImpl; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.Map; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.framework.i18n.TranslatedString; class GraphQLUtilsTest { + private static final LocalDate DATE = LocalDate.parse("2024-05-27"); + static final ExecutionContext executionContext; static { @@ -122,4 +132,28 @@ void testGetLocaleWithLocalContextLocale() { assertEquals(frenchLocale, locale); } + + public static List noFilterCases() { + var list = new ArrayList(); + list.add(null); + list.add(new GraphQLTypes.GraphQLServiceDateFilterInput(Map.of())); + return list; + } + + @ParameterizedTest + @MethodSource("noFilterCases") + void hasNoServiceDateFilter(GraphQLTypes.GraphQLServiceDateFilterInput input) { + assertFalse(org.opentripplanner.apis.gtfs.GraphQLUtils.hasServiceDateFilter(input)); + } + + public static List> hasFilterCases() { + return List.of(Map.of("start", DATE), Map.of("end", DATE), Map.of("start", DATE, "end", DATE)); + } + + @ParameterizedTest + @MethodSource("hasFilterCases") + void hasServiceDateFilter(Map params) { + var input = new GraphQLTypes.GraphQLServiceDateFilterInput(params); + assertTrue(org.opentripplanner.apis.gtfs.GraphQLUtils.hasServiceDateFilter(input)); + } } From f549d053c39a6dbb84fa94b50f74b851fa842d65 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 18 Jun 2024 12:34:54 +0200 Subject: [PATCH 031/148] Mention new filter engine --- .../opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index 41e2b812420..f43bdf7decc 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -15,6 +15,9 @@ /** * Encapsulates the logic to filter patterns by the service dates that they operate on. It also * has a method to filter routes by checking if their patterns operate on the required days + *

+ * Once a more complete filtering engine is in place in the core data model, this code should be + * there rather than a separate class in the API package. */ public class PatternByServiceDatesFilter { From ee4887ecf0a77253c1b0d56941530c91845ea95f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 18 Jun 2024 15:45:33 +0200 Subject: [PATCH 032/148] Switch to endExclusive --- .../apis/gtfs/PatternByServiceDatesFilter.java | 16 ++++++++-------- .../opentripplanner/apis/gtfs/schema.graphqls | 11 +++++++---- .../gtfs/PatternByServiceDatesFilterTest.java | 4 +++- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index f43bdf7decc..9184bd9d728 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -22,18 +22,18 @@ public class PatternByServiceDatesFilter { private final LocalDate startInclusive; - private final LocalDate endInclusive; + private final LocalDate endExclusive; private final Function> getPatternsForRoute; private final Function> getServiceDatesForTrip; /** * This method is not private to enable unit testing. *

- * See the API documentation for a discussion of {@code startInclusive} and {@code endInclusive}. + * See the API documentation for a discussion of {@code startInclusive} and {@code endExclusive}. */ PatternByServiceDatesFilter( @Nullable LocalDate startInclusive, - @Nullable LocalDate endInclusive, + @Nullable LocalDate endExclusive, Function> getPatternsForRoute, Function> getServiceDatesForTrip ) { @@ -41,12 +41,12 @@ public class PatternByServiceDatesFilter { this.getServiceDatesForTrip = Objects.requireNonNull(getServiceDatesForTrip); // optional, but one must be defined this.startInclusive = startInclusive; - this.endInclusive = endInclusive; + this.endExclusive = endExclusive; - if (startInclusive == null && endInclusive == null) { - throw new IllegalArgumentException("startInclusive and endInclusive cannot be both null"); + if (startInclusive == null && endExclusive == null) { + throw new IllegalArgumentException("startInclusive and endExclusive cannot be both null"); } else if ( - startInclusive != null && endInclusive != null && startInclusive.isAfter(endInclusive) + startInclusive != null && endExclusive != null && startInclusive.isAfter(endExclusive) ) { throw new IllegalArgumentException("start must be before end"); } @@ -96,7 +96,7 @@ private boolean hasServicesOnDate(TripPattern pattern) { ( startInclusive == null || date.isEqual(startInclusive) || date.isAfter(startInclusive) ) && - (endInclusive == null || date.isEqual(endInclusive) || date.isBefore(endInclusive)) + (endExclusive == null || date.isBefore(endExclusive)) ); }); } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 8ca0ac189cb..cbedf2a369f 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3447,13 +3447,16 @@ on Sunday morning at 1am to 3am, might have the previous Saturday's service date """ input ServiceDateFilterInput { """ - Inclusive start date of the filter. If `null` this means that no `start` filter is applied and all - dates that are before or on `end` are selected. + **Inclusive** start date of the filter. If `null` this means that no `start` filter is applied and all + dates that are before `end` are selected. """ start: LocalDate """ - Inclusive end date of the filter. If `null` this means that no end filter is applied and all - entities that are after or on `start` are selected. + **Exclusive** end date of the filter. This means that if you want a time window from Sunday to + Sunday, `end` must be on Monday. + + If `null` this means that no end filter is applied and all entities that are after or on `start` + are selected. """ end: LocalDate } diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index 1893516ab58..01743dec89d 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -95,11 +95,13 @@ void validRange(LocalDate start, LocalDate end) { static List ranges() { return List.of( Arguments.of(null, parse("2024-05-03"), NOT_REMOVED), - Arguments.of(null, parse("2024-05-01"), NOT_REMOVED), Arguments.of(parse("2024-05-03"), null, NOT_REMOVED), Arguments.of(parse("2024-05-01"), null, NOT_REMOVED), + Arguments.of(null, parse("2024-04-30"), REMOVED), + Arguments.of(null, parse("2024-05-01"), REMOVED), Arguments.of(parse("2024-05-02"), parse("2024-05-02"), REMOVED), Arguments.of(parse("2024-05-02"), parse("2024-05-03"), REMOVED), + Arguments.of(parse("2024-05-02"), parse("2024-06-01"), REMOVED), Arguments.of(parse("2025-01-01"), null, REMOVED), Arguments.of(parse("2025-01-01"), parse("2025-01-02"), REMOVED), Arguments.of(null, parse("2023-12-31"), REMOVED), From 6755cbc868b09e25f3395658b0d6bf566c543a3f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 19 Jun 2024 10:18:12 +0200 Subject: [PATCH 033/148] Update Javadoc --- .../opentripplanner/ext/geocoder/configure/GeocoderModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java b/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java index 17949e5cb8a..b67f81385b8 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java @@ -10,7 +10,7 @@ import org.opentripplanner.transit.service.TransitService; /** - * This module builds the lucene geocoder based on the whether the feature flag is + * This module builds the Lucene geocoder based on whether the feature flag is on or off. */ @Module public class GeocoderModule { From 4bbe43e8c708b45bde67fb641b19ac6db1001afb Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 19 Jun 2024 10:20:17 +0200 Subject: [PATCH 034/148] Re-add backwards-compatibility --- .../ext/geocoder/GeocoderResource.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java index c9cd199c557..304829843ae 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/GeocoderResource.java @@ -3,6 +3,7 @@ import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; @@ -30,6 +31,20 @@ public GeocoderResource(@Context OtpServerRequestContext requestContext) { luceneIndex = requestContext.lucenceIndex(); } + /** + * This class is only here for backwards-compatibility. It will be removed in the future. + */ + @Path("/routers/{ignoreRouterId}/geocode") + public static class GeocoderResourceOldPath extends GeocoderResource { + + public GeocoderResourceOldPath( + @Context OtpServerRequestContext serverContext, + @PathParam("ignoreRouterId") String ignore + ) { + super(serverContext); + } + } + /** * Geocode using data using the OTP graph for stops, clusters and street names * From a714a532e6ef0404e001fbd9519127a8a2935126 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 20 Jun 2024 13:18:21 +0200 Subject: [PATCH 035/148] Inject transit model instead of transit service --- .../opentripplanner/ext/geocoder/LuceneIndex.java | 15 ++++++++++----- .../ext/geocoder/configure/GeocoderModule.java | 6 +++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index f5b4a6e59e9..6283a779088 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -47,14 +47,12 @@ import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.site.StopLocationsGroup; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.transit.service.TransitService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class LuceneIndex implements Serializable { - private static final Logger LOG = LoggerFactory.getLogger(LuceneIndex.class); - private static final String TYPE = "type"; private static final String ID = "id"; private static final String SECONDARY_IDS = "secondary_ids"; @@ -70,7 +68,14 @@ public class LuceneIndex implements Serializable { private final SuggestIndexSearcher searcher; private final StopClusterMapper stopClusterMapper; - public LuceneIndex( + public LuceneIndex(TransitModel transitModel, StopConsolidationService stopConsolidationService) { + this(new DefaultTransitService(transitModel), stopConsolidationService); + } + + /** + * This method is only visible for testing. + */ + LuceneIndex( TransitService transitService, @Nullable StopConsolidationService stopConsolidationService ) { diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java b/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java index b67f81385b8..9eaf6ade8e5 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/configure/GeocoderModule.java @@ -7,7 +7,7 @@ import org.opentripplanner.ext.geocoder.LuceneIndex; import org.opentripplanner.ext.stopconsolidation.StopConsolidationService; import org.opentripplanner.framework.application.OTPFeature; -import org.opentripplanner.transit.service.TransitService; +import org.opentripplanner.transit.service.TransitModel; /** * This module builds the Lucene geocoder based on whether the feature flag is on or off. @@ -19,11 +19,11 @@ public class GeocoderModule { @Singleton @Nullable LuceneIndex luceneIndex( - TransitService service, + TransitModel transitModel, @Nullable StopConsolidationService stopConsolidationService ) { if (OTPFeature.SandboxAPIGeocoder.isOn()) { - return new LuceneIndex(service, stopConsolidationService); + return new LuceneIndex(transitModel, stopConsolidationService); } else { return null; } From 29a750daf8a04d472783603690a30b296ff5ffa4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 20 Jun 2024 22:41:18 +0200 Subject: [PATCH 036/148] Add comment --- .../standalone/configure/ConstructApplication.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java index f2c279e7609..7259acb00c2 100644 --- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java +++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplication.java @@ -180,6 +180,7 @@ private void setupTransitRoutingServer() { if (OTPFeature.SandboxAPIGeocoder.isOn()) { LOG.info("Initializing geocoder"); + // eagerly initialize the geocoder this.factory.luceneIndex(); } } From 9b17f9777cb21c395ce3ff890db49d8938c26c43 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 20 Jun 2024 22:42:54 +0200 Subject: [PATCH 037/148] Remove trailing and leading whitespace --- src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index 6283a779088..8f11ff252e7 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -253,6 +253,7 @@ private Stream matchingDocuments( String searchTerms, boolean autocomplete ) { + searchTerms = searchTerms.strip(); try { if (autocomplete) { var completionQuery = new FuzzyCompletionQuery( From f95fd60bbd42ca15a4123fb3132fbd6f2a0620ae Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 21 Jun 2024 14:50:20 +0200 Subject: [PATCH 038/148] Add comment about unusal instantiation of DefaultTransitService --- .../java/org/opentripplanner/ext/geocoder/LuceneIndex.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index 8f11ff252e7..fe7bef8ad13 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -68,6 +68,11 @@ public class LuceneIndex implements Serializable { private final SuggestIndexSearcher searcher; private final StopClusterMapper stopClusterMapper; + /** + * Since the {@link TransitService} is request scoped, we don't inject it into this class. + * However, we do need some methods in the service and that's why we instantiate it manually in this + * constructor. + */ public LuceneIndex(TransitModel transitModel, StopConsolidationService stopConsolidationService) { this(new DefaultTransitService(transitModel), stopConsolidationService); } From 13d55c22999ff71795065e754adff9efec21efd8 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 1 Jul 2024 13:55:51 +0200 Subject: [PATCH 039/148] Rename ServiceDateFilterInput to LocalDateRangeInput --- .../apis/gtfs/GraphQLUtils.java | 8 +- .../gtfs/PatternByServiceDatesFilter.java | 4 +- .../apis/gtfs/generated/GraphQLTypes.java | 75 +++++++++---------- .../opentripplanner/apis/gtfs/schema.graphqls | 12 +-- .../framework/graphql/GraphQLUtilsTest.java | 10 +-- 5 files changed, 51 insertions(+), 58 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java index 9aa1235879c..1fe09b60787 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java @@ -115,12 +115,10 @@ public static boolean startsWith(I18NString str, String name, Locale locale) { * Checks if a service date filter input has at least one filter set. If both start and end are * null then no filtering is necessary and this method returns null. */ - public static boolean hasServiceDateFilter( - GraphQLTypes.GraphQLServiceDateFilterInput serviceDays - ) { + public static boolean hasServiceDateFilter(GraphQLTypes.GraphQLLocalDateRangeInput dateRange) { return ( - serviceDays != null && - (serviceDays.getGraphQLStart() != null || serviceDays.getGraphQLEnd() != null) + dateRange != null && + (dateRange.getGraphQLStart() != null || dateRange.getGraphQLEnd() != null) ); } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index 9184bd9d728..2aef232abc1 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -6,7 +6,7 @@ import java.util.function.Function; import java.util.stream.Stream; import javax.annotation.Nullable; -import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLServiceDateFilterInput; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Trip; @@ -53,7 +53,7 @@ public class PatternByServiceDatesFilter { } public PatternByServiceDatesFilter( - GraphQLServiceDateFilterInput filterInput, + GraphQLTypes.GraphQLLocalDateRangeInput filterInput, TransitService transitService ) { this( 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 9f6a237f7d9..01c40b5b239 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/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; @@ -1269,6 +1268,35 @@ public void setGraphQLOriginModesWithParentStation( } } + public static class GraphQLLocalDateRangeInput { + + private java.time.LocalDate end; + private java.time.LocalDate start; + + public GraphQLLocalDateRangeInput(Map args) { + if (args != null) { + this.end = (java.time.LocalDate) args.get("end"); + this.start = (java.time.LocalDate) args.get("start"); + } + } + + public java.time.LocalDate getGraphQLEnd() { + return this.end; + } + + public java.time.LocalDate getGraphQLStart() { + return this.start; + } + + public void setGraphQLEnd(java.time.LocalDate end) { + this.end = end; + } + + public void setGraphQLStart(java.time.LocalDate start) { + this.start = start; + } + } + /** Identifies whether this stop represents a stop or station. */ public enum GraphQLLocationType { ENTRANCE, @@ -3450,7 +3478,7 @@ public static class GraphQLQueryTypeRoutesArgs { private List feeds; private List ids; private String name; - private GraphQLServiceDateFilterInput serviceDates; + private GraphQLLocalDateRangeInput serviceDates; private List transportModes; public GraphQLQueryTypeRoutesArgs(Map args) { @@ -3459,7 +3487,7 @@ public GraphQLQueryTypeRoutesArgs(Map args) { this.ids = (List) args.get("ids"); this.name = (String) args.get("name"); this.serviceDates = - new GraphQLServiceDateFilterInput((Map) args.get("serviceDates")); + new GraphQLLocalDateRangeInput((Map) args.get("serviceDates")); if (args.get("transportModes") != null) { this.transportModes = ((List) args.get("transportModes")).stream() @@ -3482,7 +3510,7 @@ public String getGraphQLName() { return this.name; } - public GraphQLServiceDateFilterInput getGraphQLServiceDates() { + public GraphQLLocalDateRangeInput getGraphQLServiceDates() { return this.serviceDates; } @@ -3502,7 +3530,7 @@ public void setGraphQLName(String name) { this.name = name; } - public void setGraphQLServiceDates(GraphQLServiceDateFilterInput serviceDates) { + public void setGraphQLServiceDates(GraphQLLocalDateRangeInput serviceDates) { this.serviceDates = serviceDates; } @@ -3947,20 +3975,20 @@ public void setGraphQLLanguage(String language) { public static class GraphQLRoutePatternsArgs { - private GraphQLServiceDateFilterInput serviceDates; + private GraphQLLocalDateRangeInput serviceDates; public GraphQLRoutePatternsArgs(Map args) { if (args != null) { this.serviceDates = - new GraphQLServiceDateFilterInput((Map) args.get("serviceDates")); + new GraphQLLocalDateRangeInput((Map) args.get("serviceDates")); } } - public GraphQLServiceDateFilterInput getGraphQLServiceDates() { + public GraphQLLocalDateRangeInput getGraphQLServiceDates() { return this.serviceDates; } - public void setGraphQLServiceDates(GraphQLServiceDateFilterInput serviceDates) { + public void setGraphQLServiceDates(GraphQLLocalDateRangeInput serviceDates) { this.serviceDates = serviceDates; } } @@ -4126,35 +4154,6 @@ public void setGraphQLDestinationScooterPolicy( } } - public static class GraphQLServiceDateFilterInput { - - private java.time.LocalDate end; - private java.time.LocalDate start; - - public GraphQLServiceDateFilterInput(Map args) { - if (args != null) { - this.end = (java.time.LocalDate) args.get("end"); - this.start = (java.time.LocalDate) args.get("start"); - } - } - - public java.time.LocalDate getGraphQLEnd() { - return this.end; - } - - public java.time.LocalDate getGraphQLStart() { - return this.start; - } - - public void setGraphQLEnd(java.time.LocalDate end) { - this.end = end; - } - - public void setGraphQLStart(java.time.LocalDate start) { - this.start = start; - } - } - public static class GraphQLStopAlertsArgs { private List types; diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index cbedf2a369f..26ee6bdf4db 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3439,13 +3439,9 @@ enum Qualifier { """ -Filters entity by their service dates. - -**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. +Filters an entity by a date range. """ -input ServiceDateFilterInput { +input LocalDateRangeInput { """ **Inclusive** start date of the filter. If `null` this means that no `start` filter is applied and all dates that are before `end` are selected. @@ -3627,7 +3623,7 @@ type QueryType { 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. """ - serviceDates: ServiceDateFilterInput + serviceDates: LocalDateRangeInput ): [Route] """ @@ -4525,7 +4521,7 @@ type Route implements Node { 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. """ - serviceDates: ServiceDateFilterInput + serviceDates: LocalDateRangeInput ): [Pattern] """List of stops on this route""" diff --git a/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java b/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java index 8119b9256c8..42330652410 100644 --- a/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java @@ -133,16 +133,16 @@ void testGetLocaleWithLocalContextLocale() { assertEquals(frenchLocale, locale); } - public static List noFilterCases() { - var list = new ArrayList(); + public static List noFilterCases() { + var list = new ArrayList(); list.add(null); - list.add(new GraphQLTypes.GraphQLServiceDateFilterInput(Map.of())); + list.add(new GraphQLTypes.GraphQLLocalDateRangeInput(Map.of())); return list; } @ParameterizedTest @MethodSource("noFilterCases") - void hasNoServiceDateFilter(GraphQLTypes.GraphQLServiceDateFilterInput input) { + void hasNoServiceDateFilter(GraphQLTypes.GraphQLLocalDateRangeInput input) { assertFalse(org.opentripplanner.apis.gtfs.GraphQLUtils.hasServiceDateFilter(input)); } @@ -153,7 +153,7 @@ public static List> hasFilterCases() { @ParameterizedTest @MethodSource("hasFilterCases") void hasServiceDateFilter(Map params) { - var input = new GraphQLTypes.GraphQLServiceDateFilterInput(params); + var input = new GraphQLTypes.GraphQLLocalDateRangeInput(params); assertTrue(org.opentripplanner.apis.gtfs.GraphQLUtils.hasServiceDateFilter(input)); } } From 0edc190f22bb03c9d97044a2c8147ed691195b37 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 1 Jul 2024 17:36:45 +0200 Subject: [PATCH 040/148] Move logic into mapper, extract separate class for date range --- .../apis/gtfs/GraphQLUtils.java | 12 ------ .../gtfs/PatternByServiceDatesFilter.java | 28 +++++-------- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 3 +- .../apis/gtfs/datafetchers/RouteImpl.java | 3 +- .../gtfs/mapping/LocalDateRangeMapper.java | 18 ++++++++ .../apis/gtfs/model/LocalDateRange.java | 23 +++++++++++ .../gtfs/PatternByServiceDatesFilterTest.java | 17 ++++++-- .../mapping/LocalDateRangeMapperTest.java | 41 +++++++++++++++++++ .../apis/gtfs/model/LocalDateRangeTest.java | 22 ++++++++++ .../framework/graphql/GraphQLUtilsTest.java | 34 --------------- 10 files changed, 132 insertions(+), 69 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/model/LocalDateRangeTest.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java index 1fe09b60787..3fb339daa32 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLUtils.java @@ -2,7 +2,6 @@ import java.time.Instant; import java.util.Locale; -import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLFilterPlaceType; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLFormFactor; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLInputField; @@ -110,15 +109,4 @@ public static boolean startsWith(String str, String name, Locale locale) { public static boolean startsWith(I18NString str, String name, Locale locale) { return str != null && str.toString(locale).toLowerCase(locale).startsWith(name); } - - /** - * Checks if a service date filter input has at least one filter set. If both start and end are - * null then no filtering is necessary and this method returns null. - */ - public static boolean hasServiceDateFilter(GraphQLTypes.GraphQLLocalDateRangeInput dateRange) { - return ( - dateRange != null && - (dateRange.getGraphQLStart() != null || dateRange.getGraphQLEnd() != null) - ); - } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index 2aef232abc1..82d67cae7f3 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -5,8 +5,8 @@ import java.util.Objects; import java.util.function.Function; import java.util.stream.Stream; -import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.model.LocalDateRange; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.network.TripPattern; import org.opentripplanner.transit.model.timetable.Trip; @@ -21,33 +21,26 @@ */ public class PatternByServiceDatesFilter { - private final LocalDate startInclusive; - private final LocalDate endExclusive; private final Function> getPatternsForRoute; private final Function> getServiceDatesForTrip; + private final LocalDateRange range; /** * This method is not private to enable unit testing. *

- * See the API documentation for a discussion of {@code startInclusive} and {@code endExclusive}. */ PatternByServiceDatesFilter( - @Nullable LocalDate startInclusive, - @Nullable LocalDate endExclusive, + LocalDateRange range, Function> getPatternsForRoute, Function> getServiceDatesForTrip ) { this.getPatternsForRoute = Objects.requireNonNull(getPatternsForRoute); this.getServiceDatesForTrip = Objects.requireNonNull(getServiceDatesForTrip); - // optional, but one must be defined - this.startInclusive = startInclusive; - this.endExclusive = endExclusive; + this.range = range; - if (startInclusive == null && endExclusive == null) { + if (range.unlimited()) { throw new IllegalArgumentException("startInclusive and endExclusive cannot be both null"); - } else if ( - startInclusive != null && endExclusive != null && startInclusive.isAfter(endExclusive) - ) { + } else if (range.startBeforeEnd()) { throw new IllegalArgumentException("start must be before end"); } } @@ -57,8 +50,7 @@ public PatternByServiceDatesFilter( TransitService transitService ) { this( - filterInput.getGraphQLStart(), - filterInput.getGraphQLEnd(), + new LocalDateRange(filterInput.getGraphQLStart(), filterInput.getGraphQLEnd()), transitService::getPatternsForRoute, trip -> transitService.getCalendarService().getServiceDatesForServiceId(trip.getServiceId()) ); @@ -94,9 +86,11 @@ private boolean hasServicesOnDate(TripPattern pattern) { .stream() .anyMatch(date -> ( - startInclusive == null || date.isEqual(startInclusive) || date.isAfter(startInclusive) + range.startInclusive() == null || + date.isEqual(range.startInclusive()) || + date.isAfter(range.startInclusive()) ) && - (endExclusive == null || date.isBefore(endExclusive)) + (range.endExclusive() == null || date.isBefore(range.endExclusive())) ); }); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index c83669a386b..b22310d1da3 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -31,6 +31,7 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLQueryTypeStopsByRadiusArgs; +import org.opentripplanner.apis.gtfs.mapping.LocalDateRangeMapper; import org.opentripplanner.apis.gtfs.mapping.routerequest.LegacyRouteRequestMapper; import org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapper; import org.opentripplanner.ext.fares.impl.DefaultFareService; @@ -611,7 +612,7 @@ public DataFetcher> routes() { ); } - if (GraphQLUtils.hasServiceDateFilter(args.getGraphQLServiceDates())) { + if (LocalDateRangeMapper.hasServiceDateFilter(args.getGraphQLServiceDates())) { var filter = new PatternByServiceDatesFilter(args.getGraphQLServiceDates(), transitService); routeStream = filter.filterRoutes(routeStream).stream(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java index 5eeb662a6d1..921f85ff813 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java @@ -15,6 +15,7 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLBikesAllowed; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLTransitMode; import org.opentripplanner.apis.gtfs.mapping.BikesAllowedMapper; +import org.opentripplanner.apis.gtfs.mapping.LocalDateRangeMapper; import org.opentripplanner.routing.alertpatch.EntitySelector; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.services.TransitAlertService; @@ -181,7 +182,7 @@ public DataFetcher> patterns() { var args = new GraphQLTypes.GraphQLRoutePatternsArgs(environment.getArguments()); - if (GraphQLUtils.hasServiceDateFilter(args.getGraphQLServiceDates())) { + if (LocalDateRangeMapper.hasServiceDateFilter(args.getGraphQLServiceDates())) { var filter = new PatternByServiceDatesFilter(args.getGraphQLServiceDates(), transitService); return filter.filterPatterns(patterns); } else { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapper.java new file mode 100644 index 00000000000..56064ea37dc --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapper.java @@ -0,0 +1,18 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.model.LocalDateRange; + +public class LocalDateRangeMapper { + + /** + * Checks if a service date filter input has at least one filter set. If both start and end are + * null then no filtering is necessary and this method returns null. + */ + public static boolean hasServiceDateFilter(GraphQLTypes.GraphQLLocalDateRangeInput dateRange) { + return ( + dateRange != null && + !new LocalDateRange(dateRange.getGraphQLStart(), dateRange.getGraphQLEnd()).unlimited() + ); + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java b/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java new file mode 100644 index 00000000000..83e0c93b5bd --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java @@ -0,0 +1,23 @@ +package org.opentripplanner.apis.gtfs.model; + +import java.time.LocalDate; +import javax.annotation.Nullable; + +/** + * See the API documentation for a discussion of {@code startInclusive} and {@code endExclusive}. + */ +public record LocalDateRange(@Nullable LocalDate startInclusive, @Nullable LocalDate endExclusive) { + /** + * Does it actually define a limit or is the range unlimited? + */ + public boolean unlimited() { + return startInclusive == null && endExclusive == null; + } + + /** + * Is the start date before the end ( + */ + public boolean startBeforeEnd() { + return startInclusive != null && endExclusive != null && startInclusive.isAfter(endExclusive); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index 01743dec89d..57f399ad821 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.apis.gtfs.model.LocalDateRange; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; @@ -71,7 +72,12 @@ static List invalidRangeCases() { void invalidRange(LocalDate start, LocalDate end) { assertThrows( IllegalArgumentException.class, - () -> new PatternByServiceDatesFilter(start, end, r -> List.of(), d -> List.of()) + () -> + new PatternByServiceDatesFilter( + new LocalDateRange(start, end), + r -> List.of(), + d -> List.of() + ) ); } @@ -88,7 +94,11 @@ static List validRangeCases() { @MethodSource("validRangeCases") void validRange(LocalDate start, LocalDate end) { assertDoesNotThrow(() -> - new PatternByServiceDatesFilter(start, end, r -> List.of(), d -> List.of()) + new PatternByServiceDatesFilter( + new LocalDateRange(start, end), + r -> List.of(), + d -> List.of() + ) ); } @@ -141,8 +151,7 @@ void filterRoutes(LocalDate start, LocalDate end, FilterExpectation expectation) private static PatternByServiceDatesFilter mockFilter(LocalDate start, LocalDate end) { return new PatternByServiceDatesFilter( - start, - end, + new LocalDateRange(start, end), route -> List.of(PATTERN_1), trip -> List.of(parse("2024-05-01"), parse("2024-06-01")) ); diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java new file mode 100644 index 00000000000..eba38c0a180 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java @@ -0,0 +1,41 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; + +class LocalDateRangeMapperTest { + + private static final LocalDate DATE = LocalDate.parse("2024-05-27"); + + public static List noFilterCases() { + var list = new ArrayList(); + list.add(null); + list.add(new GraphQLTypes.GraphQLLocalDateRangeInput(Map.of())); + return list; + } + + @ParameterizedTest + @MethodSource("noFilterCases") + void hasNoServiceDateFilter(GraphQLTypes.GraphQLLocalDateRangeInput input) { + assertFalse(LocalDateRangeMapper.hasServiceDateFilter(input)); + } + + public static List> hasFilterCases() { + return List.of(Map.of("start", DATE), Map.of("end", DATE), Map.of("start", DATE, "end", DATE)); + } + + @ParameterizedTest + @MethodSource("hasFilterCases") + void hasServiceDateFilter(Map params) { + var input = new GraphQLTypes.GraphQLLocalDateRangeInput(params); + assertTrue(LocalDateRangeMapper.hasServiceDateFilter(input)); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/model/LocalDateRangeTest.java b/src/test/java/org/opentripplanner/apis/gtfs/model/LocalDateRangeTest.java new file mode 100644 index 00000000000..e675a4c8772 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/model/LocalDateRangeTest.java @@ -0,0 +1,22 @@ +package org.opentripplanner.apis.gtfs.model; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.time.LocalDate; +import org.junit.jupiter.api.Test; + +class LocalDateRangeTest { + + private static final LocalDate DATE = LocalDate.parse("2024-06-01"); + + @Test + void unlimited() { + assertFalse(new LocalDateRange(DATE, DATE).unlimited()); + } + + @Test + void limited() { + assertFalse(new LocalDateRange(DATE, null).unlimited()); + assertFalse(new LocalDateRange(null, DATE).unlimited()); + } +} diff --git a/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java b/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java index 42330652410..b72cb6e5a0d 100644 --- a/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java @@ -3,29 +3,19 @@ import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder; import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import graphql.ExecutionInput; import graphql.execution.ExecutionContext; import graphql.execution.ExecutionId; import graphql.schema.DataFetchingEnvironmentImpl; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; import java.util.Map; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.framework.i18n.TranslatedString; class GraphQLUtilsTest { - private static final LocalDate DATE = LocalDate.parse("2024-05-27"); - static final ExecutionContext executionContext; static { @@ -132,28 +122,4 @@ void testGetLocaleWithLocalContextLocale() { assertEquals(frenchLocale, locale); } - - public static List noFilterCases() { - var list = new ArrayList(); - list.add(null); - list.add(new GraphQLTypes.GraphQLLocalDateRangeInput(Map.of())); - return list; - } - - @ParameterizedTest - @MethodSource("noFilterCases") - void hasNoServiceDateFilter(GraphQLTypes.GraphQLLocalDateRangeInput input) { - assertFalse(org.opentripplanner.apis.gtfs.GraphQLUtils.hasServiceDateFilter(input)); - } - - public static List> hasFilterCases() { - return List.of(Map.of("start", DATE), Map.of("end", DATE), Map.of("start", DATE, "end", DATE)); - } - - @ParameterizedTest - @MethodSource("hasFilterCases") - void hasServiceDateFilter(Map params) { - var input = new GraphQLTypes.GraphQLLocalDateRangeInput(params); - assertTrue(org.opentripplanner.apis.gtfs.GraphQLUtils.hasServiceDateFilter(input)); - } } From a83a9e820481df8bda3f37e799aa30626aa971f6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 2 Jul 2024 16:20:33 +0200 Subject: [PATCH 041/148] Update src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java Co-authored-by: Joel Lappalainen --- .../opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index 82d67cae7f3..e7dfa1bcf07 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -14,7 +14,7 @@ /** * Encapsulates the logic to filter patterns by the service dates that they operate on. It also - * has a method to filter routes by checking if their patterns operate on the required days + * has a method to filter routes by checking if their patterns operate on the required days. *

* Once a more complete filtering engine is in place in the core data model, this code should be * there rather than a separate class in the API package. From b88a35e69b1b576ac2930fd511f1a6298dc2dfb0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 2 Jul 2024 16:21:10 +0200 Subject: [PATCH 042/148] Update error message --- .../opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index e7dfa1bcf07..1f2ef45dfd5 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -39,7 +39,7 @@ public class PatternByServiceDatesFilter { this.range = range; if (range.unlimited()) { - throw new IllegalArgumentException("startInclusive and endExclusive cannot be both null"); + throw new IllegalArgumentException("start and end cannot be both null"); } else if (range.startBeforeEnd()) { throw new IllegalArgumentException("start must be before end"); } From 2e22b5e28b14d8cfe510a289ab812b26a3e13488 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 3 Jul 2024 17:37:56 +0200 Subject: [PATCH 043/148] Clean up after merge --- .../opentripplanner/apis/gtfs/schema.graphqls | 110 ++++++++++++------ .../apis/gtfs/queries/routes-extended.graphql | 30 ++--- 2 files changed, 91 insertions(+), 49 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index eb3c631ec3c..0fcb76a211a 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -469,18 +469,18 @@ type FareProductUse { **Illustration** ```yaml itinerary: - leg1: - fareProducts: - id: "AAA" // id of a FareProductUse instance - product: - id: "day-pass" // product id - name: "Day Pass" - leg2: - fareProducts: - id: "AAA" // identical to leg1. the passenger needs to buy ONE pass, not two. - product: - id: "day-pass" // product id - name: "Day Pass" + leg1: + fareProducts: + id: "AAA" // id of a FareProductUse instance + product: + id: "day-pass" // product id + name: "Day Pass" + leg2: + fareProducts: + id: "AAA" // identical to leg1. the passenger needs to buy ONE pass, not two. + product: + id: "day-pass" // product id + name: "Day Pass" ``` **It is the responsibility of the API consumers to display the day pass as a product for the @@ -494,18 +494,18 @@ type FareProductUse { **Illustration** ```yaml itinerary: - leg1: - fareProducts: - id: "AAA" // id of a FareProductUse instance, not product id - product: - id: "single-ticket" // product id - name: "Single Ticket" - leg2: - fareProducts: - id: "BBB" // different to leg1. the passenger needs to buy two single tickets. - product: - id: "single-ticket" // product id - name: "Single Ticket" + leg1: + fareProducts: + id: "AAA" // id of a FareProductUse instance, not product id + product: + id: "single-ticket" // product id + name: "Single Ticket" + leg2: + fareProducts: + id: "BBB" // different to leg1. the passenger needs to buy two single tickets. + product: + id: "single-ticket" // product id + name: "Single Ticket" ``` """ id: String! @@ -588,8 +588,8 @@ type Itinerary { How many transfers are part of this itinerary. Notes: - - Interlined/stay-seated transfers do not increase this count. - - Transferring from a flex to a fixed schedule trip and vice versa increases this count. + - Interlined/stay-seated transfers do not increase this count. + - Transferring from a flex to a fixed schedule trip and vice versa increases this count. """ numberOfTransfers: Int! "Time when the user leaves from the origin." @@ -1618,9 +1618,17 @@ type QueryType { "Only return routes with these feedIds" feeds: [String], "Only return routes with these ids" - ids: [String], + ids: [String] @deprecated(reason : "Since it is hard to reason about the ID filter being combined with others in this resolver, it will be moved to a separate one."), "Query routes by this name" name: String, + """ + Only include routes whose pattern operates on at least one service date specified by this filter. + + **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. + """ + serviceDates: LocalDateRangeInput, "Only include routes, which use one of these modes" transportModes: [Mode] ): [Route] @@ -1843,7 +1851,16 @@ type Route implements Node { "Transport mode of this route, e.g. `BUS`" mode: TransitMode "List of patterns which operate on this route" - patterns: [Pattern] + patterns( + """ + Filter patterns by the service dates they operate on. + + **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. + """ + serviceDates: LocalDateRangeInput + ): [Pattern] "Short name of the route, usually a line number, e.g. 550" shortName: String """ @@ -1889,8 +1906,8 @@ type RouteType { """ GTFS Route type. For the list of possible values, see: - https://developers.google.com/transit/gtfs/reference/#routestxt and - https://developers.google.com/transit/gtfs/reference/extended-route-types + https://developers.google.com/transit/gtfs/reference/#routestxt and + https://developers.google.com/transit/gtfs/reference/extended-route-types """ routeType: Int! """ @@ -3496,6 +3513,13 @@ scalar GeoJson @specifiedBy(url : "https://www.rfcreader.com/#rfc7946") scalar Grams +""" +An ISO-8601-formatted local date, i.e. `2024-05-24` for the 24th of May, 2024. + +ISO-8601 allows many different date formats, however only the most common one - `yyyy-MM-dd` - is accepted. +""" +scalar LocalDate @specifiedBy(url : "https://www.iso.org/standard/70907.html") + "A IETF BCP 47 language tag" scalar Locale @specifiedBy(url : "https://www.rfcreader.com/#rfc5646") @@ -3615,6 +3639,7 @@ input BicycleWalkPreferencesInput { "Costs related to walking a bicycle." cost: BicycleWalkPreferencesCostInput """ + " How long it takes to hop on or off a bicycle when switching to walking the bicycle or when getting on the bicycle again. However, this is not applied when getting on a rented bicycle for the first time or off the bicycle when returning the bicycle. @@ -3836,6 +3861,23 @@ input InputUnpreferred { useUnpreferredRoutesPenalty: Int @deprecated(reason : "Use unpreferredCost instead") } +"Filters an entity by a date range." +input LocalDateRangeInput { + """ + **Exclusive** end date of the filter. This means that if you want a time window from Sunday to + Sunday, `end` must be on Monday. + + If `null` this means that no end filter is applied and all entities that are after or on `start` + are selected. + """ + end: LocalDate + """ + **Inclusive** start date of the filter. If `null` this means that no `start` filter is applied and all + dates that are before `end` are selected. + """ + start: LocalDate +} + """ The filter definition to include or exclude parking facilities used during routing. @@ -3843,11 +3885,11 @@ Logically, the filter algorithm work as follows: - The starting point is the set of all facilities, lets call it `A`. - Then all `select` filters are applied to `A`, potentially reducing the number of facilities used. - Let's call the result of this `B`. - An empty `select` will lead to `A` being equal to `B`. +Let's call the result of this `B`. +An empty `select` will lead to `A` being equal to `B`. - Lastly, the `not` filters are applied to `B`, reducing the set further. - Lets call this final set `C`. - An empty `not` will lead to `B` being equal to `C`. +Lets call this final set `C`. +An empty `not` will lead to `B` being equal to `C`. - The remaining parking facilities in `C` are used for routing. """ input ParkingFilter { diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql index 0251245d183..7f5c68961aa 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/routes-extended.graphql @@ -1,17 +1,17 @@ { - routes { - longName - shortName - gtfsId - agency { - gtfsId - name - } - mode - sortOrder - bikesAllowed - patterns(serviceDates: {start: "2024-05-23" end: "2024-05-30"}) { - name - } + routes { + longName + shortName + gtfsId + agency { + gtfsId + name } -} \ No newline at end of file + mode + sortOrder + bikesAllowed + patterns(serviceDates: { start: "2024-05-23", end: "2024-05-30" }) { + name + } + } +} From 67c32797197bfcaeb940aa330d3a2922eaa15f83 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 3 Jul 2024 17:45:14 +0200 Subject: [PATCH 044/148] Fix indentation --- .../opentripplanner/apis/gtfs/schema.graphqls | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 0fcb76a211a..1a43a763ea9 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -469,18 +469,18 @@ type FareProductUse { **Illustration** ```yaml itinerary: - leg1: - fareProducts: - id: "AAA" // id of a FareProductUse instance - product: - id: "day-pass" // product id - name: "Day Pass" - leg2: - fareProducts: - id: "AAA" // identical to leg1. the passenger needs to buy ONE pass, not two. - product: - id: "day-pass" // product id - name: "Day Pass" + leg1: + fareProducts: + id: "AAA" // id of a FareProductUse instance + product: + id: "day-pass" // product id + name: "Day Pass" + leg2: + fareProducts: + id: "AAA" // identical to leg1. the passenger needs to buy ONE pass, not two. + product: + id: "day-pass" // product id + name: "Day Pass" ``` **It is the responsibility of the API consumers to display the day pass as a product for the @@ -494,18 +494,18 @@ type FareProductUse { **Illustration** ```yaml itinerary: - leg1: - fareProducts: - id: "AAA" // id of a FareProductUse instance, not product id - product: - id: "single-ticket" // product id - name: "Single Ticket" - leg2: - fareProducts: - id: "BBB" // different to leg1. the passenger needs to buy two single tickets. - product: - id: "single-ticket" // product id - name: "Single Ticket" + leg1: + fareProducts: + id: "AAA" // id of a FareProductUse instance, not product id + product: + id: "single-ticket" // product id + name: "Single Ticket" + leg2: + fareProducts: + id: "BBB" // different to leg1. the passenger needs to buy two single tickets. + product: + id: "single-ticket" // product id + name: "Single Ticket" ``` """ id: String! @@ -588,8 +588,8 @@ type Itinerary { How many transfers are part of this itinerary. Notes: - - Interlined/stay-seated transfers do not increase this count. - - Transferring from a flex to a fixed schedule trip and vice versa increases this count. + - Interlined/stay-seated transfers do not increase this count. + - Transferring from a flex to a fixed schedule trip and vice versa increases this count. """ numberOfTransfers: Int! "Time when the user leaves from the origin." @@ -1906,8 +1906,8 @@ type RouteType { """ GTFS Route type. For the list of possible values, see: - https://developers.google.com/transit/gtfs/reference/#routestxt and - https://developers.google.com/transit/gtfs/reference/extended-route-types + https://developers.google.com/transit/gtfs/reference/#routestxt and + https://developers.google.com/transit/gtfs/reference/extended-route-types """ routeType: Int! """ @@ -3639,7 +3639,6 @@ input BicycleWalkPreferencesInput { "Costs related to walking a bicycle." cost: BicycleWalkPreferencesCostInput """ - " How long it takes to hop on or off a bicycle when switching to walking the bicycle or when getting on the bicycle again. However, this is not applied when getting on a rented bicycle for the first time or off the bicycle when returning the bicycle. @@ -3885,11 +3884,11 @@ Logically, the filter algorithm work as follows: - The starting point is the set of all facilities, lets call it `A`. - Then all `select` filters are applied to `A`, potentially reducing the number of facilities used. -Let's call the result of this `B`. -An empty `select` will lead to `A` being equal to `B`. + Let's call the result of this `B`. + An empty `select` will lead to `A` being equal to `B`. - Lastly, the `not` filters are applied to `B`, reducing the set further. -Lets call this final set `C`. -An empty `not` will lead to `B` being equal to `C`. + Lets call this final set `C`. + An empty `not` will lead to `B` being equal to `C`. - The remaining parking facilities in `C` are used for routing. """ input ParkingFilter { From 361ff9130c759254e98a2091c80dd312795c68d2 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 4 Jul 2024 16:28:27 +0200 Subject: [PATCH 045/148] Move helper code into utils --- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 4 ++-- .../opentripplanner/apis/gtfs/datafetchers/RouteImpl.java | 4 ++-- .../time/LocalDateRangeUtil.java} | 4 ++-- .../apis/gtfs/mapping/LocalDateRangeMapperTest.java | 5 +++-- 4 files changed, 9 insertions(+), 8 deletions(-) rename src/main/java/org/opentripplanner/apis/gtfs/{mapping/LocalDateRangeMapper.java => support/time/LocalDateRangeUtil.java} (86%) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index b22310d1da3..d0963361d11 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -31,9 +31,9 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLQueryTypeStopsByRadiusArgs; -import org.opentripplanner.apis.gtfs.mapping.LocalDateRangeMapper; import org.opentripplanner.apis.gtfs.mapping.routerequest.LegacyRouteRequestMapper; import org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapper; +import org.opentripplanner.apis.gtfs.support.time.LocalDateRangeUtil; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.ext.fares.impl.GtfsFaresService; import org.opentripplanner.ext.fares.model.FareRuleSet; @@ -612,7 +612,7 @@ public DataFetcher> routes() { ); } - if (LocalDateRangeMapper.hasServiceDateFilter(args.getGraphQLServiceDates())) { + if (LocalDateRangeUtil.hasServiceDateFilter(args.getGraphQLServiceDates())) { var filter = new PatternByServiceDatesFilter(args.getGraphQLServiceDates(), transitService); routeStream = filter.filterRoutes(routeStream).stream(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java index 921f85ff813..a3f557951f0 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RouteImpl.java @@ -15,7 +15,7 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLBikesAllowed; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLTransitMode; import org.opentripplanner.apis.gtfs.mapping.BikesAllowedMapper; -import org.opentripplanner.apis.gtfs.mapping.LocalDateRangeMapper; +import org.opentripplanner.apis.gtfs.support.time.LocalDateRangeUtil; import org.opentripplanner.routing.alertpatch.EntitySelector; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.services.TransitAlertService; @@ -182,7 +182,7 @@ public DataFetcher> patterns() { var args = new GraphQLTypes.GraphQLRoutePatternsArgs(environment.getArguments()); - if (LocalDateRangeMapper.hasServiceDateFilter(args.getGraphQLServiceDates())) { + if (LocalDateRangeUtil.hasServiceDateFilter(args.getGraphQLServiceDates())) { var filter = new PatternByServiceDatesFilter(args.getGraphQLServiceDates(), transitService); return filter.filterPatterns(patterns); } else { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/support/time/LocalDateRangeUtil.java similarity index 86% rename from src/main/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapper.java rename to src/main/java/org/opentripplanner/apis/gtfs/support/time/LocalDateRangeUtil.java index 56064ea37dc..b4545f10658 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/support/time/LocalDateRangeUtil.java @@ -1,9 +1,9 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.support.time; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.model.LocalDateRange; -public class LocalDateRangeMapper { +public class LocalDateRangeUtil { /** * Checks if a service date filter input has at least one filter set. If both start and end are diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java index eba38c0a180..81b3b283151 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.support.time.LocalDateRangeUtil; class LocalDateRangeMapperTest { @@ -25,7 +26,7 @@ public static List noFilterCases() { @ParameterizedTest @MethodSource("noFilterCases") void hasNoServiceDateFilter(GraphQLTypes.GraphQLLocalDateRangeInput input) { - assertFalse(LocalDateRangeMapper.hasServiceDateFilter(input)); + assertFalse(LocalDateRangeUtil.hasServiceDateFilter(input)); } public static List> hasFilterCases() { @@ -36,6 +37,6 @@ public static List> hasFilterCases() { @MethodSource("hasFilterCases") void hasServiceDateFilter(Map params) { var input = new GraphQLTypes.GraphQLLocalDateRangeInput(params); - assertTrue(LocalDateRangeMapper.hasServiceDateFilter(input)); + assertTrue(LocalDateRangeUtil.hasServiceDateFilter(input)); } } From b6de8304340acb952d88a46cd9589f569d3c7db9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 5 Jul 2024 22:46:04 +0200 Subject: [PATCH 046/148] Rename method --- .../apis/gtfs/PatternByServiceDatesFilterTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java index 57f399ad821..f01bac12006 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilterTest.java @@ -122,7 +122,7 @@ static List ranges() { @ParameterizedTest @MethodSource("ranges") void filterPatterns(LocalDate start, LocalDate end, FilterExpectation expectation) { - var filter = mockFilter(start, end); + var filter = defaultFilter(start, end); var filterInput = List.of(PATTERN_1); var filterOutput = filter.filterPatterns(filterInput); @@ -137,7 +137,7 @@ void filterPatterns(LocalDate start, LocalDate end, FilterExpectation expectatio @ParameterizedTest @MethodSource("ranges") void filterRoutes(LocalDate start, LocalDate end, FilterExpectation expectation) { - var filter = mockFilter(start, end); + var filter = defaultFilter(start, end); var filterInput = List.of(ROUTE_1); var filterOutput = filter.filterRoutes(filterInput.stream()); @@ -149,7 +149,7 @@ void filterRoutes(LocalDate start, LocalDate end, FilterExpectation expectation) } } - private static PatternByServiceDatesFilter mockFilter(LocalDate start, LocalDate end) { + private static PatternByServiceDatesFilter defaultFilter(LocalDate start, LocalDate end) { return new PatternByServiceDatesFilter( new LocalDateRange(start, end), route -> List.of(PATTERN_1), From 614434fa0b772aea3378b1ee9e2c1ae53f611bec Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 5 Jul 2024 22:54:34 +0200 Subject: [PATCH 047/148] Move code inside date range in order to remove feature envy --- .../apis/gtfs/PatternByServiceDatesFilter.java | 11 +---------- .../apis/gtfs/model/LocalDateRange.java | 14 +++++++++++++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java index 1f2ef45dfd5..8eecfe6273b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/PatternByServiceDatesFilter.java @@ -82,16 +82,7 @@ private boolean hasServicesOnDate(TripPattern pattern) { .anyMatch(trip -> { var dates = getServiceDatesForTrip.apply(trip); - return dates - .stream() - .anyMatch(date -> - ( - range.startInclusive() == null || - date.isEqual(range.startInclusive()) || - date.isAfter(range.startInclusive()) - ) && - (range.endExclusive() == null || date.isBefore(range.endExclusive())) - ); + return dates.stream().anyMatch(range::contains); }); } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java b/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java index 83e0c93b5bd..d466534e65c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java @@ -15,9 +15,21 @@ public boolean unlimited() { } /** - * Is the start date before the end ( + * Is the start date before the end? */ public boolean startBeforeEnd() { return startInclusive != null && endExclusive != null && startInclusive.isAfter(endExclusive); } + + /** + * Is the given LocalDate instance inside of this date range? + */ + public boolean contains(LocalDate date) { + return ( + ( + startInclusive() == null || date.isEqual(startInclusive()) || date.isAfter(startInclusive()) + ) && + (endExclusive() == null || date.isBefore(endExclusive())) + ); + } } From b617f95f7e3a525fd7299eff3dd433fbe28a934e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 8 Jul 2024 11:18:50 +0200 Subject: [PATCH 048/148] Rework test --- .../apis/gtfs/model/LocalDateRangeTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/model/LocalDateRangeTest.java b/src/test/java/org/opentripplanner/apis/gtfs/model/LocalDateRangeTest.java index e675a4c8772..5d079f47459 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/model/LocalDateRangeTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/model/LocalDateRangeTest.java @@ -1,6 +1,7 @@ package org.opentripplanner.apis.gtfs.model; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.LocalDate; import org.junit.jupiter.api.Test; @@ -10,13 +11,14 @@ class LocalDateRangeTest { private static final LocalDate DATE = LocalDate.parse("2024-06-01"); @Test - void unlimited() { + void limited() { assertFalse(new LocalDateRange(DATE, DATE).unlimited()); + assertFalse(new LocalDateRange(DATE, null).unlimited()); + assertFalse(new LocalDateRange(null, DATE).unlimited()); } @Test - void limited() { - assertFalse(new LocalDateRange(DATE, null).unlimited()); - assertFalse(new LocalDateRange(null, DATE).unlimited()); + void unlimited() { + assertTrue(new LocalDateRange(null, null).unlimited()); } } From dbdc80bf95aabdd3468a0df6fe6f2bd56b3456bd Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 3 Jul 2024 16:06:10 +0200 Subject: [PATCH 049/148] Parse parking lots from NeTEx --- .../netex/index/NetexEntityIndex.java | 14 +++- .../api/NetexEntityIndexReadOnlyView.java | 4 + .../netex/loader/parser/NetexParser.java | 6 +- .../netex/loader/parser/SiteFrameParser.java | 12 ++- .../netex/mapping/NetexMapper.java | 8 ++ .../netex/mapping/VehicleParkingMapper.java | 78 +++++++++++++++++++ .../vehicle_parking/VehicleParking.java | 1 + 7 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java diff --git a/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java b/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java index 0ca734d5b6a..9841765f3a3 100644 --- a/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java +++ b/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java @@ -1,6 +1,9 @@ package org.opentripplanner.netex.index; +import com.google.common.collect.ImmutableSet; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import org.opentripplanner.netex.index.api.NetexEntityIndexReadOnlyView; import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMap; import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMapById; @@ -28,6 +31,7 @@ import org.rutebanken.netex.model.OperatingDay; import org.rutebanken.netex.model.OperatingPeriod_VersionStructure; import org.rutebanken.netex.model.Operator; +import org.rutebanken.netex.model.Parking; import org.rutebanken.netex.model.Quay; import org.rutebanken.netex.model.Route; import org.rutebanken.netex.model.ServiceJourney; @@ -97,8 +101,9 @@ public class NetexEntityIndex { public final HierarchicalVersionMapById stopPlaceById; public final HierarchicalVersionMapById tariffZonesById; public final HierarchicalMapById brandingById; + public final Set parkings; - // Relations between entities - The Netex XML sometimes rely on the the + // Relations between entities - The Netex XML sometimes relies on the // nested structure of the XML document, rater than explicit references. // Since we throw away the document we need to keep track of these. @@ -142,6 +147,7 @@ public NetexEntityIndex() { this.tariffZonesById = new HierarchicalVersionMapById<>(); this.brandingById = new HierarchicalMapById<>(); this.timeZone = new HierarchicalElement<>(); + this.parkings = new HashSet<>(0); } /** @@ -184,6 +190,7 @@ public NetexEntityIndex(NetexEntityIndex parent) { this.tariffZonesById = new HierarchicalVersionMapById<>(parent.tariffZonesById); this.brandingById = new HierarchicalMapById<>(parent.brandingById); this.timeZone = new HierarchicalElement<>(parent.timeZone); + this.parkings = new HashSet<>(parent.parkings); } /** @@ -353,6 +360,11 @@ public ReadOnlyHierarchicalVersionMapById getStopPlaceById() { return stopPlaceById; } + @Override + public ImmutableSet getParkings() { + return ImmutableSet.copyOf(parkings); + } + @Override public ReadOnlyHierarchicalVersionMapById getTariffZonesById() { return tariffZonesById; diff --git a/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java b/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java index 3c7bc98b36a..9229f40c7f8 100644 --- a/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java +++ b/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java @@ -1,5 +1,6 @@ package org.opentripplanner.netex.index.api; +import com.google.common.collect.ImmutableSet; import java.util.Collection; import org.rutebanken.netex.model.Authority; import org.rutebanken.netex.model.Branding; @@ -19,6 +20,7 @@ import org.rutebanken.netex.model.OperatingDay; import org.rutebanken.netex.model.OperatingPeriod_VersionStructure; import org.rutebanken.netex.model.Operator; +import org.rutebanken.netex.model.Parking; import org.rutebanken.netex.model.Quay; import org.rutebanken.netex.model.Route; import org.rutebanken.netex.model.ServiceJourney; @@ -80,6 +82,8 @@ public interface NetexEntityIndexReadOnlyView { ReadOnlyHierarchicalVersionMapById getStopPlaceById(); + ImmutableSet getParkings(); + ReadOnlyHierarchicalVersionMapById getTariffZonesById(); ReadOnlyHierarchicalMapById getBrandingById(); diff --git a/src/main/java/org/opentripplanner/netex/loader/parser/NetexParser.java b/src/main/java/org/opentripplanner/netex/loader/parser/NetexParser.java index 54b0043e072..3c24562ef6f 100644 --- a/src/main/java/org/opentripplanner/netex/loader/parser/NetexParser.java +++ b/src/main/java/org/opentripplanner/netex/loader/parser/NetexParser.java @@ -16,7 +16,7 @@ abstract class NetexParser { /** - * Currently a lot of elements on a frame is skipped. If any of these elements are pressent we + * Currently a lot of elements on a frame is skipped. If any of these elements are present we * print a warning for elements that might be relevant for OTP and an info message for none * relevant elements. */ @@ -39,10 +39,10 @@ static void verifyCommonUnusedPropertiesIsNotSet(Logger log, VersionFrame_Versio /** * Log a warning for Netex elements which is not mapped. There might be something wrong with the * data or there might be something wrong with the Netex data import(ignoring these elements). The - * element should be relevant to OTP. OTP do not support Netex 100%, but elements in Nordic + * element should be relevant to OTP. OTP does not support NeTEx 100%, but elements in the Nordic * profile, see https://enturas.atlassian.net/wiki/spaces/PUBLIC/overview should be supported. *

- * If you get this warning and think the element should be mapped, please feel free to report an + * If you see this warning and think the element should be mapped, please feel free to report an * issue on GitHub. */ static void warnOnMissingMapping(Logger log, Object rel) { diff --git a/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java b/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java index 8cbe0c8aee6..7651bbb39d9 100644 --- a/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java +++ b/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java @@ -9,6 +9,7 @@ import org.opentripplanner.netex.support.JAXBUtils; import org.rutebanken.netex.model.FlexibleStopPlace; import org.rutebanken.netex.model.GroupOfStopPlaces; +import org.rutebanken.netex.model.Parking; import org.rutebanken.netex.model.Quay; import org.rutebanken.netex.model.Quays_RelStructure; import org.rutebanken.netex.model.Site_VersionFrameStructure; @@ -35,6 +36,7 @@ class SiteFrameParser extends NetexParser { private final Collection tariffZones = new ArrayList<>(); private final Collection quays = new ArrayList<>(); + private final Collection parkings = new ArrayList<>(0); @Override public void parse(Site_VersionFrameStructure frame) { @@ -51,6 +53,10 @@ public void parse(Site_VersionFrameStructure frame) { if (frame.getTariffZones() != null) { parseTariffZones(frame.getTariffZones().getTariffZone()); } + + if (frame.getParkings() != null) { + parseParkings(frame.getParkings().getParking()); + } // Keep list sorted alphabetically warnOnMissingMapping(LOG, frame.getAccesses()); warnOnMissingMapping(LOG, frame.getAddresses()); @@ -59,7 +65,6 @@ public void parse(Site_VersionFrameStructure frame) { warnOnMissingMapping(LOG, frame.getCheckConstraintDelays()); warnOnMissingMapping(LOG, frame.getCheckConstraintThroughputs()); warnOnMissingMapping(LOG, frame.getNavigationPaths()); - warnOnMissingMapping(LOG, frame.getParkings()); warnOnMissingMapping(LOG, frame.getPathJunctions()); warnOnMissingMapping(LOG, frame.getPathLinks()); warnOnMissingMapping(LOG, frame.getPointsOfInterest()); @@ -79,6 +84,7 @@ void setResultOnIndex(NetexEntityIndex netexIndex) { netexIndex.stopPlaceById.addAll(stopPlaces); netexIndex.tariffZonesById.addAll(tariffZones); netexIndex.quayById.addAll(quays); + netexIndex.parkings.addAll(parkings); } private void parseFlexibleStopPlaces(Collection flexibleStopPlacesList) { @@ -89,6 +95,10 @@ private void parseGroupsOfStopPlaces(Collection groupsOfStopP groupsOfStopPlaces.addAll(groupsOfStopPlacesList); } + private void parseParkings(List parking) { + parkings.addAll(parking); + } + private void parseStopPlaces(List> stopPlaceList) { for (JAXBElement jaxBStopPlace : stopPlaceList) { StopPlace stopPlace = (StopPlace) jaxBStopPlace.getValue(); diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java index c3c9ad2d0ae..85ab33fabc5 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java @@ -200,6 +200,8 @@ public void mapNetexToOtp(NetexEntityIndexReadOnlyView netexIndex) { mapNoticeAssignments(); addEntriesToGroupMapperForPostProcessingLater(); + + mapParkings(); } /* PRIVATE METHODS */ @@ -511,6 +513,12 @@ private void mapNoticeAssignments() { } } + private void mapParkings() { + var mapper = new VehicleParkingMapper(idFactory); + var parkingLots = mapper.map(currentNetexIndex.getParkings()); + System.out.print(parkingLots); + } + private void addEntriesToGroupMapperForPostProcessingLater() { if (level != 0) { groupMapper.addInterchange( diff --git a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java new file mode 100644 index 00000000000..11003ea2561 --- /dev/null +++ b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java @@ -0,0 +1,78 @@ +package org.opentripplanner.netex.mapping; + +import static org.rutebanken.netex.model.ParkingVehicleEnumeration.CYCLE; +import static org.rutebanken.netex.model.ParkingVehicleEnumeration.E_CYCLE; +import static org.rutebanken.netex.model.ParkingVehicleEnumeration.PEDAL_CYCLE; + +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; +import org.opentripplanner.framework.i18n.NonLocalizedString; +import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; +import org.rutebanken.netex.model.Parking; +import org.rutebanken.netex.model.ParkingVehicleEnumeration; + +/** + * Maps from NeTEx Parking to an internal {@link VehicleParking}. + */ +class VehicleParkingMapper { + + private final FeedScopedIdFactory idFactory; + + private static final Set BICYCLE_TYPES = Set.of( + PEDAL_CYCLE, + E_CYCLE, + CYCLE + ); + + public VehicleParkingMapper(FeedScopedIdFactory idFactory) { + this.idFactory = idFactory; + } + + Collection map(Collection parkings) { + return parkings.stream().map(this::map).collect(Collectors.toUnmodifiableSet()); + } + + VehicleParking map(Parking parking) { + return VehicleParking + .builder() + .id(idFactory.createId(parking.getId())) + .name(NonLocalizedString.ofNullable(parking.getName().getValue())) + .coordinate(WgsCoordinateMapper.mapToDomain(parking.getCentroid())) + .capacity(mapCapacity(parking)) + .entrance(mapEntrance(parking)) + .build(); + } + + private VehicleParking.VehicleParkingEntranceCreator mapEntrance(Parking parking) { + return builder -> + builder + .entranceId(idFactory.createId(parking.getId() + "/entrance")) + .coordinate(WgsCoordinateMapper.mapToDomain(parking.getCentroid())) + .walkAccessible(true) + .carAccessible(true); + } + + private static VehicleParkingSpaces mapCapacity(Parking parking) { + var builder = VehicleParkingSpaces.builder(); + + // we assume that if we have bicycle in vehicle types it's a bicycle parking lot + // it's not possible in NeTEx to split the spaces between the types + var hasBikes = hasBikes(parking); + int capacity = parking.getTotalCapacity().intValue(); + + if (hasBikes) { + builder.bicycleSpaces(capacity); + } else { + builder.carSpaces(capacity); + } + + return builder.carSpaces(capacity).build(); + } + + private static boolean hasBikes(Parking parking) { + return parking.getParkingVehicleTypes().stream().anyMatch(BICYCLE_TYPES::contains); + } +} diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index 6ebd34e1287..d5dfc4ce8c1 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -308,6 +308,7 @@ public boolean equals(Object o) { public String toString() { return ToStringBuilder .of(VehicleParking.class) + .addStr("id", id.toString()) .addStr("name", name.toString()) .addObj("coordinate", coordinate) .toString(); From 8550febea5b78272777faa34f1e4a67039d2650e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 3 Jul 2024 17:29:05 +0200 Subject: [PATCH 050/148] Flesh out parsing, add test --- .../VehicleParkingGroupsLayerTest.java | 4 +- .../VehicleParkingsLayerTest.java | 4 +- .../opentripplanner/netex/NetexBundle.java | 7 +- .../netex/configure/NetexConfigure.java | 7 +- .../netex/mapping/NetexMapper.java | 8 +- .../netex/mapping/VehicleParkingMapper.java | 11 +-- .../vehicle_parking/VehicleParkingSpaces.java | 11 +++ .../opentripplanner/ConstantsForTests.java | 7 +- .../mapping/VehicleParkingMapperTest.java | 86 +++++++++++++++++++ 9 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java index 60ba9100f43..1beca037457 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingGroupsLayerTest.java @@ -125,8 +125,8 @@ public void vehicleParkingGroupGeometryTest() { assertEquals("[POINT (1.1 1.9)]", geometries.toString()); assertEquals( - "VehicleParkingAndGroup[vehicleParkingGroup=VehicleParkingGroup{name: 'groupName', coordinate: (1.9, 1.1)}, vehicleParking=[VehicleParking{name: 'name', coordinate: (2.0, 1.0)}]]", - geometries.get(0).getUserData().toString() + "VehicleParkingAndGroup[vehicleParkingGroup=VehicleParkingGroup{name: 'groupName', coordinate: (1.9, 1.1)}, vehicleParking=[VehicleParking{id: 'F:id', name: 'name', coordinate: (2.0, 1.0)}]]", + geometries.getFirst().getUserData().toString() ); } diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java index fdb723b3dc7..14e96e2aa28 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/vehicleparkings/VehicleParkingsLayerTest.java @@ -117,8 +117,8 @@ public void vehicleParkingGeometryTest() { assertEquals("[POINT (1 2)]", geometries.toString()); assertEquals( - "VehicleParking{name: 'default name', coordinate: (2.0, 1.0)}", - geometries.get(0).getUserData().toString() + "VehicleParking{id: 'F:id', name: 'default name', coordinate: (2.0, 1.0)}", + geometries.getFirst().getUserData().toString() ); } diff --git a/src/main/java/org/opentripplanner/netex/NetexBundle.java b/src/main/java/org/opentripplanner/netex/NetexBundle.java index 8d6f098de89..c0da007bb10 100644 --- a/src/main/java/org/opentripplanner/netex/NetexBundle.java +++ b/src/main/java/org/opentripplanner/netex/NetexBundle.java @@ -17,6 +17,7 @@ import org.opentripplanner.netex.loader.parser.NetexDocumentParser; import org.opentripplanner.netex.mapping.NetexMapper; import org.opentripplanner.netex.validation.Validator; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; import org.opentripplanner.transit.model.framework.Deduplicator; import org.rutebanken.netex.model.PublicationDeliveryStructure; import org.slf4j.Logger; @@ -46,6 +47,7 @@ public class NetexBundle implements Closeable { private final double maxStopToShapeSnapDistance; private final boolean noTransfersOnIsolatedStops; private final boolean ignoreFareFrame; + private final VehicleParkingService parkingService; /** The NeTEx entities loaded from the input files and passed on to the mapper. */ private NetexEntityIndex index = new NetexEntityIndex(); /** Report errors to issue store */ @@ -59,6 +61,7 @@ public NetexBundle( CompositeDataSource source, NetexDataSourceHierarchy hierarchy, OtpTransitServiceBuilder transitBuilder, + VehicleParkingService parkingService, Set ferryIdsNotAllowedForBicycle, double maxStopToShapeSnapDistance, boolean noTransfersOnIsolatedStops, @@ -68,6 +71,7 @@ public NetexBundle( this.source = source; this.hierarchy = hierarchy; this.transitBuilder = transitBuilder; + this.parkingService = parkingService; this.ferryIdsNotAllowedForBicycle = ferryIdsNotAllowedForBicycle; this.maxStopToShapeSnapDistance = maxStopToShapeSnapDistance; this.noTransfersOnIsolatedStops = noTransfersOnIsolatedStops; @@ -93,7 +97,8 @@ public OtpTransitServiceBuilder loadBundle( issueStore, ferryIdsNotAllowedForBicycle, maxStopToShapeSnapDistance, - noTransfersOnIsolatedStops + noTransfersOnIsolatedStops, + parkingService ); // Load data diff --git a/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java b/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java index 464c03f28e1..7224d243d77 100644 --- a/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java +++ b/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java @@ -11,6 +11,7 @@ import org.opentripplanner.netex.config.NetexFeedParameters; import org.opentripplanner.netex.loader.NetexDataSourceHierarchy; import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; import org.opentripplanner.standalone.config.BuildConfig; import org.opentripplanner.transit.service.TransitModel; @@ -46,7 +47,7 @@ public NetexModule createNetexModule( transitModel.getStopModel(), issueStore ); - netexBundles.add(netexBundle(transitServiceBuilder, it)); + netexBundles.add(netexBundle(transitServiceBuilder, it, graph.getVehicleParkingService())); } return new NetexModule( @@ -62,7 +63,8 @@ public NetexModule createNetexModule( /** public to enable testing */ public NetexBundle netexBundle( OtpTransitServiceBuilder transitServiceBuilder, - ConfiguredDataSource configuredDataSource + ConfiguredDataSource configuredDataSource, + VehicleParkingService parkingService ) { var source = (CompositeDataSource) configuredDataSource.dataSource(); var config = configuredDataSource.config(); @@ -72,6 +74,7 @@ public NetexBundle netexBundle( source, hierarchy(source, config), transitServiceBuilder, + parkingService, config.ferryIdsNotAllowedForBicycle(), buildParams.maxStopToShapeSnapDistance, config.noTransfersOnIsolatedStops(), diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java index 85ab33fabc5..d99a315a39b 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java @@ -19,6 +19,7 @@ import org.opentripplanner.netex.mapping.calendar.CalendarServiceBuilder; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; import org.opentripplanner.netex.mapping.support.NetexMapperIndexes; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; import org.opentripplanner.transit.model.basic.Notice; import org.opentripplanner.transit.model.framework.AbstractTransitEntity; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -68,6 +69,7 @@ public class NetexMapper { private final Set ferryIdsNotAllowedForBicycle; private final double maxStopToShapeSnapDistance; private final boolean noTransfersOnIsolatedStops; + private final VehicleParkingService parkingService; /** Map entries that cross reference entities within a group/operator, for example Interchanges. */ private GroupNetexMapper groupMapper; @@ -94,7 +96,8 @@ public NetexMapper( DataImportIssueStore issueStore, Set ferryIdsNotAllowedForBicycle, double maxStopToShapeSnapDistance, - boolean noTransfersOnIsolatedStops + boolean noTransfersOnIsolatedStops, + VehicleParkingService parkingService ) { this.transitBuilder = transitBuilder; this.deduplicator = deduplicator; @@ -103,6 +106,7 @@ public NetexMapper( this.ferryIdsNotAllowedForBicycle = ferryIdsNotAllowedForBicycle; this.noTransfersOnIsolatedStops = noTransfersOnIsolatedStops; this.maxStopToShapeSnapDistance = maxStopToShapeSnapDistance; + this.parkingService = parkingService; this.calendarServiceBuilder = new CalendarServiceBuilder(idFactory); this.tripCalendarBuilder = new TripCalendarBuilder(this.calendarServiceBuilder, issueStore); } @@ -516,7 +520,7 @@ private void mapNoticeAssignments() { private void mapParkings() { var mapper = new VehicleParkingMapper(idFactory); var parkingLots = mapper.map(currentNetexIndex.getParkings()); - System.out.print(parkingLots); + parkingService.updateVehicleParking(parkingLots, List.of()); } private void addEntriesToGroupMapperForPostProcessingLater() { diff --git a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java index 11003ea2561..a6ee863596e 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java @@ -42,6 +42,8 @@ VehicleParking map(Parking parking) { .name(NonLocalizedString.ofNullable(parking.getName().getValue())) .coordinate(WgsCoordinateMapper.mapToDomain(parking.getCentroid())) .capacity(mapCapacity(parking)) + .bicyclePlaces(hasBikes(parking)) + .carPlaces(!hasBikes(parking)) .entrance(mapEntrance(parking)) .build(); } @@ -57,13 +59,12 @@ private VehicleParking.VehicleParkingEntranceCreator mapEntrance(Parking parking private static VehicleParkingSpaces mapCapacity(Parking parking) { var builder = VehicleParkingSpaces.builder(); - - // we assume that if we have bicycle in vehicle types it's a bicycle parking lot - // it's not possible in NeTEx to split the spaces between the types - var hasBikes = hasBikes(parking); int capacity = parking.getTotalCapacity().intValue(); - if (hasBikes) { + // we assume that if we have bicycle in vehicle types it's a bicycle parking lot + // it's not possible in NeTEx to split the spaces between the types, so if you want that + // you have to define two parking lots with the same coordinates + if (hasBikes(parking)) { builder.bicycleSpaces(capacity); } else { builder.carSpaces(capacity); diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingSpaces.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingSpaces.java index de25fc75521..8b21cc21f2c 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingSpaces.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingSpaces.java @@ -2,6 +2,7 @@ import java.io.Serializable; import java.util.Objects; +import org.opentripplanner.framework.tostring.ToStringBuilder; /** * The number of spaces by type. {@code null} if unknown. @@ -70,6 +71,16 @@ public boolean equals(Object o) { ); } + @Override + public String toString() { + return ToStringBuilder + .of(VehicleParkingSpaces.class) + .addNum("carSpaces", carSpaces) + .addNum("wheelchairAccessibleCarSpaces", wheelchairAccessibleCarSpaces) + .addNum("bicycleSpaces", bicycleSpaces) + .toString(); + } + public static class VehicleParkingSpacesBuilder { private Integer bicycleSpaces; diff --git a/src/test/java/org/opentripplanner/ConstantsForTests.java b/src/test/java/org/opentripplanner/ConstantsForTests.java index e74c66e527b..b002d7a3c41 100644 --- a/src/test/java/org/opentripplanner/ConstantsForTests.java +++ b/src/test/java/org/opentripplanner/ConstantsForTests.java @@ -31,6 +31,7 @@ import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.linking.LinkingDirection; import org.opentripplanner.routing.linking.VertexLinker; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; import org.opentripplanner.service.vehiclerental.model.RentalVehicleType; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.street.StreetVehicleRentalLink; @@ -102,7 +103,8 @@ public static NetexBundle createMinimalNetexNordicBundle() { var configuredDataSource = new ConfiguredDataSource<>(dataSource, buildConfig.netexDefaults); var transitService = new OtpTransitServiceBuilder(new StopModel(), DataImportIssueStore.NOOP); - return new NetexConfigure(buildConfig).netexBundle(transitService, configuredDataSource); + return new NetexConfigure(buildConfig) + .netexBundle(transitService, configuredDataSource, new VehicleParkingService()); } public static NetexBundle createMinimalNetexEpipBundle() { @@ -114,7 +116,8 @@ public static NetexBundle createMinimalNetexEpipBundle() { var configuredDataSource = new ConfiguredDataSource<>(dataSource, buildConfig.netexDefaults); var transitService = new OtpTransitServiceBuilder(new StopModel(), DataImportIssueStore.NOOP); - return new NetexConfigure(buildConfig).netexBundle(transitService, configuredDataSource); + return new NetexConfigure(buildConfig) + .netexBundle(transitService, configuredDataSource, new VehicleParkingService()); } /** diff --git a/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java new file mode 100644 index 00000000000..336281fe53d --- /dev/null +++ b/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java @@ -0,0 +1,86 @@ +package org.opentripplanner.netex.mapping; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.rutebanken.netex.model.ParkingVehicleEnumeration.AGRICULTURAL_VEHICLE; +import static org.rutebanken.netex.model.ParkingVehicleEnumeration.CAR; +import static org.rutebanken.netex.model.ParkingVehicleEnumeration.CAR_WITH_CARAVAN; +import static org.rutebanken.netex.model.ParkingVehicleEnumeration.CYCLE; +import static org.rutebanken.netex.model.ParkingVehicleEnumeration.PEDAL_CYCLE; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; +import org.rutebanken.netex.model.LocationStructure; +import org.rutebanken.netex.model.MultilingualString; +import org.rutebanken.netex.model.Parking; +import org.rutebanken.netex.model.ParkingVehicleEnumeration; +import org.rutebanken.netex.model.SimplePoint_VersionStructure; + +class VehicleParkingMapperTest { + + private static final VehicleParkingMapper MAPPER = new VehicleParkingMapper( + new FeedScopedIdFactory("parking") + ); + + public static List> carCases() { + return List.of(Set.of(), Set.of(CAR, AGRICULTURAL_VEHICLE)); + } + + @ParameterizedTest + @MethodSource("carCases") + void mapCarLot(Set vehicleTypes) { + var vp = MAPPER.map(parking(vehicleTypes)); + assertEquals("A name", vp.getName().toString()); + assertCommonProperties(vp); + assertTrue(vp.hasAnyCarPlaces()); + assertEquals(VehicleParkingSpaces.builder().carSpaces(10).build(), vp.getCapacity()); + } + + public static List> bicycleCases() { + return List.of(Set.of(CYCLE), Set.of(PEDAL_CYCLE, CAR, CAR_WITH_CARAVAN)); + } + + @ParameterizedTest + @MethodSource("bicycleCases") + void mapBicycleLot(Set vehicleTypes) { + var vp = MAPPER.map(parking(vehicleTypes)); + assertEquals("A name", vp.getName().toString()); + assertCommonProperties(vp); + assertTrue(vp.hasBicyclePlaces()); + assertEquals(VehicleParkingSpaces.builder().bicycleSpaces(10).build(), vp.getCapacity()); + } + + private static void assertCommonProperties(VehicleParking vp) { + assertEquals(new WgsCoordinate(10, 20), vp.getCoordinate()); + assertEquals( + "[VehicleParkingEntrance{entranceId: parking:LOT1/entrance, coordinate: (10.0, 20.0), carAccessible: true, walkAccessible: true}]", + vp.getEntrances().toString() + ); + } + + private static Parking parking(Set vehicleTypes) { + var name = new MultilingualString(); + name.setValue("A name"); + var point = new SimplePoint_VersionStructure(); + var loc = new LocationStructure(); + loc.setLatitude(new BigDecimal(10)); + loc.setLongitude(new BigDecimal(20)); + point.setLocation(loc); + + var parking = new Parking(); + parking.setId("LOT1"); + parking.setName(name); + parking.setCentroid(point); + parking.setTotalCapacity(BigInteger.TEN); + parking.getParkingVehicleTypes().addAll(vehicleTypes); + return parking; + } +} From e95b6755b9efb011713368d8dcd55faf74bf6fc6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 3 Jul 2024 17:54:23 +0200 Subject: [PATCH 051/148] Fix building of capacity --- .../org/opentripplanner/netex/mapping/VehicleParkingMapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java index a6ee863596e..15ebea466c0 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java @@ -70,7 +70,7 @@ private static VehicleParkingSpaces mapCapacity(Parking parking) { builder.carSpaces(capacity); } - return builder.carSpaces(capacity).build(); + return builder.build(); } private static boolean hasBikes(Parking parking) { From b5d492afd92c6ceee0801b553fefaa3340ae9e1c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 3 Jul 2024 22:53:31 +0200 Subject: [PATCH 052/148] Change wiring of vehicle parking --- .../org/opentripplanner/netex/NetexBundle.java | 16 ++++++++++------ .../org/opentripplanner/netex/NetexModule.java | 7 +++++++ .../netex/configure/NetexConfigure.java | 7 ++----- .../netex/mapping/NetexMapper.java | 18 ++++++------------ .../mapping/VehicleParkingMapperTest.java | 3 +-- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/NetexBundle.java b/src/main/java/org/opentripplanner/netex/NetexBundle.java index c0da007bb10..7d80f431309 100644 --- a/src/main/java/org/opentripplanner/netex/NetexBundle.java +++ b/src/main/java/org/opentripplanner/netex/NetexBundle.java @@ -3,6 +3,7 @@ import jakarta.xml.bind.JAXBException; import java.io.Closeable; import java.io.IOException; +import java.util.Collection; import java.util.List; import java.util.Set; import org.opentripplanner.datastore.api.CompositeDataSource; @@ -17,7 +18,7 @@ import org.opentripplanner.netex.loader.parser.NetexDocumentParser; import org.opentripplanner.netex.mapping.NetexMapper; import org.opentripplanner.netex.validation.Validator; -import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.transit.model.framework.Deduplicator; import org.rutebanken.netex.model.PublicationDeliveryStructure; import org.slf4j.Logger; @@ -47,7 +48,6 @@ public class NetexBundle implements Closeable { private final double maxStopToShapeSnapDistance; private final boolean noTransfersOnIsolatedStops; private final boolean ignoreFareFrame; - private final VehicleParkingService parkingService; /** The NeTEx entities loaded from the input files and passed on to the mapper. */ private NetexEntityIndex index = new NetexEntityIndex(); /** Report errors to issue store */ @@ -61,7 +61,6 @@ public NetexBundle( CompositeDataSource source, NetexDataSourceHierarchy hierarchy, OtpTransitServiceBuilder transitBuilder, - VehicleParkingService parkingService, Set ferryIdsNotAllowedForBicycle, double maxStopToShapeSnapDistance, boolean noTransfersOnIsolatedStops, @@ -71,7 +70,6 @@ public NetexBundle( this.source = source; this.hierarchy = hierarchy; this.transitBuilder = transitBuilder; - this.parkingService = parkingService; this.ferryIdsNotAllowedForBicycle = ferryIdsNotAllowedForBicycle; this.maxStopToShapeSnapDistance = maxStopToShapeSnapDistance; this.noTransfersOnIsolatedStops = noTransfersOnIsolatedStops; @@ -97,8 +95,7 @@ public OtpTransitServiceBuilder loadBundle( issueStore, ferryIdsNotAllowedForBicycle, maxStopToShapeSnapDistance, - noTransfersOnIsolatedStops, - parkingService + noTransfersOnIsolatedStops ); // Load data @@ -120,6 +117,13 @@ public void close() throws IOException { source.close(); } + /** + * Return the list of parking lots contained in the netex bundle. + */ + public Collection vehicleParkings() { + return mapper.mapVehicleParkings(); + } + /** Load all files entries in the bundle */ private void loadFileEntries() { // Load global shared files diff --git a/src/main/java/org/opentripplanner/netex/NetexModule.java b/src/main/java/org/opentripplanner/netex/NetexModule.java index b9a05d25b10..8c5c0826670 100644 --- a/src/main/java/org/opentripplanner/netex/NetexModule.java +++ b/src/main/java/org/opentripplanner/netex/NetexModule.java @@ -13,6 +13,7 @@ import org.opentripplanner.model.calendar.ServiceDateInterval; import org.opentripplanner.model.impl.OtpTransitServiceBuilder; import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingHelper; import org.opentripplanner.standalone.config.BuildConfig; import org.opentripplanner.transit.service.TransitModel; @@ -100,6 +101,12 @@ public void buildGraph() { ); transitModel.validateTimeZones(); + + var lots = netexBundle.vehicleParkings(); + graph.getVehicleParkingService().updateVehicleParking(lots, List.of()); + var linker = new VehicleParkingHelper(graph); + lots.forEach(linker::linkVehicleParkingToGraph); + } transitModel.updateCalendarServiceData(hasActiveTransit, calendarServiceData, issueStore); diff --git a/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java b/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java index 7224d243d77..464c03f28e1 100644 --- a/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java +++ b/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java @@ -11,7 +11,6 @@ import org.opentripplanner.netex.config.NetexFeedParameters; import org.opentripplanner.netex.loader.NetexDataSourceHierarchy; import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; import org.opentripplanner.standalone.config.BuildConfig; import org.opentripplanner.transit.service.TransitModel; @@ -47,7 +46,7 @@ public NetexModule createNetexModule( transitModel.getStopModel(), issueStore ); - netexBundles.add(netexBundle(transitServiceBuilder, it, graph.getVehicleParkingService())); + netexBundles.add(netexBundle(transitServiceBuilder, it)); } return new NetexModule( @@ -63,8 +62,7 @@ public NetexModule createNetexModule( /** public to enable testing */ public NetexBundle netexBundle( OtpTransitServiceBuilder transitServiceBuilder, - ConfiguredDataSource configuredDataSource, - VehicleParkingService parkingService + ConfiguredDataSource configuredDataSource ) { var source = (CompositeDataSource) configuredDataSource.dataSource(); var config = configuredDataSource.config(); @@ -74,7 +72,6 @@ public NetexBundle netexBundle( source, hierarchy(source, config), transitServiceBuilder, - parkingService, config.ferryIdsNotAllowedForBicycle(), buildParams.maxStopToShapeSnapDistance, config.noTransfersOnIsolatedStops(), diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java index d99a315a39b..e6a0d724dd4 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java @@ -19,7 +19,7 @@ import org.opentripplanner.netex.mapping.calendar.CalendarServiceBuilder; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; import org.opentripplanner.netex.mapping.support.NetexMapperIndexes; -import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.transit.model.basic.Notice; import org.opentripplanner.transit.model.framework.AbstractTransitEntity; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -69,7 +69,6 @@ public class NetexMapper { private final Set ferryIdsNotAllowedForBicycle; private final double maxStopToShapeSnapDistance; private final boolean noTransfersOnIsolatedStops; - private final VehicleParkingService parkingService; /** Map entries that cross reference entities within a group/operator, for example Interchanges. */ private GroupNetexMapper groupMapper; @@ -96,8 +95,7 @@ public NetexMapper( DataImportIssueStore issueStore, Set ferryIdsNotAllowedForBicycle, double maxStopToShapeSnapDistance, - boolean noTransfersOnIsolatedStops, - VehicleParkingService parkingService + boolean noTransfersOnIsolatedStops ) { this.transitBuilder = transitBuilder; this.deduplicator = deduplicator; @@ -106,7 +104,6 @@ public NetexMapper( this.ferryIdsNotAllowedForBicycle = ferryIdsNotAllowedForBicycle; this.noTransfersOnIsolatedStops = noTransfersOnIsolatedStops; this.maxStopToShapeSnapDistance = maxStopToShapeSnapDistance; - this.parkingService = parkingService; this.calendarServiceBuilder = new CalendarServiceBuilder(idFactory); this.tripCalendarBuilder = new TripCalendarBuilder(this.calendarServiceBuilder, issueStore); } @@ -204,8 +201,11 @@ public void mapNetexToOtp(NetexEntityIndexReadOnlyView netexIndex) { mapNoticeAssignments(); addEntriesToGroupMapperForPostProcessingLater(); + } - mapParkings(); + public Collection mapVehicleParkings() { + var mapper = new VehicleParkingMapper(idFactory); + return mapper.map(currentNetexIndex.getParkings()); } /* PRIVATE METHODS */ @@ -517,12 +517,6 @@ private void mapNoticeAssignments() { } } - private void mapParkings() { - var mapper = new VehicleParkingMapper(idFactory); - var parkingLots = mapper.map(currentNetexIndex.getParkings()); - parkingService.updateVehicleParking(parkingLots, List.of()); - } - private void addEntriesToGroupMapperForPostProcessingLater() { if (level != 0) { groupMapper.addInterchange( diff --git a/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java index 336281fe53d..98dbf661839 100644 --- a/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java +++ b/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java @@ -38,7 +38,6 @@ public static List> carCases() { @MethodSource("carCases") void mapCarLot(Set vehicleTypes) { var vp = MAPPER.map(parking(vehicleTypes)); - assertEquals("A name", vp.getName().toString()); assertCommonProperties(vp); assertTrue(vp.hasAnyCarPlaces()); assertEquals(VehicleParkingSpaces.builder().carSpaces(10).build(), vp.getCapacity()); @@ -52,13 +51,13 @@ public static List> bicycleCases() { @MethodSource("bicycleCases") void mapBicycleLot(Set vehicleTypes) { var vp = MAPPER.map(parking(vehicleTypes)); - assertEquals("A name", vp.getName().toString()); assertCommonProperties(vp); assertTrue(vp.hasBicyclePlaces()); assertEquals(VehicleParkingSpaces.builder().bicycleSpaces(10).build(), vp.getCapacity()); } private static void assertCommonProperties(VehicleParking vp) { + assertEquals("A name", vp.getName().toString()); assertEquals(new WgsCoordinate(10, 20), vp.getCoordinate()); assertEquals( "[VehicleParkingEntrance{entranceId: parking:LOT1/entrance, coordinate: (10.0, 20.0), carAccessible: true, walkAccessible: true}]", From 3133496c81fbbe7ae8af2fc6c324d8688e4a04e1 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 4 Jul 2024 00:02:15 +0200 Subject: [PATCH 053/148] Improve building of the links --- client/src/components/MapView/MapView.tsx | 2 +- client/src/util/getColorForMode.ts | 4 ++-- .../apis/vectortiles/DebugStyleSpec.java | 23 +++++++++++++++---- .../apis/vectortiles/model/StyleBuilder.java | 23 ++++++++++++++++--- .../graph_builder/GraphBuilder.java | 2 +- .../module/StreetLinkerModule.java | 22 +----------------- .../vector/vertex/VertexPropertyMapper.java | 6 +++++ .../netex/mapping/VehicleParkingMapper.java | 2 +- .../opentripplanner/routing/graph/Graph.java | 6 ----- .../vehicle_parking/VehicleParkingHelper.java | 3 +-- .../model/edge/StreetVehicleParkingLink.java | 7 +++--- .../vertex/VehicleParkingEntranceVertex.java | 16 +++++++++++++ 12 files changed, 71 insertions(+), 45 deletions(-) diff --git a/client/src/components/MapView/MapView.tsx b/client/src/components/MapView/MapView.tsx index 36d98be79b6..9c3761bb530 100644 --- a/client/src/components/MapView/MapView.tsx +++ b/client/src/components/MapView/MapView.tsx @@ -75,7 +75,7 @@ export function MapView({ }} // it's unfortunate that you have to list these layers here. // maybe there is a way around it: https://github.com/visgl/react-map-gl/discussions/2343 - interactiveLayerIds={['regular-stop', 'area-stop', 'group-stop', 'vertex', 'edge', 'link']} + interactiveLayerIds={['regular-stop', 'area-stop', 'group-stop', 'parking-vertex', 'vertex', 'edge', 'link']} onClick={showFeaturePropPopup} // put lat/long in URL and pan to it on page reload hash={true} diff --git a/client/src/util/getColorForMode.ts b/client/src/util/getColorForMode.ts index 79af525e826..ca7f9fd3474 100644 --- a/client/src/util/getColorForMode.ts +++ b/client/src/util/getColorForMode.ts @@ -1,10 +1,10 @@ import { Mode } from '../gql/graphql.ts'; export const getColorForMode = function (mode: Mode) { - if (mode === Mode.Foot) return '#444'; + if (mode === Mode.Foot) return '#7e7e7e'; if (mode === Mode.Bicycle) return '#5076D9'; if (mode === Mode.Scooter) return '#253664'; - if (mode === Mode.Car) return '#444'; + if (mode === Mode.Car) return '#191616'; if (mode === Mode.Rail) return '#86BF8B'; if (mode === Mode.Coach) return '#25642A'; if (mode === Mode.Metro) return '#D9B250'; diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java b/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java index 7741a7a58cb..21cdfee9ef7 100644 --- a/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java +++ b/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java @@ -23,6 +23,7 @@ import org.opentripplanner.street.model.edge.StreetVehicleParkingLink; import org.opentripplanner.street.model.edge.TemporaryFreeEdge; import org.opentripplanner.street.model.edge.TemporaryPartialStreetEdge; +import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; /** * A Mapbox/Mapblibre style specification for rendering debug information about transit and @@ -38,7 +39,8 @@ public class DebugStyleSpec { "© OpenStreetMap Contributors" ); private static final String MAGENTA = "#f21d52"; - private static final String GREEN = "#22DD9E"; + private static final String BRIGHT_GREEN = "#22DD9E"; + private static final String DARK_GREEN = "#136b04"; private static final String PURPLE = "#BC55F2"; private static final String BLACK = "#140d0e"; private static final int MAX_ZOOM = 23; @@ -101,7 +103,7 @@ static StyleSpec build( .ofId("link") .typeLine() .vectorSourceLayer(edges) - .lineColor(GREEN) + .lineColor(BRIGHT_GREEN) .edgeFilter( StreetTransitStopLink.class, StreetTransitEntranceLink.class, @@ -125,11 +127,24 @@ static StyleSpec build( .minZoom(15) .maxZoom(MAX_ZOOM) .intiallyHidden(), + StyleBuilder + .ofId("parking-vertex") + .typeCircle() + .vectorSourceLayer(vertices) + .vertexFilter(VehicleParkingEntranceVertex.class) + .circleStroke(BLACK, CIRCLE_STROKE) + .circleRadius( + new ZoomDependentNumber(1, List.of(new ZoomStop(13, 1.4f), new ZoomStop(MAX_ZOOM, 10))) + ) + .circleColor(DARK_GREEN) + .minZoom(13) + .maxZoom(MAX_ZOOM) + .intiallyHidden(), StyleBuilder .ofId("area-stop") .typeFill() .vectorSourceLayer(areaStops) - .fillColor(GREEN) + .fillColor(BRIGHT_GREEN) .fillOpacity(0.5f) .fillOutlineColor(BLACK) .minZoom(6) @@ -138,7 +153,7 @@ static StyleSpec build( .ofId("group-stop") .typeFill() .vectorSourceLayer(groupStops) - .fillColor(GREEN) + .fillColor(BRIGHT_GREEN) .fillOpacity(0.5f) .fillOutlineColor(BLACK) .minZoom(6) diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java b/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java index 28c1e792fd1..312eaa9e727 100644 --- a/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java +++ b/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java @@ -12,6 +12,7 @@ import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.framework.json.ObjectMappers; import org.opentripplanner.street.model.edge.Edge; +import org.opentripplanner.street.model.vertex.Vertex; /** * Builds a Maplibre/Mapbox vector tile @@ -37,6 +38,8 @@ public StyleBuilder vectorSourceLayer(VectorSourceLayer source) { return sourceLayer(source.vectorLayer()); } + + public enum LayerType { Circle, Line, @@ -192,9 +195,17 @@ public StyleBuilder intiallyHidden() { */ @SafeVarargs public final StyleBuilder edgeFilter(Class... classToFilter) { - var clazzes = Arrays.stream(classToFilter).map(Class::getSimpleName).toList(); - filter = ListUtils.combine(List.of("in", "class"), clazzes); - return this; + return filterClasses(classToFilter); + } + + + + /** + * Only apply the style to the given vertices. + */ + @SafeVarargs + public final StyleBuilder vertexFilter(Class... classToFilter) { + return filterClasses(classToFilter); } public JsonNode toJson() { @@ -216,6 +227,12 @@ public JsonNode toJson() { return OBJECT_MAPPER.valueToTree(copy); } + private StyleBuilder filterClasses(Class... classToFilter) { + var clazzes = Arrays.stream(classToFilter).map(Class::getSimpleName).toList(); + filter = ListUtils.combine(List.of("in", "class"), clazzes); + return this; + } + private String validateColor(String color) { if (!color.startsWith("#")) { throw new IllegalArgumentException("Colors must start with '#'"); diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java index c036c113e26..3c1408089a4 100644 --- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java +++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java @@ -127,7 +127,7 @@ public static GraphBuilder create( graphBuilder.addModule(factory.osmBoardingLocationsModule()); } - // This module is outside the hasGTFS conditional block because it also links things like bike rental + // This module is outside the hasGTFS conditional block because it also links things like parking // which need to be handled even when there's no transit. graphBuilder.addModule(factory.streetLinkerModule()); diff --git a/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java b/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java index 37334161b0a..48e6e484a0c 100644 --- a/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java +++ b/src/main/java/org/opentripplanner/graph_builder/module/StreetLinkerModule.java @@ -258,16 +258,12 @@ private void linkTransitEntrances(Graph graph) { } private void linkVehicleParks(Graph graph, DataImportIssueStore issueStore) { - if (graph.hasLinkedBikeParks) { - LOG.info("Already linked vehicle parks to graph..."); - return; - } LOG.info("Linking vehicle parks to graph..."); List vehicleParkingToRemove = new ArrayList<>(); for (VehicleParkingEntranceVertex vehicleParkingEntranceVertex : graph.getVerticesOfType( VehicleParkingEntranceVertex.class )) { - if (vehicleParkingEntranceHasLinks(vehicleParkingEntranceVertex)) { + if (vehicleParkingEntranceVertex.isLinkedToGraph()) { continue; } @@ -296,22 +292,6 @@ private void linkVehicleParks(Graph graph, DataImportIssueStore issueStore) { var vehicleParkingService = graph.getVehicleParkingService(); vehicleParkingService.updateVehicleParking(List.of(), vehicleParkingToRemove); } - graph.hasLinkedBikeParks = true; - } - - private boolean vehicleParkingEntranceHasLinks( - VehicleParkingEntranceVertex vehicleParkingEntranceVertex - ) { - return !( - vehicleParkingEntranceVertex - .getIncoming() - .stream() - .allMatch(VehicleParkingEdge.class::isInstance) && - vehicleParkingEntranceVertex - .getOutgoing() - .stream() - .allMatch(VehicleParkingEdge.class::isInstance) - ); } /** diff --git a/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java index a493269cc3b..3bf6a9d8706 100644 --- a/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java +++ b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java @@ -9,6 +9,7 @@ import org.opentripplanner.inspector.vector.KeyValue; import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex; import org.opentripplanner.street.model.vertex.BarrierVertex; +import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; import org.opentripplanner.street.model.vertex.Vertex; public class VertexPropertyMapper extends PropertyMapper { @@ -23,6 +24,11 @@ protected Collection map(Vertex input) { switch (input) { case BarrierVertex v -> List.of(kv("permission", v.getBarrierPermissions().toString())); case VehicleRentalPlaceVertex v -> List.of(kv("rentalId", v.getStation().getId())); + case VehicleParkingEntranceVertex v -> List.of( + kv("rentalId", v.getVehicleParking().getId()), + kv("walkAccessible", Boolean.toString(v.isWalkAccessible())), + kv("carAccessible", Boolean.toString(v.isCarAccessible())) + ); default -> List.of(); }; return ListUtils.combine(baseProps, properties); diff --git a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java index 15ebea466c0..ffb545d5dd9 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java @@ -27,7 +27,7 @@ class VehicleParkingMapper { CYCLE ); - public VehicleParkingMapper(FeedScopedIdFactory idFactory) { + VehicleParkingMapper(FeedScopedIdFactory idFactory) { this.idFactory = idFactory; } diff --git a/src/main/java/org/opentripplanner/routing/graph/Graph.java b/src/main/java/org/opentripplanner/routing/graph/Graph.java index 4b6c9938317..6a6d962cf36 100644 --- a/src/main/java/org/opentripplanner/routing/graph/Graph.java +++ b/src/main/java/org/opentripplanner/routing/graph/Graph.java @@ -90,12 +90,6 @@ public class Graph implements Serializable { /** True if OSM data was loaded into this Graph. */ public boolean hasStreets = false; - /** - * Have bike parks already been linked to the graph. As the linking happens twice if a base graph - * is used, we store information on whether bike park linking should be skipped. - */ - - public boolean hasLinkedBikeParks = false; /** * The difference in meters between the WGS84 ellipsoid height and geoid height at the graph's * center diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingHelper.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingHelper.java index 507ce9329ed..257f2805ca2 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingHelper.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingHelper.java @@ -2,7 +2,6 @@ import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.street.model.edge.StreetVehicleParkingLink; import org.opentripplanner.street.model.edge.VehicleParkingEdge; @@ -30,7 +29,7 @@ public List createVehicleParkingVertices( .getEntrances() .stream() .map(vertexFactory::vehicleParkingEntrance) - .collect(Collectors.toList()); + .toList(); } public static void linkVehicleParkingEntrances( diff --git a/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java b/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java index e682a7bfac1..098e91b1b63 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java +++ b/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java @@ -2,6 +2,7 @@ import javax.annotation.Nonnull; import org.locationtech.jts.geom.LineString; +import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; @@ -95,11 +96,9 @@ public I18NString getName() { return vehicleParkingEntranceVertex.getName(); } + @Override public LineString getGeometry() { - return null; + return GeometryUtils.makeLineString(fromv.getCoordinate(), tov.getCoordinate()); } - public double getDistanceMeters() { - return 0; - } } diff --git a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java index a3ab0aa17b0..d43f29ad2bc 100644 --- a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java +++ b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java @@ -1,10 +1,12 @@ package org.opentripplanner.street.model.vertex; +import java.util.Collection; import java.util.Objects; import javax.annotation.Nonnull; import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingEntrance; +import org.opentripplanner.street.model.edge.Edge; import org.opentripplanner.street.model.edge.StreetVehicleParkingLink; import org.opentripplanner.street.model.edge.VehicleParkingEdge; @@ -49,4 +51,18 @@ public boolean isCarAccessible() { public boolean isWalkAccessible() { return parkingEntrance.isWalkAccessible(); } + + public boolean isLinkedToGraph( + ) { + return !( + hasLinks(getIncoming()) && + hasLinks(getOutgoing()) + ); + } + + private boolean hasLinks(Collection incoming) { + return incoming + .stream() + .allMatch(VehicleParkingEdge.class::isInstance); + } } From 96b900489c0a5c58cebb68d64802ada8cdfa7293 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 4 Jul 2024 00:04:59 +0200 Subject: [PATCH 054/148] Swap colour order --- client/src/util/getColorForMode.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/util/getColorForMode.ts b/client/src/util/getColorForMode.ts index ca7f9fd3474..cb1ad8b6981 100644 --- a/client/src/util/getColorForMode.ts +++ b/client/src/util/getColorForMode.ts @@ -1,10 +1,10 @@ import { Mode } from '../gql/graphql.ts'; export const getColorForMode = function (mode: Mode) { - if (mode === Mode.Foot) return '#7e7e7e'; + if (mode === Mode.Foot) return '#191616'; if (mode === Mode.Bicycle) return '#5076D9'; if (mode === Mode.Scooter) return '#253664'; - if (mode === Mode.Car) return '#191616'; + if (mode === Mode.Car) return '#7e7e7e'; if (mode === Mode.Rail) return '#86BF8B'; if (mode === Mode.Coach) return '#25642A'; if (mode === Mode.Metro) return '#D9B250'; From 90ddcec9e9dd7598bcfffce746dba5be2f8b5fdd Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 4 Jul 2024 08:02:56 +0200 Subject: [PATCH 055/148] Format code --- .../apis/vectortiles/model/StyleBuilder.java | 4 -- .../vector/vertex/VertexPropertyMapper.java | 2 +- .../opentripplanner/netex/NetexModule.java | 1 - .../model/edge/StreetVehicleParkingLink.java | 1 - .../vertex/VehicleParkingEntranceVertex.java | 12 ++--- .../opentripplanner/ConstantsForTests.java | 5 +- .../apis/vectortiles/style.json | 46 +++++++++++++++++++ 7 files changed, 52 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java b/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java index 312eaa9e727..d842b5e6687 100644 --- a/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java +++ b/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java @@ -38,8 +38,6 @@ public StyleBuilder vectorSourceLayer(VectorSourceLayer source) { return sourceLayer(source.vectorLayer()); } - - public enum LayerType { Circle, Line, @@ -198,8 +196,6 @@ public final StyleBuilder edgeFilter(Class... classToFilter) { return filterClasses(classToFilter); } - - /** * Only apply the style to the given vertices. */ diff --git a/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java index 3bf6a9d8706..83b4a675fdb 100644 --- a/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java +++ b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java @@ -28,7 +28,7 @@ protected Collection map(Vertex input) { kv("rentalId", v.getVehicleParking().getId()), kv("walkAccessible", Boolean.toString(v.isWalkAccessible())), kv("carAccessible", Boolean.toString(v.isCarAccessible())) - ); + ); default -> List.of(); }; return ListUtils.combine(baseProps, properties); diff --git a/src/main/java/org/opentripplanner/netex/NetexModule.java b/src/main/java/org/opentripplanner/netex/NetexModule.java index 8c5c0826670..c0390e9793d 100644 --- a/src/main/java/org/opentripplanner/netex/NetexModule.java +++ b/src/main/java/org/opentripplanner/netex/NetexModule.java @@ -106,7 +106,6 @@ public void buildGraph() { graph.getVehicleParkingService().updateVehicleParking(lots, List.of()); var linker = new VehicleParkingHelper(graph); lots.forEach(linker::linkVehicleParkingToGraph); - } transitModel.updateCalendarServiceData(hasActiveTransit, calendarServiceData, issueStore); diff --git a/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java b/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java index 098e91b1b63..2ad9d0f39c4 100644 --- a/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java +++ b/src/main/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLink.java @@ -100,5 +100,4 @@ public I18NString getName() { public LineString getGeometry() { return GeometryUtils.makeLineString(fromv.getCoordinate(), tov.getCoordinate()); } - } diff --git a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java index d43f29ad2bc..91512c98c46 100644 --- a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java +++ b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java @@ -52,17 +52,11 @@ public boolean isWalkAccessible() { return parkingEntrance.isWalkAccessible(); } - public boolean isLinkedToGraph( - ) { - return !( - hasLinks(getIncoming()) && - hasLinks(getOutgoing()) - ); + public boolean isLinkedToGraph() { + return !(hasLinks(getIncoming()) && hasLinks(getOutgoing())); } private boolean hasLinks(Collection incoming) { - return incoming - .stream() - .allMatch(VehicleParkingEdge.class::isInstance); + return incoming.stream().allMatch(VehicleParkingEdge.class::isInstance); } } diff --git a/src/test/java/org/opentripplanner/ConstantsForTests.java b/src/test/java/org/opentripplanner/ConstantsForTests.java index b002d7a3c41..76826b8c247 100644 --- a/src/test/java/org/opentripplanner/ConstantsForTests.java +++ b/src/test/java/org/opentripplanner/ConstantsForTests.java @@ -31,7 +31,6 @@ import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.linking.LinkingDirection; import org.opentripplanner.routing.linking.VertexLinker; -import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; import org.opentripplanner.service.vehiclerental.model.RentalVehicleType; import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation; import org.opentripplanner.service.vehiclerental.street.StreetVehicleRentalLink; @@ -104,7 +103,7 @@ public static NetexBundle createMinimalNetexNordicBundle() { var transitService = new OtpTransitServiceBuilder(new StopModel(), DataImportIssueStore.NOOP); return new NetexConfigure(buildConfig) - .netexBundle(transitService, configuredDataSource, new VehicleParkingService()); + .netexBundle(transitService, configuredDataSource); } public static NetexBundle createMinimalNetexEpipBundle() { @@ -117,7 +116,7 @@ public static NetexBundle createMinimalNetexEpipBundle() { var transitService = new OtpTransitServiceBuilder(new StopModel(), DataImportIssueStore.NOOP); return new NetexConfigure(buildConfig) - .netexBundle(transitService, configuredDataSource, new VehicleParkingService()); + .netexBundle(transitService, configuredDataSource); } /** diff --git a/src/test/resources/org/opentripplanner/apis/vectortiles/style.json b/src/test/resources/org/opentripplanner/apis/vectortiles/style.json index 6f981b7f67d..5a2ed9572e2 100644 --- a/src/test/resources/org/opentripplanner/apis/vectortiles/style.json +++ b/src/test/resources/org/opentripplanner/apis/vectortiles/style.json @@ -191,6 +191,52 @@ "visibility" : "none" } }, + { + "id" : "parking-vertex", + "type" : "circle", + "source" : "vectorSource", + "source-layer" : "vertices", + "minzoom" : 13, + "maxzoom" : 23, + "paint" : { + "circle-stroke-color" : "#140d0e", + "circle-stroke-width" : { + "base" : 1.0, + "stops" : [ + [ + 15, + 0.2 + ], + [ + 23, + 3.0 + ] + ] + }, + "circle-radius" : { + "base" : 1.0, + "stops" : [ + [ + 13, + 1.4 + ], + [ + 23, + 10.0 + ] + ] + }, + "circle-color" : "#136b04" + }, + "filter" : [ + "in", + "class", + "VehicleParkingEntranceVertex" + ], + "layout" : { + "visibility" : "none" + } + }, { "id" : "area-stop", "type" : "fill", From 39f13f3a75b08809bae948074bb55e99c0834125 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 4 Jul 2024 08:07:49 +0200 Subject: [PATCH 056/148] Update snapshot tests --- src/test/java/org/opentripplanner/ConstantsForTests.java | 6 ++---- .../algorithm/mapping/__snapshots__/CarSnapshotTest.snap | 8 ++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/opentripplanner/ConstantsForTests.java b/src/test/java/org/opentripplanner/ConstantsForTests.java index 76826b8c247..e74c66e527b 100644 --- a/src/test/java/org/opentripplanner/ConstantsForTests.java +++ b/src/test/java/org/opentripplanner/ConstantsForTests.java @@ -102,8 +102,7 @@ public static NetexBundle createMinimalNetexNordicBundle() { var configuredDataSource = new ConfiguredDataSource<>(dataSource, buildConfig.netexDefaults); var transitService = new OtpTransitServiceBuilder(new StopModel(), DataImportIssueStore.NOOP); - return new NetexConfigure(buildConfig) - .netexBundle(transitService, configuredDataSource); + return new NetexConfigure(buildConfig).netexBundle(transitService, configuredDataSource); } public static NetexBundle createMinimalNetexEpipBundle() { @@ -115,8 +114,7 @@ public static NetexBundle createMinimalNetexEpipBundle() { var configuredDataSource = new ConfiguredDataSource<>(dataSource, buildConfig.netexDefaults); var transitService = new OtpTransitServiceBuilder(new StopModel(), DataImportIssueStore.NOOP); - return new NetexConfigure(buildConfig) - .netexBundle(transitService, configuredDataSource); + return new NetexConfigure(buildConfig).netexBundle(transitService, configuredDataSource); } /** diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap index 8f527b15bf7..5d83d85cd7a 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/__snapshots__/CarSnapshotTest.snap @@ -28,8 +28,8 @@ org.opentripplanner.routing.algorithm.mapping.CarSnapshotTest.directCarPark=[ "generalizedCost" : 61, "interlineWithPreviousLeg" : false, "legGeometry" : { - "length" : 5, - "points" : "ya|tGv~{kV??nCEB|Dn@@" + "length" : 6, + "points" : "ya|tGv~{kV??nCEB|Dn@@??" }, "mode" : "CAR", "pathway" : false, @@ -147,8 +147,8 @@ org.opentripplanner.routing.algorithm.mapping.CarSnapshotTest.directCarPark=[ "generalizedCost" : 285, "interlineWithPreviousLeg" : false, "legGeometry" : { - "length" : 5, - "points" : "gz{tGrd|kVn@@CgElCEB?" + "length" : 6, + "points" : "gz{tGrd|kV??n@@CgElCEB?" }, "mode" : "WALK", "pathway" : false, From 2dd6589e169b957227965f9e53211a6987a4f235 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 4 Jul 2024 11:10:22 +0200 Subject: [PATCH 057/148] Update docs --- .../opentripplanner/netex/mapping/VehicleParkingMapper.java | 3 ++- .../street/model/vertex/VehicleParkingEntranceVertex.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java index ffb545d5dd9..84dfc201909 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java @@ -61,7 +61,8 @@ private static VehicleParkingSpaces mapCapacity(Parking parking) { var builder = VehicleParkingSpaces.builder(); int capacity = parking.getTotalCapacity().intValue(); - // we assume that if we have bicycle in vehicle types it's a bicycle parking lot + // we assume that if we have something bicycle-like in the vehicle types it's a bicycle parking + // lot // it's not possible in NeTEx to split the spaces between the types, so if you want that // you have to define two parking lots with the same coordinates if (hasBikes(parking)) { diff --git a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java index 91512c98c46..6ba13ecf9b8 100644 --- a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java +++ b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java @@ -53,10 +53,10 @@ public boolean isWalkAccessible() { } public boolean isLinkedToGraph() { - return !(hasLinks(getIncoming()) && hasLinks(getOutgoing())); + return !(hasNoLinkEdges(getIncoming()) && hasNoLinkEdges(getOutgoing())); } - private boolean hasLinks(Collection incoming) { + private boolean hasNoLinkEdges(Collection incoming) { return incoming.stream().allMatch(VehicleParkingEdge.class::isInstance); } } From aab96e6597b4a95a5d032aba18d6398ce03af8fb Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 4 Jul 2024 11:39:54 +0200 Subject: [PATCH 058/148] Update Javadoc and implementation --- .../model/vertex/VehicleParkingEntranceVertex.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java index 6ba13ecf9b8..2f389f60817 100644 --- a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java +++ b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java @@ -52,11 +52,14 @@ public boolean isWalkAccessible() { return parkingEntrance.isWalkAccessible(); } + /** + * Is this vertex already linked to the graph with a {@link StreetVehicleParkingLink}? + */ public boolean isLinkedToGraph() { - return !(hasNoLinkEdges(getIncoming()) && hasNoLinkEdges(getOutgoing())); + return hasLink(getIncoming()) || hasLink(getOutgoing()); } - private boolean hasNoLinkEdges(Collection incoming) { - return incoming.stream().allMatch(VehicleParkingEdge.class::isInstance); + private boolean hasLink(Collection incoming) { + return incoming.stream().anyMatch(StreetVehicleParkingLink.class::isInstance); } } From 6df518f281b1dc30cc7454c0d8160d231ba11b1d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 4 Jul 2024 12:05:02 +0200 Subject: [PATCH 059/148] Add test for linking --- .../edge/StreetVehicleParkingLinkTest.java | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java index 99de720a5a2..af03bd754bb 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java @@ -9,6 +9,7 @@ import java.util.Set; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -38,37 +39,25 @@ static Stream testCases() { @ParameterizedTest(name = "Parking[tags={0}], Request[not={1}, select={2}] should traverse={3}") @MethodSource("testCases") - void foo(Set parkingTags, Set not, Set select, boolean shouldTraverse) { + void parkingFilters( + Set parkingTags, + Set not, + Set select, + boolean shouldTraverse + ) { var streetVertex = intersectionVertex(1, 1); - var parking = VehicleParking - .builder() - .id(id("parking")) - .coordinate(new WgsCoordinate(1, 1)) - .tags(parkingTags) - .build(); - - var entrance = VehicleParkingEntrance - .builder() - .vehicleParking(parking) - .entranceId(id("entrance")) - .coordinate(new WgsCoordinate(1, 1)) - .name(new NonLocalizedString("entrance")) - .walkAccessible(true) - .carAccessible(true) - .build(); - - var entranceVertex = new VehicleParkingEntranceVertex(entrance); + final var entranceVertex = buildVertex(parkingTags); var req = StreetSearchRequest.of(); req.withMode(StreetMode.BIKE_TO_PARK); req.withPreferences(p -> - p.withBike(bike -> { + p.withBike(bike -> bike.withParking(parkingPreferences -> { parkingPreferences.withRequiredVehicleParkingTags(select); parkingPreferences.withBannedVehicleParkingTags(not); parkingPreferences.withCost(0); - }); - }) + }) + ) ); var edge = StreetVehicleParkingLink.createStreetVehicleParkingLink( @@ -84,6 +73,33 @@ void foo(Set parkingTags, Set not, Set select, boolean s } } + @Test + void isLinkedToGraph() { + var vertex = buildVertex(Set.of()); + assertFalse(vertex.isLinkedToGraph()); + } + + private static VehicleParkingEntranceVertex buildVertex(Set parkingTags) { + var parking = VehicleParking + .builder() + .id(id("parking")) + .coordinate(new WgsCoordinate(1, 1)) + .tags(parkingTags) + .build(); + + var entrance = VehicleParkingEntrance + .builder() + .vehicleParking(parking) + .entranceId(id("entrance")) + .coordinate(new WgsCoordinate(1, 1)) + .name(new NonLocalizedString("entrance")) + .walkAccessible(true) + .carAccessible(true) + .build(); + + return new VehicleParkingEntranceVertex(entrance); + } + private State[] traverse(Vertex fromV, Edge edge, StreetSearchRequest request) { var state = new State(fromV, request); From c12d713e32b708ef433908f4d5f0f4f41e4517b6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 4 Jul 2024 12:33:42 +0200 Subject: [PATCH 060/148] Add more tests --- .../edge/StreetVehicleParkingLinkTest.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java index af03bd754bb..8a3316e44e0 100644 --- a/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java +++ b/src/test/java/org/opentripplanner/street/model/edge/StreetVehicleParkingLinkTest.java @@ -18,6 +18,7 @@ import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingEntrance; +import org.opentripplanner.street.model._data.StreetModelForTest; import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; import org.opentripplanner.street.model.vertex.Vertex; import org.opentripplanner.street.search.request.StreetSearchRequest; @@ -74,11 +75,31 @@ void parkingFilters( } @Test - void isLinkedToGraph() { + void notLinkedToGraph() { var vertex = buildVertex(Set.of()); assertFalse(vertex.isLinkedToGraph()); } + @Test + void linkedToGraphWithIncoming() { + var vertex = buildVertex(Set.of()); + var streetVertex = StreetModelForTest.intersectionVertex(1, 1); + vertex.addIncoming( + StreetVehicleParkingLink.createStreetVehicleParkingLink(streetVertex, vertex) + ); + assertTrue(vertex.isLinkedToGraph()); + } + + @Test + void linkedToGraphWithOutgoing() { + var vertex = buildVertex(Set.of()); + var streetVertex = StreetModelForTest.intersectionVertex(1, 1); + vertex.addOutgoing( + StreetVehicleParkingLink.createStreetVehicleParkingLink(streetVertex, vertex) + ); + assertTrue(vertex.isLinkedToGraph()); + } + private static VehicleParkingEntranceVertex buildVertex(Set parkingTags) { var parking = VehicleParking .builder() From 55257014ad99bd041ef5c3817a4163570785334b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 8 Jul 2024 17:37:04 +0200 Subject: [PATCH 061/148] Use HierarchicalMapById --- .../netex/index/NetexEntityIndex.java | 13 +++++-------- .../index/api/NetexEntityIndexReadOnlyView.java | 3 +-- .../opentripplanner/netex/mapping/NetexMapper.java | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java b/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java index 9841765f3a3..6a5308f91a3 100644 --- a/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java +++ b/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java @@ -1,9 +1,6 @@ package org.opentripplanner.netex.index; -import com.google.common.collect.ImmutableSet; import java.util.Collection; -import java.util.HashSet; -import java.util.Set; import org.opentripplanner.netex.index.api.NetexEntityIndexReadOnlyView; import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMap; import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMapById; @@ -101,7 +98,7 @@ public class NetexEntityIndex { public final HierarchicalVersionMapById stopPlaceById; public final HierarchicalVersionMapById tariffZonesById; public final HierarchicalMapById brandingById; - public final Set parkings; + public final HierarchicalMapById parkings; // Relations between entities - The Netex XML sometimes relies on the // nested structure of the XML document, rater than explicit references. @@ -147,7 +144,7 @@ public NetexEntityIndex() { this.tariffZonesById = new HierarchicalVersionMapById<>(); this.brandingById = new HierarchicalMapById<>(); this.timeZone = new HierarchicalElement<>(); - this.parkings = new HashSet<>(0); + this.parkings = new HierarchicalMapById<>(); } /** @@ -190,7 +187,7 @@ public NetexEntityIndex(NetexEntityIndex parent) { this.tariffZonesById = new HierarchicalVersionMapById<>(parent.tariffZonesById); this.brandingById = new HierarchicalMapById<>(parent.brandingById); this.timeZone = new HierarchicalElement<>(parent.timeZone); - this.parkings = new HashSet<>(parent.parkings); + this.parkings = new HierarchicalMapById<>(parent.parkings); } /** @@ -361,8 +358,8 @@ public ReadOnlyHierarchicalVersionMapById getStopPlaceById() { } @Override - public ImmutableSet getParkings() { - return ImmutableSet.copyOf(parkings); + public ReadOnlyHierarchicalMapById getParkings() { + return parkings; } @Override diff --git a/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java b/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java index 9229f40c7f8..24a3d7a8ac4 100644 --- a/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java +++ b/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java @@ -1,6 +1,5 @@ package org.opentripplanner.netex.index.api; -import com.google.common.collect.ImmutableSet; import java.util.Collection; import org.rutebanken.netex.model.Authority; import org.rutebanken.netex.model.Branding; @@ -82,7 +81,7 @@ public interface NetexEntityIndexReadOnlyView { ReadOnlyHierarchicalVersionMapById getStopPlaceById(); - ImmutableSet getParkings(); + ReadOnlyHierarchicalMapById getParkings(); ReadOnlyHierarchicalVersionMapById getTariffZonesById(); diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java index e6a0d724dd4..e88a75f750a 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java @@ -205,7 +205,7 @@ public void mapNetexToOtp(NetexEntityIndexReadOnlyView netexIndex) { public Collection mapVehicleParkings() { var mapper = new VehicleParkingMapper(idFactory); - return mapper.map(currentNetexIndex.getParkings()); + return mapper.map(currentNetexIndex.getParkings().localValues()); } /* PRIVATE METHODS */ From c6ae01a5d62fcb0ec61f0f4c832372bc41e00275 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 15 Jul 2024 10:30:42 +0200 Subject: [PATCH 062/148] Add test for allPassengerVehicles --- .../netex/mapping/VehicleParkingMapperTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java index 98dbf661839..b81075a5b0e 100644 --- a/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java +++ b/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.rutebanken.netex.model.ParkingVehicleEnumeration.AGRICULTURAL_VEHICLE; +import static org.rutebanken.netex.model.ParkingVehicleEnumeration.ALL_PASSENGER_VEHICLES; import static org.rutebanken.netex.model.ParkingVehicleEnumeration.CAR; import static org.rutebanken.netex.model.ParkingVehicleEnumeration.CAR_WITH_CARAVAN; import static org.rutebanken.netex.model.ParkingVehicleEnumeration.CYCLE; @@ -31,7 +32,7 @@ class VehicleParkingMapperTest { ); public static List> carCases() { - return List.of(Set.of(), Set.of(CAR, AGRICULTURAL_VEHICLE)); + return List.of(Set.of(), Set.of(CAR, AGRICULTURAL_VEHICLE, ALL_PASSENGER_VEHICLES)); } @ParameterizedTest From acaa41fa1167decf003db61a9b70a1c1da24319a Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 15 Jul 2024 16:14:48 +0200 Subject: [PATCH 063/148] Take NeTEx hierarchy into account when mapping lots --- .../inspector/vector/KeyValue.java | 21 +++++++++++ .../vector/vertex/VertexPropertyMapper.java | 36 ++++++++++++++++--- .../model/impl/OtpTransitServiceBuilder.java | 11 ++++++ .../opentripplanner/netex/NetexBundle.java | 9 ----- .../opentripplanner/netex/NetexModule.java | 2 +- .../netex/index/NetexEntityIndex.java | 2 +- .../api/NetexEntityIndexReadOnlyView.java | 2 +- .../netex/mapping/NetexMapper.java | 25 ++++++++----- 8 files changed, 83 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java b/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java index 6c8b0f3aa4e..05429b77fb7 100644 --- a/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java +++ b/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java @@ -1,7 +1,28 @@ package org.opentripplanner.inspector.vector; +import java.util.Collection; +import java.util.stream.Collectors; +import org.opentripplanner.transit.model.framework.FeedScopedId; + public record KeyValue(String key, Object value) { public static KeyValue kv(String key, Object value) { return new KeyValue(key, value); } + public static KeyValue kv(String key, FeedScopedId value) { + if(value !=null){ + return new KeyValue(key, value.toString()); + } + else { + return new KeyValue(key, null); + } + } + + /** + * Takes a key and a collection of values, calls toString on the values and joins them using + * comma as the separator. + */ + public static KeyValue kColl(String key, Collection value) { + var values = value.stream().map(Object::toString).collect(Collectors.joining(",")); + return new KeyValue(key, values); + } } diff --git a/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java index 83b4a675fdb..01f5263b11a 100644 --- a/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java +++ b/src/main/java/org/opentripplanner/inspector/vector/vertex/VertexPropertyMapper.java @@ -1,16 +1,22 @@ package org.opentripplanner.inspector.vector.vertex; +import static org.opentripplanner.inspector.vector.KeyValue.kColl; import static org.opentripplanner.inspector.vector.KeyValue.kv; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.opentripplanner.apis.support.mapping.PropertyMapper; import org.opentripplanner.framework.collection.ListUtils; import org.opentripplanner.inspector.vector.KeyValue; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingEntrance; import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex; import org.opentripplanner.street.model.vertex.BarrierVertex; import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex; import org.opentripplanner.street.model.vertex.Vertex; +import org.opentripplanner.street.search.TraverseMode; public class VertexPropertyMapper extends PropertyMapper { @@ -23,14 +29,36 @@ protected Collection map(Vertex input) { List properties = switch (input) { case BarrierVertex v -> List.of(kv("permission", v.getBarrierPermissions().toString())); - case VehicleRentalPlaceVertex v -> List.of(kv("rentalId", v.getStation().getId())); + case VehicleRentalPlaceVertex v -> List.of(kv("rentalId", v.getStation())); case VehicleParkingEntranceVertex v -> List.of( - kv("rentalId", v.getVehicleParking().getId()), - kv("walkAccessible", Boolean.toString(v.isWalkAccessible())), - kv("carAccessible", Boolean.toString(v.isCarAccessible())) + kv("parkingId", v.getVehicleParking().getId()), + kColl("spacesFor", spacesFor(v.getVehicleParking())), + kColl("traversalPermission", traversalPermissions(v.getParkingEntrance())) ); default -> List.of(); }; return ListUtils.combine(baseProps, properties); } + + private Set spacesFor(VehicleParking vehicleParking) { + var ret = new HashSet(); + if (vehicleParking.hasAnyCarPlaces()) { + ret.add(TraverseMode.CAR); + } + if (vehicleParking.hasBicyclePlaces()) { + ret.add(TraverseMode.BICYCLE); + } + return ret; + } + + private Set traversalPermissions(VehicleParkingEntrance entrance) { + var ret = new HashSet(); + if (entrance.isCarAccessible()) { + ret.add(TraverseMode.CAR); + } + if (entrance.isWalkAccessible()) { + ret.add(TraverseMode.WALK); + } + return ret; + } } diff --git a/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java index 43c18cec59d..373b99f0bc6 100644 --- a/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java +++ b/src/main/java/org/opentripplanner/model/impl/OtpTransitServiceBuilder.java @@ -24,6 +24,7 @@ import org.opentripplanner.model.transfer.ConstrainedTransfer; import org.opentripplanner.model.transfer.TransferPoint; import org.opentripplanner.routing.api.request.framework.TimePenalty; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.transit.model.basic.Notice; import org.opentripplanner.transit.model.framework.AbstractTransitEntity; import org.opentripplanner.transit.model.framework.DefaultEntityById; @@ -116,6 +117,8 @@ public class OtpTransitServiceBuilder { private final EntityById groupOfRouteById = new DefaultEntityById<>(); + private final List vehicleParkings = new ArrayList<>(); + private final DataImportIssueStore issueStore; public OtpTransitServiceBuilder(StopModel stopModel, DataImportIssueStore issueStore) { @@ -264,6 +267,14 @@ public CalendarServiceData buildCalendarServiceData() { ); } + /** + * The list of parking lots contained in the transit data (so far only NeTEx). + * Note that parking lots can also be sourced from OSM data as well as realtime updaters. + */ + public List vehicleParkings() { + return vehicleParkings; + } + public OtpTransitService build() { return new OtpTransitServiceImpl(this); } diff --git a/src/main/java/org/opentripplanner/netex/NetexBundle.java b/src/main/java/org/opentripplanner/netex/NetexBundle.java index 7d80f431309..8d6f098de89 100644 --- a/src/main/java/org/opentripplanner/netex/NetexBundle.java +++ b/src/main/java/org/opentripplanner/netex/NetexBundle.java @@ -3,7 +3,6 @@ import jakarta.xml.bind.JAXBException; import java.io.Closeable; import java.io.IOException; -import java.util.Collection; import java.util.List; import java.util.Set; import org.opentripplanner.datastore.api.CompositeDataSource; @@ -18,7 +17,6 @@ import org.opentripplanner.netex.loader.parser.NetexDocumentParser; import org.opentripplanner.netex.mapping.NetexMapper; import org.opentripplanner.netex.validation.Validator; -import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.transit.model.framework.Deduplicator; import org.rutebanken.netex.model.PublicationDeliveryStructure; import org.slf4j.Logger; @@ -117,13 +115,6 @@ public void close() throws IOException { source.close(); } - /** - * Return the list of parking lots contained in the netex bundle. - */ - public Collection vehicleParkings() { - return mapper.mapVehicleParkings(); - } - /** Load all files entries in the bundle */ private void loadFileEntries() { // Load global shared files diff --git a/src/main/java/org/opentripplanner/netex/NetexModule.java b/src/main/java/org/opentripplanner/netex/NetexModule.java index c0390e9793d..2bf3403395c 100644 --- a/src/main/java/org/opentripplanner/netex/NetexModule.java +++ b/src/main/java/org/opentripplanner/netex/NetexModule.java @@ -102,7 +102,7 @@ public void buildGraph() { transitModel.validateTimeZones(); - var lots = netexBundle.vehicleParkings(); + var lots = transitBuilder.vehicleParkings(); graph.getVehicleParkingService().updateVehicleParking(lots, List.of()); var linker = new VehicleParkingHelper(graph); lots.forEach(linker::linkVehicleParkingToGraph); diff --git a/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java b/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java index 6a5308f91a3..0cb94f66a77 100644 --- a/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java +++ b/src/main/java/org/opentripplanner/netex/index/NetexEntityIndex.java @@ -358,7 +358,7 @@ public ReadOnlyHierarchicalVersionMapById getStopPlaceById() { } @Override - public ReadOnlyHierarchicalMapById getParkings() { + public ReadOnlyHierarchicalMapById getParkingsById() { return parkings; } diff --git a/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java b/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java index 24a3d7a8ac4..37b8e9790b9 100644 --- a/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java +++ b/src/main/java/org/opentripplanner/netex/index/api/NetexEntityIndexReadOnlyView.java @@ -81,7 +81,7 @@ public interface NetexEntityIndexReadOnlyView { ReadOnlyHierarchicalVersionMapById getStopPlaceById(); - ReadOnlyHierarchicalMapById getParkings(); + ReadOnlyHierarchicalMapById getParkingsById(); ReadOnlyHierarchicalVersionMapById getTariffZonesById(); diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java index e88a75f750a..8e3fd6e166d 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java @@ -19,7 +19,6 @@ import org.opentripplanner.netex.mapping.calendar.CalendarServiceBuilder; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; import org.opentripplanner.netex.mapping.support.NetexMapperIndexes; -import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.transit.model.basic.Notice; import org.opentripplanner.transit.model.framework.AbstractTransitEntity; import org.opentripplanner.transit.model.framework.Deduplicator; @@ -78,9 +77,9 @@ public class NetexMapper { /** * Shared/cached entity index, used by more than one mapper. This index provides alternative - * indexes to netex entites, as well as global indexes to OTP domain objects needed in the mapping + * indexes to netex entities, as well as global indexes to OTP domain objects needed in the mapping * process. Some of these indexes are feed scoped, and some are file group level scoped. As a rule - * of tomb the indexes for OTP Model entities are global(small memory overhead), while the indexes + * of thumb the indexes for OTP Model entities are global(small memory overhead), while the indexes * for the Netex entities follow the main index {@link #currentNetexIndex}, hence sopped by file * group. */ @@ -159,7 +158,7 @@ public void finishUp() { /** *

- * This method mapes the last Netex file imported using the *local* entities in the hierarchical + * This method maps the last Netex file imported using the *local* entities in the hierarchical * {@link NetexEntityIndexReadOnlyView}. *

*

@@ -200,12 +199,9 @@ public void mapNetexToOtp(NetexEntityIndexReadOnlyView netexIndex) { mapTripPatterns(serviceIds); mapNoticeAssignments(); - addEntriesToGroupMapperForPostProcessingLater(); - } + mapVehicleParkings(); - public Collection mapVehicleParkings() { - var mapper = new VehicleParkingMapper(idFactory); - return mapper.map(currentNetexIndex.getParkings().localValues()); + addEntriesToGroupMapperForPostProcessingLater(); } /* PRIVATE METHODS */ @@ -525,6 +521,17 @@ private void addEntriesToGroupMapperForPostProcessingLater() { } } + private void mapVehicleParkings() { + var mapper = new VehicleParkingMapper(idFactory); + currentNetexIndex + .getParkingsById() + .localKeys() + .forEach(id -> { + var parking = mapper.map(currentNetexIndex.getParkingsById().lookup(id)); + transitBuilder.vehicleParkings().add(parking); + }); + } + /** * The start of period is used to find the valid entities based on the current time. This should * probably be configurable in the future, or even better incorporate the version number into the From ac046c67387920bc977f74281e5e284e0bff8bf4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 15 Jul 2024 18:06:11 +0200 Subject: [PATCH 064/148] Take missing capacity into account --- .../netex/mapping/NetexMapper.java | 6 ++-- .../netex/mapping/VehicleParkingMapper.java | 21 ++++++++---- .../mapping/VehicleParkingMapperTest.java | 34 +++++++++++++++---- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java index 8e3fd6e166d..897260fbddb 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/NetexMapper.java @@ -522,13 +522,15 @@ private void addEntriesToGroupMapperForPostProcessingLater() { } private void mapVehicleParkings() { - var mapper = new VehicleParkingMapper(idFactory); + var mapper = new VehicleParkingMapper(idFactory, issueStore); currentNetexIndex .getParkingsById() .localKeys() .forEach(id -> { var parking = mapper.map(currentNetexIndex.getParkingsById().lookup(id)); - transitBuilder.vehicleParkings().add(parking); + if (parking != null) { + transitBuilder.vehicleParkings().add(parking); + } }); } diff --git a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java index 84dfc201909..7f78e7191ef 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java @@ -4,10 +4,10 @@ import static org.rutebanken.netex.model.ParkingVehicleEnumeration.E_CYCLE; import static org.rutebanken.netex.model.ParkingVehicleEnumeration.PEDAL_CYCLE; -import java.util.Collection; import java.util.Set; -import java.util.stream.Collectors; +import javax.annotation.Nullable; import org.opentripplanner.framework.i18n.NonLocalizedString; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; @@ -26,16 +26,23 @@ class VehicleParkingMapper { E_CYCLE, CYCLE ); + private final DataImportIssueStore issueStore; - VehicleParkingMapper(FeedScopedIdFactory idFactory) { + VehicleParkingMapper(FeedScopedIdFactory idFactory, DataImportIssueStore issueStore) { this.idFactory = idFactory; + this.issueStore = issueStore; } - Collection map(Collection parkings) { - return parkings.stream().map(this::map).collect(Collectors.toUnmodifiableSet()); - } - + @Nullable VehicleParking map(Parking parking) { + if (parking.getTotalCapacity() == null) { + issueStore.add( + "MissingParkingCapacity", + "NeTEx Parking %s does not contain totalCapacity", + parking.getId() + ); + return null; + } return VehicleParking .builder() .id(idFactory.createId(parking.getId())) diff --git a/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java index b81075a5b0e..bf56be1be1b 100644 --- a/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java +++ b/src/test/java/org/opentripplanner/netex/mapping/VehicleParkingMapperTest.java @@ -1,6 +1,7 @@ package org.opentripplanner.netex.mapping; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.rutebanken.netex.model.ParkingVehicleEnumeration.AGRICULTURAL_VEHICLE; import static org.rutebanken.netex.model.ParkingVehicleEnumeration.ALL_PASSENGER_VEHICLES; @@ -13,9 +14,13 @@ import java.math.BigInteger; import java.util.List; import java.util.Set; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.graph_builder.issue.api.DataImportIssue; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.graph_builder.issue.service.DefaultDataImportIssueStore; import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory; import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; @@ -27,10 +32,6 @@ class VehicleParkingMapperTest { - private static final VehicleParkingMapper MAPPER = new VehicleParkingMapper( - new FeedScopedIdFactory("parking") - ); - public static List> carCases() { return List.of(Set.of(), Set.of(CAR, AGRICULTURAL_VEHICLE, ALL_PASSENGER_VEHICLES)); } @@ -38,7 +39,7 @@ public static List> carCases() { @ParameterizedTest @MethodSource("carCases") void mapCarLot(Set vehicleTypes) { - var vp = MAPPER.map(parking(vehicleTypes)); + var vp = mapper().map(parking(vehicleTypes)); assertCommonProperties(vp); assertTrue(vp.hasAnyCarPlaces()); assertEquals(VehicleParkingSpaces.builder().carSpaces(10).build(), vp.getCapacity()); @@ -51,12 +52,33 @@ public static List> bicycleCases() { @ParameterizedTest @MethodSource("bicycleCases") void mapBicycleLot(Set vehicleTypes) { - var vp = MAPPER.map(parking(vehicleTypes)); + var vp = mapper().map(parking(vehicleTypes)); assertCommonProperties(vp); assertTrue(vp.hasBicyclePlaces()); assertEquals(VehicleParkingSpaces.builder().bicycleSpaces(10).build(), vp.getCapacity()); } + @Test + void dropEmptyCapacity() { + var parking = parking(Set.of(CAR)); + parking.setTotalCapacity(null); + var issueStore = new DefaultDataImportIssueStore(); + var vp = mapper(issueStore).map(parking); + assertNull(vp); + assertEquals( + List.of("MissingParkingCapacity"), + issueStore.listIssues().stream().map(DataImportIssue::getType).toList() + ); + } + + private VehicleParkingMapper mapper() { + return mapper(DataImportIssueStore.NOOP); + } + + private static VehicleParkingMapper mapper(DataImportIssueStore issueStore) { + return new VehicleParkingMapper(new FeedScopedIdFactory("parking"), issueStore); + } + private static void assertCommonProperties(VehicleParking vp) { assertEquals("A name", vp.getName().toString()); assertEquals(new WgsCoordinate(10, 20), vp.getCoordinate()); From 618e0a1a7e2ea7ef438f68ee9c6a1319eb70d675 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 15 Jul 2024 18:20:39 +0200 Subject: [PATCH 065/148] Add more robust null check for issue --- .../netex/mapping/VehicleParkingMapper.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java index 7f78e7191ef..f0ca2aeb51f 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java @@ -38,8 +38,8 @@ VehicleParking map(Parking parking) { if (parking.getTotalCapacity() == null) { issueStore.add( "MissingParkingCapacity", - "NeTEx Parking %s does not contain totalCapacity", - parking.getId() + "NeTEx Parking '%s' does not contain totalCapacity", + parkingDebugId(parking) ); return null; } @@ -55,6 +55,18 @@ VehicleParking map(Parking parking) { .build(); } + private static String parkingDebugId(Parking parking) { + if (parking.getId() != null) { + return parking.getId(); + } else if (parking.getName() != null) { + return parking.getName().getValue(); + } else if (parking.getCentroid() != null) { + return parking.getCentroid().toString(); + } else { + return parking.toString(); + } + } + private VehicleParking.VehicleParkingEntranceCreator mapEntrance(Parking parking) { return builder -> builder From 949a6c7c080507a578440d8213f965f226d54795 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 16 Jul 2024 12:49:13 +0200 Subject: [PATCH 066/148] Add comment --- docs/examples/entur/build-config.json | 3 ++- .../opentripplanner/netex/mapping/VehicleParkingMapper.java | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/examples/entur/build-config.json b/docs/examples/entur/build-config.json index e9351882774..c8f6b4c9ccd 100644 --- a/docs/examples/entur/build-config.json +++ b/docs/examples/entur/build-config.json @@ -33,7 +33,8 @@ { "type": "netex", "source": "gs://${OTP_GCS_BUCKET}/outbound/netex/rb_norway-aggregated-netex-otp2.zip", - "feedId": "EN" + "feedId": "EN", + "ignoreParkingData": true } ], "osm": [ diff --git a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java index f0ca2aeb51f..862c5f0c648 100644 --- a/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java +++ b/src/main/java/org/opentripplanner/netex/mapping/VehicleParkingMapper.java @@ -55,6 +55,10 @@ VehicleParking map(Parking parking) { .build(); } + /** + * In the Nordic profile many fields of {@link Parking} are optional so even adding the ID to the + * issue store can lead to NPEs. For this reason we have a lot of fallbacks. + */ private static String parkingDebugId(Parking parking) { if (parking.getId() != null) { return parking.getId(); From 44edc443d47947b6bc9ea6294be44d0c03d911fa Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 16 Jul 2024 13:55:46 +0200 Subject: [PATCH 067/148] Add config option to ignore parking --- docs/BuildConfiguration.md | 2 + docs/examples/entur/build-config.json | 2 +- .../opentripplanner/netex/NetexBundle.java | 9 ++-- .../netex/config/IgnorableFeature.java | 6 +++ .../netex/config/NetexFeedParameters.java | 41 ++++++++++++++----- .../netex/configure/NetexConfigure.java | 2 +- .../loader/parser/NetexDocumentParser.java | 18 ++++---- .../netex/loader/parser/SiteFrameParser.java | 13 +++++- .../config/buildconfig/NetexConfig.java | 9 ++++ .../loader/parser/SiteFrameParserTest.java | 3 +- 10 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 src/main/java/org/opentripplanner/netex/config/IgnorableFeature.java diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index cefd8d2cf2f..d6c6c87295d 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -78,6 +78,7 @@ Sections follow that describe particular settings in more depth. |    [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* | `false` | 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 | @@ -106,6 +107,7 @@ Sections follow that describe particular settings in more depth. |       [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* | `false` | 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 | diff --git a/docs/examples/entur/build-config.json b/docs/examples/entur/build-config.json index c8f6b4c9ccd..2acea588234 100644 --- a/docs/examples/entur/build-config.json +++ b/docs/examples/entur/build-config.json @@ -34,7 +34,7 @@ "type": "netex", "source": "gs://${OTP_GCS_BUCKET}/outbound/netex/rb_norway-aggregated-netex-otp2.zip", "feedId": "EN", - "ignoreParkingData": true + "ignoreParking": true } ], "osm": [ diff --git a/src/main/java/org/opentripplanner/netex/NetexBundle.java b/src/main/java/org/opentripplanner/netex/NetexBundle.java index 8d6f098de89..b9b8be224a9 100644 --- a/src/main/java/org/opentripplanner/netex/NetexBundle.java +++ b/src/main/java/org/opentripplanner/netex/NetexBundle.java @@ -9,6 +9,7 @@ import org.opentripplanner.datastore.api.DataSource; import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.model.impl.OtpTransitServiceBuilder; +import org.opentripplanner.netex.config.IgnorableFeature; import org.opentripplanner.netex.config.NetexFeedParameters; import org.opentripplanner.netex.index.NetexEntityIndex; import org.opentripplanner.netex.loader.GroupEntries; @@ -45,7 +46,7 @@ public class NetexBundle implements Closeable { private final Set ferryIdsNotAllowedForBicycle; private final double maxStopToShapeSnapDistance; private final boolean noTransfersOnIsolatedStops; - private final boolean ignoreFareFrame; + private final Set ignoredFeatures; /** The NeTEx entities loaded from the input files and passed on to the mapper. */ private NetexEntityIndex index = new NetexEntityIndex(); /** Report errors to issue store */ @@ -62,7 +63,7 @@ public NetexBundle( Set ferryIdsNotAllowedForBicycle, double maxStopToShapeSnapDistance, boolean noTransfersOnIsolatedStops, - boolean ignoreFareFrame + Set ignorableFeatures ) { this.feedId = feedId; this.source = source; @@ -71,7 +72,7 @@ public NetexBundle( this.ferryIdsNotAllowedForBicycle = ferryIdsNotAllowedForBicycle; this.maxStopToShapeSnapDistance = maxStopToShapeSnapDistance; this.noTransfersOnIsolatedStops = noTransfersOnIsolatedStops; - this.ignoreFareFrame = ignoreFareFrame; + this.ignoredFeatures = Set.copyOf(ignorableFeatures); } /** load the bundle, map it to the OTP transit model and return */ @@ -179,7 +180,7 @@ private void loadSingeFileEntry(String fileDescription, DataSource entry) { LOG.info("reading entity {}: {}", fileDescription, entry.name()); issueStore.startProcessingSource(entry.name()); PublicationDeliveryStructure doc = xmlParser.parseXmlDoc(entry.asInputStream()); - NetexDocumentParser.parseAndPopulateIndex(index, doc, ignoreFareFrame); + NetexDocumentParser.parseAndPopulateIndex(index, doc, ignoredFeatures); } catch (JAXBException e) { throw new RuntimeException(e.getMessage(), e); } finally { diff --git a/src/main/java/org/opentripplanner/netex/config/IgnorableFeature.java b/src/main/java/org/opentripplanner/netex/config/IgnorableFeature.java new file mode 100644 index 00000000000..4aa44e062d6 --- /dev/null +++ b/src/main/java/org/opentripplanner/netex/config/IgnorableFeature.java @@ -0,0 +1,6 @@ +package org.opentripplanner.netex.config; + +public enum IgnorableFeature { + FARE_FRAME, + PARKING, +} diff --git a/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java b/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java index cffecea0d48..4c0a50105b2 100644 --- a/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java +++ b/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java @@ -1,6 +1,8 @@ package org.opentripplanner.netex.config; import static java.util.Objects.requireNonNull; +import static org.opentripplanner.netex.config.IgnorableFeature.FARE_FRAME; +import static org.opentripplanner.netex.config.IgnorableFeature.PARKING; import java.net.URI; import java.util.Collection; @@ -29,7 +31,7 @@ public class NetexFeedParameters implements DataSourceConfig { private static final String SHARED_GROUP_FILE_PATTERN = "(\\w{3})-.*-shared\\.xml"; private static final String GROUP_FILE_PATTERN = "(\\w{3})-.*\\.xml"; private static final boolean NO_TRANSFERS_ON_ISOLATED_STOPS = false; - private static final boolean IGNORE_FARE_FRAME = false; + private static final Set IGNORED_FEATURES = Set.of(); private static final Set FERRY_IDS_NOT_ALLOWED_FOR_BICYCLE = Collections.emptySet(); @@ -48,7 +50,7 @@ public class NetexFeedParameters implements DataSourceConfig { private final String ignoreFilePattern; private final Set ferryIdsNotAllowedForBicycle; private final boolean noTransfersOnIsolatedStops; - private final boolean ignoreFareFrame; + private final Set ignoredFeatures; private NetexFeedParameters() { this.source = null; @@ -63,7 +65,7 @@ private NetexFeedParameters() { } this.ferryIdsNotAllowedForBicycle = FERRY_IDS_NOT_ALLOWED_FOR_BICYCLE; this.noTransfersOnIsolatedStops = NO_TRANSFERS_ON_ISOLATED_STOPS; - this.ignoreFareFrame = IGNORE_FARE_FRAME; + this.ignoredFeatures = IGNORED_FEATURES; } private NetexFeedParameters(Builder builder) { @@ -75,7 +77,7 @@ private NetexFeedParameters(Builder builder) { this.ignoreFilePattern = requireNonNull(builder.ignoreFilePattern); this.ferryIdsNotAllowedForBicycle = Set.copyOf(builder.ferryIdsNotAllowedForBicycle); this.noTransfersOnIsolatedStops = builder.noTransfersOnIsolatedStops; - this.ignoreFareFrame = builder.ignoreFareFrame; + this.ignoredFeatures = Set.copyOf(builder.ignoredFeatures); } public static Builder of() { @@ -127,7 +129,11 @@ public boolean noTransfersOnIsolatedStops() { /** See {@link org.opentripplanner.standalone.config.buildconfig.NetexConfig}. */ public boolean ignoreFareFrame() { - return ignoreFareFrame; + return ignoredFeatures.contains(FARE_FRAME); + } + + public boolean ignoreParking() { + return ignoredFeatures.contains(PARKING); } @Override @@ -142,7 +148,7 @@ public boolean equals(Object o) { sharedFilePattern.equals(that.sharedFilePattern) && sharedGroupFilePattern.equals(that.sharedGroupFilePattern) && groupFilePattern.equals(that.groupFilePattern) && - ignoreFareFrame == that.ignoreFareFrame && + ignoredFeatures == that.ignoredFeatures && ferryIdsNotAllowedForBicycle.equals(that.ferryIdsNotAllowedForBicycle) ); } @@ -156,7 +162,7 @@ public int hashCode() { sharedFilePattern, sharedGroupFilePattern, groupFilePattern, - ignoreFareFrame, + ignoredFeatures, ferryIdsNotAllowedForBicycle ); } @@ -171,11 +177,15 @@ public String toString() { .addStr("sharedGroupFilePattern", sharedGroupFilePattern, DEFAULT.sharedGroupFilePattern) .addStr("groupFilePattern", groupFilePattern, DEFAULT.groupFilePattern) .addStr("ignoreFilePattern", ignoreFilePattern, DEFAULT.ignoreFilePattern) - .addBoolIfTrue("ignoreFareFrame", ignoreFareFrame) + .addCol("ignoredFeatures", ignoredFeatures) .addCol("ferryIdsNotAllowedForBicycle", ferryIdsNotAllowedForBicycle, Set.of()) .toString(); } + public Set ignoredFeatures() { + return ignoredFeatures; + } + public static class Builder { private final NetexFeedParameters original; @@ -187,7 +197,7 @@ public static class Builder { private String ignoreFilePattern; private final Set ferryIdsNotAllowedForBicycle = new HashSet<>(); private boolean noTransfersOnIsolatedStops; - private boolean ignoreFareFrame; + private final Set ignoredFeatures; private Builder(NetexFeedParameters original) { this.original = original; @@ -199,7 +209,7 @@ private Builder(NetexFeedParameters original) { this.ignoreFilePattern = original.ignoreFilePattern; this.ferryIdsNotAllowedForBicycle.addAll(original.ferryIdsNotAllowedForBicycle); this.noTransfersOnIsolatedStops = original.noTransfersOnIsolatedStops; - this.ignoreFareFrame = original.ignoreFareFrame; + this.ignoredFeatures = new HashSet<>(original.ignoredFeatures); } public URI source() { @@ -247,7 +257,16 @@ public Builder withNoTransfersOnIsolatedStops(boolean noTransfersOnIsolatedStops } public Builder withIgnoreFareFrame(boolean ignoreFareFrame) { - this.ignoreFareFrame = ignoreFareFrame; + if (ignoreFareFrame) { + this.ignoredFeatures.add(FARE_FRAME); + } + return this; + } + + public Builder withIgnoreParking(boolean ignoreParking) { + if (ignoreParking) { + this.ignoredFeatures.add(PARKING); + } return this; } diff --git a/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java b/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java index 464c03f28e1..50c49836246 100644 --- a/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java +++ b/src/main/java/org/opentripplanner/netex/configure/NetexConfigure.java @@ -75,7 +75,7 @@ public NetexBundle netexBundle( config.ferryIdsNotAllowedForBicycle(), buildParams.maxStopToShapeSnapDistance, config.noTransfersOnIsolatedStops(), - config.ignoreFareFrame() + config.ignoredFeatures() ); } diff --git a/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java b/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java index 925b6dfd019..00ffa8a9d2f 100644 --- a/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java +++ b/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java @@ -1,8 +1,12 @@ package org.opentripplanner.netex.loader.parser; +import static org.opentripplanner.netex.config.IgnorableFeature.FARE_FRAME; + import jakarta.xml.bind.JAXBElement; import java.util.Collection; import java.util.List; +import java.util.Set; +import org.opentripplanner.netex.config.IgnorableFeature; import org.opentripplanner.netex.index.NetexEntityIndex; import org.rutebanken.netex.model.Common_VersionFrameStructure; import org.rutebanken.netex.model.CompositeFrame; @@ -30,11 +34,11 @@ public class NetexDocumentParser { private static final Logger LOG = LoggerFactory.getLogger(NetexDocumentParser.class); private final NetexEntityIndex netexIndex; - private final boolean ignoreFareFrame; + private final Set ignoredFeatures; - private NetexDocumentParser(NetexEntityIndex netexIndex, boolean ignoreFareFrame) { + private NetexDocumentParser(NetexEntityIndex netexIndex, Set ignoredFeatures) { this.netexIndex = netexIndex; - this.ignoreFareFrame = ignoreFareFrame; + this.ignoredFeatures = ignoredFeatures; } /** @@ -44,9 +48,9 @@ private NetexDocumentParser(NetexEntityIndex netexIndex, boolean ignoreFareFrame public static void parseAndPopulateIndex( NetexEntityIndex index, PublicationDeliveryStructure doc, - boolean ignoreFareFrame + Set ignoredFeatures ) { - new NetexDocumentParser(index, ignoreFareFrame).parse(doc); + new NetexDocumentParser(index, ignoredFeatures).parse(doc); } public static void finnishUp() { @@ -74,8 +78,8 @@ private void parseCommonFrame(Common_VersionFrameStructure value) { } else if (value instanceof ServiceFrame) { parse((ServiceFrame) value, new ServiceFrameParser(netexIndex.flexibleStopPlaceById)); } else if (value instanceof SiteFrame) { - parse((SiteFrame) value, new SiteFrameParser()); - } else if (!ignoreFareFrame && value instanceof FareFrame) { + parse((SiteFrame) value, new SiteFrameParser(ignoredFeatures)); + } else if (ignoredFeatures.contains(FARE_FRAME) && value instanceof FareFrame) { parse((FareFrame) value, new FareFrameParser()); } else if (value instanceof CompositeFrame) { // We recursively parse composite frames and content until there diff --git a/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java b/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java index 7651bbb39d9..38cd91ded98 100644 --- a/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java +++ b/src/main/java/org/opentripplanner/netex/loader/parser/SiteFrameParser.java @@ -1,10 +1,14 @@ package org.opentripplanner.netex.loader.parser; +import static org.opentripplanner.netex.config.IgnorableFeature.PARKING; + import jakarta.xml.bind.JAXBElement; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Set; import org.opentripplanner.framework.application.OTPFeature; +import org.opentripplanner.netex.config.IgnorableFeature; import org.opentripplanner.netex.index.NetexEntityIndex; import org.opentripplanner.netex.support.JAXBUtils; import org.rutebanken.netex.model.FlexibleStopPlace; @@ -36,8 +40,15 @@ class SiteFrameParser extends NetexParser { private final Collection tariffZones = new ArrayList<>(); private final Collection quays = new ArrayList<>(); + private final Collection parkings = new ArrayList<>(0); + private final Set ignoredFeatures; + + SiteFrameParser(Set ignoredFeatures) { + this.ignoredFeatures = ignoredFeatures; + } + @Override public void parse(Site_VersionFrameStructure frame) { if (frame.getStopPlaces() != null) { @@ -54,7 +65,7 @@ public void parse(Site_VersionFrameStructure frame) { parseTariffZones(frame.getTariffZones().getTariffZone()); } - if (frame.getParkings() != null) { + if (!ignoredFeatures.contains(PARKING) && frame.getParkings() != null) { parseParkings(frame.getParkings().getParking()); } // Keep list sorted alphabetically diff --git a/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java b/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java index d4cd4521e54..6be400b7549 100644 --- a/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java @@ -3,6 +3,7 @@ import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_0; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2; import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_6; import org.opentripplanner.netex.config.NetexFeedParameters; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; @@ -164,6 +165,14 @@ private static NetexFeedParameters.Builder mapFilePatternParameters( .summary("Ignore contents of the FareFrame") .docDefaultValue(base.ignoreFareFrame()) .asBoolean(base.ignoreFareFrame()) + ) + .withIgnoreParking( + config + .of("ignoreParking") + .since(V2_6) + .summary("Ignore Parking elements.") + .docDefaultValue(base.ignoreParking()) + .asBoolean(base.ignoreFareFrame()) ); } diff --git a/src/test/java/org/opentripplanner/netex/loader/parser/SiteFrameParserTest.java b/src/test/java/org/opentripplanner/netex/loader/parser/SiteFrameParserTest.java index efd9365b84d..bc60cff2d0e 100644 --- a/src/test/java/org/opentripplanner/netex/loader/parser/SiteFrameParserTest.java +++ b/src/test/java/org/opentripplanner/netex/loader/parser/SiteFrameParserTest.java @@ -6,6 +6,7 @@ import jakarta.xml.bind.JAXBElement; import java.util.Collection; +import java.util.Set; import org.junit.jupiter.api.Test; import org.opentripplanner.netex.NetexTestDataSupport; import org.opentripplanner.netex.index.NetexEntityIndex; @@ -21,7 +22,7 @@ class SiteFrameParserTest { @Test void testParseQuays() { - SiteFrameParser siteFrameParser = new SiteFrameParser(); + SiteFrameParser siteFrameParser = new SiteFrameParser(Set.of()); SiteFrame siteFrame = OBJECT_FACTORY.createSiteFrame(); NetexEntityIndex netexEntityIndex = new NetexEntityIndex(); From 3a88dd313952d260aae8b4d17e9a5a96cfee2ba4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 16 Jul 2024 14:06:07 +0200 Subject: [PATCH 068/148] Add code reuse --- .../netex/config/NetexFeedParameters.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java b/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java index 4c0a50105b2..5ff5004d0c8 100644 --- a/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java +++ b/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java @@ -257,15 +257,18 @@ public Builder withNoTransfersOnIsolatedStops(boolean noTransfersOnIsolatedStops } public Builder withIgnoreFareFrame(boolean ignoreFareFrame) { - if (ignoreFareFrame) { - this.ignoredFeatures.add(FARE_FRAME); - } - return this; + return applyIgnore(ignoreFareFrame, FARE_FRAME); } public Builder withIgnoreParking(boolean ignoreParking) { - if (ignoreParking) { - this.ignoredFeatures.add(PARKING); + return applyIgnore(ignoreParking, PARKING); + } + + private Builder applyIgnore(boolean ignore, IgnorableFeature feature) { + if (ignore) { + ignoredFeatures.add(feature); + } else { + ignoredFeatures.remove(feature); } return this; } From 1238429266cf33bc079cbb6770f9e6f6e8ecd648 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 16 Jul 2024 15:19:03 +0200 Subject: [PATCH 069/148] Add Javadoc --- .../org/opentripplanner/netex/config/IgnorableFeature.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/opentripplanner/netex/config/IgnorableFeature.java b/src/main/java/org/opentripplanner/netex/config/IgnorableFeature.java index 4aa44e062d6..53fe7f87f48 100644 --- a/src/main/java/org/opentripplanner/netex/config/IgnorableFeature.java +++ b/src/main/java/org/opentripplanner/netex/config/IgnorableFeature.java @@ -1,5 +1,8 @@ package org.opentripplanner.netex.config; +/** + * Optional data that can be ignored during the NeTEx parsing process. + */ public enum IgnorableFeature { FARE_FRAME, PARKING, From cc659fade4f5a693d01176d7e9da664bf35c840f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 16 Jul 2024 15:20:28 +0200 Subject: [PATCH 070/148] Replace typo in 'finishUp' --- src/main/java/org/opentripplanner/netex/NetexBundle.java | 2 +- .../netex/loader/parser/NetexDocumentParser.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/netex/NetexBundle.java b/src/main/java/org/opentripplanner/netex/NetexBundle.java index b9b8be224a9..3cd52cd246e 100644 --- a/src/main/java/org/opentripplanner/netex/NetexBundle.java +++ b/src/main/java/org/opentripplanner/netex/NetexBundle.java @@ -137,7 +137,7 @@ private void loadFileEntries() { }); } mapper.finishUp(); - NetexDocumentParser.finnishUp(); + NetexDocumentParser.finishUp(); } /** diff --git a/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java b/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java index 00ffa8a9d2f..763217f7e52 100644 --- a/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java +++ b/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java @@ -53,7 +53,7 @@ public static void parseAndPopulateIndex( new NetexDocumentParser(index, ignoredFeatures).parse(doc); } - public static void finnishUp() { + public static void finishUp() { ServiceFrameParser.logSummary(); } From 990ce7c52a6a06f020a772c3e95df63d46b45d30 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 11:39:32 +0200 Subject: [PATCH 071/148] Invert check --- .../netex/loader/parser/NetexDocumentParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java b/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java index 763217f7e52..f8058f2df8e 100644 --- a/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java +++ b/src/main/java/org/opentripplanner/netex/loader/parser/NetexDocumentParser.java @@ -79,7 +79,7 @@ private void parseCommonFrame(Common_VersionFrameStructure value) { parse((ServiceFrame) value, new ServiceFrameParser(netexIndex.flexibleStopPlaceById)); } else if (value instanceof SiteFrame) { parse((SiteFrame) value, new SiteFrameParser(ignoredFeatures)); - } else if (ignoredFeatures.contains(FARE_FRAME) && value instanceof FareFrame) { + } else if (!ignoredFeatures.contains(FARE_FRAME) && value instanceof FareFrame) { parse((FareFrame) value, new FareFrameParser()); } else if (value instanceof CompositeFrame) { // We recursively parse composite frames and content until there From 7b1f7602612702aa41b60aef285db82a5587d6b8 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 23 Jul 2024 15:55:33 +0200 Subject: [PATCH 072/148] Fix copy&paste error Co-authored-by: Henrik Abrahamsson <127481124+habrahamsson-skanetrafiken@users.noreply.github.com> --- .../standalone/config/buildconfig/NetexConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java b/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java index 6be400b7549..52bccf605d8 100644 --- a/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/buildconfig/NetexConfig.java @@ -172,7 +172,7 @@ private static NetexFeedParameters.Builder mapFilePatternParameters( .since(V2_6) .summary("Ignore Parking elements.") .docDefaultValue(base.ignoreParking()) - .asBoolean(base.ignoreFareFrame()) + .asBoolean(base.ignoreParking()) ); } From 1876a044cf718478ea89c23ded99c62e259b1b94 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 23 Jul 2024 16:09:17 +0200 Subject: [PATCH 073/148] Replace identity with equality --- .../org/opentripplanner/netex/config/NetexFeedParameters.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java b/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java index 5ff5004d0c8..f565ea5a187 100644 --- a/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java +++ b/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java @@ -148,7 +148,7 @@ public boolean equals(Object o) { sharedFilePattern.equals(that.sharedFilePattern) && sharedGroupFilePattern.equals(that.sharedGroupFilePattern) && groupFilePattern.equals(that.groupFilePattern) && - ignoredFeatures == that.ignoredFeatures && + ignoredFeatures.equals(that.ignoredFeatures) && ferryIdsNotAllowedForBicycle.equals(that.ferryIdsNotAllowedForBicycle) ); } From 1cfaa3800ddf777e47785329b27199cff3b8599a Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 23 Jul 2024 16:22:57 +0200 Subject: [PATCH 074/148] Add Javadoc for KeyValue --- .../inspector/vector/KeyValue.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java b/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java index 8557ad0a465..edad5e1b295 100644 --- a/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java +++ b/src/main/java/org/opentripplanner/inspector/vector/KeyValue.java @@ -1,14 +1,30 @@ package org.opentripplanner.inspector.vector; +import jakarta.annotation.Nullable; import java.util.Collection; import java.util.stream.Collectors; import org.opentripplanner.transit.model.framework.FeedScopedId; +/** + * A key value pair that represents data being sent to the vector tile library for visualisation + * in a map (including popups). + *

+ * The underlying format (and library) supports only a limited number of Java types and silently + * drops those that aren't supported: https://github.com/CI-CMG/mapbox-vector-tile/blob/master/src/main/java/edu/colorado/cires/cmg/mvt/encoding/MvtValue.java#L18-L40 + *

+ * For this reason this class also has static initializer that automatically converts common + * OTP classes into vector tile-compatible strings. + */ public record KeyValue(String key, Object value) { public static KeyValue kv(String key, Object value) { return new KeyValue(key, value); } - public static KeyValue kv(String key, FeedScopedId value) { + + /** + * A {@link FeedScopedId} is not a type that can be converted to a vector tile feature property + * value. Therefore, we convert it to a string after performing a null check. + */ + public static KeyValue kv(String key, @Nullable FeedScopedId value) { if (value != null) { return new KeyValue(key, value.toString()); } else { From a2eaecc797bd9a58d8191d972822825ec1de76b6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 23 Jul 2024 16:31:05 +0200 Subject: [PATCH 075/148] Rename variable --- .../street/model/vertex/VehicleParkingEntranceVertex.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java index 2f389f60817..ba7a1540715 100644 --- a/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java +++ b/src/main/java/org/opentripplanner/street/model/vertex/VehicleParkingEntranceVertex.java @@ -59,7 +59,7 @@ public boolean isLinkedToGraph() { return hasLink(getIncoming()) || hasLink(getOutgoing()); } - private boolean hasLink(Collection incoming) { - return incoming.stream().anyMatch(StreetVehicleParkingLink.class::isInstance); + private boolean hasLink(Collection edges) { + return edges.stream().anyMatch(StreetVehicleParkingLink.class::isInstance); } } From ae363f416f5ff830e5e7b0bec9a97e837a5465a6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 24 Jul 2024 16:31:44 +0200 Subject: [PATCH 076/148] Update src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java Co-authored-by: Thomas Gran --- .../org/opentripplanner/apis/gtfs/model/LocalDateRange.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java b/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java index d466534e65c..18a1a1624e3 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java @@ -27,9 +27,9 @@ public boolean startBeforeEnd() { public boolean contains(LocalDate date) { return ( ( - startInclusive() == null || date.isEqual(startInclusive()) || date.isAfter(startInclusive()) + startInclusive == null || date.isEqual(startInclusive) || date.isAfter(startInclusive) ) && - (endExclusive() == null || date.isBefore(endExclusive())) + (endExclusive == null || date.isBefore(endExclusive)) ); } } From 1ebfc42d98e6ef99bb149efdf0c5e9ffe6305dab Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 24 Jul 2024 19:41:26 +0200 Subject: [PATCH 077/148] Format code --- .../org/opentripplanner/apis/gtfs/model/LocalDateRange.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java b/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java index 18a1a1624e3..dfecfdcd960 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/LocalDateRange.java @@ -26,9 +26,7 @@ public boolean startBeforeEnd() { */ public boolean contains(LocalDate date) { return ( - ( - startInclusive == null || date.isEqual(startInclusive) || date.isAfter(startInclusive) - ) && + (startInclusive == null || date.isEqual(startInclusive) || date.isAfter(startInclusive)) && (endExclusive == null || date.isBefore(endExclusive)) ); } From 80fedfa9275e046de3583bdc52077ccefb0cc963 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 25 Jul 2024 11:47:49 +0200 Subject: [PATCH 078/148] Make method private --- .../apis/gtfs/mapping/LocalDateRangeMapperTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java index 81b3b283151..ebf3a81d681 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java @@ -16,7 +16,7 @@ class LocalDateRangeMapperTest { private static final LocalDate DATE = LocalDate.parse("2024-05-27"); - public static List noFilterCases() { + private static List noFilterCases() { var list = new ArrayList(); list.add(null); list.add(new GraphQLTypes.GraphQLLocalDateRangeInput(Map.of())); From 3cf6130f82e3062ca25a280865fcc164fff15168 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 25 Jul 2024 12:07:45 +0200 Subject: [PATCH 079/148] Make method private --- .../apis/gtfs/mapping/LocalDateRangeMapperTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java index ebf3a81d681..cbd771df933 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LocalDateRangeMapperTest.java @@ -29,7 +29,7 @@ void hasNoServiceDateFilter(GraphQLTypes.GraphQLLocalDateRangeInput input) { assertFalse(LocalDateRangeUtil.hasServiceDateFilter(input)); } - public static List> hasFilterCases() { + private static List> hasFilterCases() { return List.of(Map.of("start", DATE), Map.of("end", DATE), Map.of("start", DATE, "end", DATE)); } From 6eae01914321b774bb5511578cc0afefdf5463a6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 25 Jul 2024 16:16:54 +0200 Subject: [PATCH 080/148] Ignore NeTEx parking by default --- docs/BuildConfiguration.md | 4 ++-- .../org/opentripplanner/netex/config/NetexFeedParameters.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/BuildConfiguration.md b/docs/BuildConfiguration.md index d6c6c87295d..6a6b42cd664 100644 --- a/docs/BuildConfiguration.md +++ b/docs/BuildConfiguration.md @@ -78,7 +78,7 @@ Sections follow that describe particular settings in more depth. |    [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* | `false` | 2.6 | +|    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 | @@ -107,7 +107,7 @@ Sections follow that describe particular settings in more depth. |       [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* | `false` | 2.6 | +|       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 | diff --git a/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java b/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java index f565ea5a187..0c6c75c4db3 100644 --- a/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java +++ b/src/main/java/org/opentripplanner/netex/config/NetexFeedParameters.java @@ -31,7 +31,7 @@ public class NetexFeedParameters implements DataSourceConfig { private static final String SHARED_GROUP_FILE_PATTERN = "(\\w{3})-.*-shared\\.xml"; private static final String GROUP_FILE_PATTERN = "(\\w{3})-.*\\.xml"; private static final boolean NO_TRANSFERS_ON_ISOLATED_STOPS = false; - private static final Set IGNORED_FEATURES = Set.of(); + private static final Set IGNORED_FEATURES = Set.of(PARKING); private static final Set FERRY_IDS_NOT_ALLOWED_FOR_BICYCLE = Collections.emptySet(); From ed6b165cf06a1e5ebdd90ddcdcfe8fc0709da664 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 25 Jul 2024 16:25:00 +0200 Subject: [PATCH 081/148] Update tests --- .../opentripplanner/netex/config/NetexFeedParametersTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/netex/config/NetexFeedParametersTest.java b/src/test/java/org/opentripplanner/netex/config/NetexFeedParametersTest.java index d010e3a0d9e..93624afe339 100644 --- a/src/test/java/org/opentripplanner/netex/config/NetexFeedParametersTest.java +++ b/src/test/java/org/opentripplanner/netex/config/NetexFeedParametersTest.java @@ -89,7 +89,7 @@ void testCopyOfEqualsAndHashCode() { @Test void testToString() { - assertEquals("NetexFeedParameters{}", DEFAULT.toString()); + assertEquals("NetexFeedParameters{ignoredFeatures: [PARKING]}", DEFAULT.toString()); assertEquals( "NetexFeedParameters{" + "source: https://my.test.com, " + @@ -98,6 +98,7 @@ void testToString() { "sharedGroupFilePattern: '[sharedGoupFil]+', " + "groupFilePattern: '[groupFile]+', " + "ignoreFilePattern: '[ignoreFl]+', " + + "ignoredFeatures: [PARKING], " + "ferryIdsNotAllowedForBicycle: [Ferry:Id]" + "}", subject.toString() From faa7dfa10188c29c2347416685d334ddb58c4ec4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 27 Jul 2024 22:11:24 +0000 Subject: [PATCH 082/148] fix(deps): update dependency net.logstash.logback:logstash-logback-encoder to v8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2f007cbb214..b4799a15c40 100644 --- a/pom.xml +++ b/pom.xml @@ -577,7 +577,7 @@ net.logstash.logback logstash-logback-encoder - 7.4 + 8.0 From a3147304f73578f5eac4a409020331398731285c Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 29 Jul 2024 12:37:40 +0000 Subject: [PATCH 083/148] Add changelog entry for #5946 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 1527e18c873..d9650be1dcb 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -51,6 +51,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Add debug information for stop/quay ID and stay-seated transfers [#5962](https://github.com/opentripplanner/OpenTripPlanner/pull/5962) - Handle NeTEx `any` version [#5983](https://github.com/opentripplanner/OpenTripPlanner/pull/5983) - Keep at least one result for min-transfers and each transit-group in itinerary-group-filter [#5919](https://github.com/opentripplanner/OpenTripPlanner/pull/5919) +- Extract parking lots from NeTEx feeds [#5946](https://github.com/opentripplanner/OpenTripPlanner/pull/5946) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.5.0 (2024-03-13) From 4c33d38bedd62348321de9537af901cec5358dbd Mon Sep 17 00:00:00 2001 From: OTP Serialization Version Bot Date: Mon, 29 Jul 2024 12:38:00 +0000 Subject: [PATCH 084/148] Bump serialization version id for #5946 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2f007cbb214..9d5aa615260 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 155 + 156 31.3 2.51.1 From f2d24aefb8f80f38c8ddb9d5765d17dfc7c94b81 Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Mon, 29 Jul 2024 12:38:58 +0000 Subject: [PATCH 085/148] Upgrade debug client to version 2024/07/2024-07-29T12:38 --- src/client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/index.html b/src/client/index.html index 767f8b1d45a..fd0c2e937f7 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +

From 18625fc58ee915eaf04821a1646a8076046dfbce Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Tue, 30 Jul 2024 08:29:51 +0000 Subject: [PATCH 086/148] Add changelog entry for #5869 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index d9650be1dcb..64840e36790 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -52,6 +52,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Handle NeTEx `any` version [#5983](https://github.com/opentripplanner/OpenTripPlanner/pull/5983) - Keep at least one result for min-transfers and each transit-group in itinerary-group-filter [#5919](https://github.com/opentripplanner/OpenTripPlanner/pull/5919) - Extract parking lots from NeTEx feeds [#5946](https://github.com/opentripplanner/OpenTripPlanner/pull/5946) +- Filter routes and patterns by service date in GTFS GraphQL API [#5869](https://github.com/opentripplanner/OpenTripPlanner/pull/5869) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.5.0 (2024-03-13) From 44864d67c0ff19e3f01596110a291e7b5d69dc47 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:48:03 +0000 Subject: [PATCH 087/148] Update Debug UI dependencies (non-major) --- client/package-lock.json | 192 +++++++++++++++++++-------------------- client/package.json | 12 +-- 2 files changed, 102 insertions(+), 102 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index dc6c6dfa1a3..9e2798660ec 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -20,16 +20,16 @@ }, "devDependencies": { "@graphql-codegen/cli": "5.0.2", - "@graphql-codegen/client-preset": "4.3.2", + "@graphql-codegen/client-preset": "4.3.3", "@graphql-codegen/introspection": "4.0.3", "@parcel/watcher": "2.4.1", "@testing-library/react": "16.0.0", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", - "@typescript-eslint/eslint-plugin": "7.17.0", - "@typescript-eslint/parser": "7.17.0", + "@typescript-eslint/eslint-plugin": "7.18.0", + "@typescript-eslint/parser": "7.18.0", "@vitejs/plugin-react": "4.3.1", - "@vitest/coverage-v8": "2.0.4", + "@vitest/coverage-v8": "2.0.5", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "2.29.1", @@ -40,8 +40,8 @@ "jsdom": "24.1.1", "prettier": "3.3.3", "typescript": "5.5.4", - "vite": "5.3.4", - "vitest": "2.0.4" + "vite": "5.3.5", + "vitest": "2.0.5" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1861,9 +1861,9 @@ } }, "node_modules/@graphql-codegen/client-preset": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-4.3.2.tgz", - "integrity": "sha512-42jHyG6u2uFDIVNvzue8zR529aPT16EYIJQmvMk8XuYHo3PneQVlWmQ3j2fBy+RuWCBzpJKPKm7IGSKiw19nmg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-4.3.3.tgz", + "integrity": "sha512-IrDsSVe8bkKtxgVfKPHzjL9tYlv7KEpA59R4gZLqx/t2WIJncW1i0OMvoz9tgoZsFEs8OKKgXZbnwPZ/Qf1kEw==", "dev": true, "license": "MIT", "dependencies": { @@ -3943,17 +3943,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz", - "integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/type-utils": "7.17.0", - "@typescript-eslint/utils": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -3977,16 +3977,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz", - "integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/typescript-estree": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "engines": { @@ -4006,14 +4006,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz", - "integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -4024,14 +4024,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz", - "integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.17.0", - "@typescript-eslint/utils": "7.17.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -4052,9 +4052,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz", - "integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true, "license": "MIT", "engines": { @@ -4066,14 +4066,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz", - "integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4108,16 +4108,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz", - "integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/typescript-estree": "7.17.0" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -4131,13 +4131,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz", - "integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -4175,9 +4175,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.0.4.tgz", - "integrity": "sha512-i4lx/Wpg5zF1h2op7j0wdwuEQxaL/YTwwQaKuKMHYj7MMh8c7I4W7PNfOptZBCSBZI0z1qwn64o0pM/pA8Tz1g==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.0.5.tgz", + "integrity": "sha512-qeFcySCg5FLO2bHHSa0tAZAOnAUbp4L6/A5JDuj9+bt53JREl8hpLjLHEWF0e/gWc8INVpJaqA7+Ene2rclpZg==", "dev": true, "license": "MIT", "dependencies": { @@ -4198,18 +4198,18 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "2.0.4" + "vitest": "2.0.5" } }, "node_modules/@vitest/expect": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.4.tgz", - "integrity": "sha512-39jr5EguIoanChvBqe34I8m1hJFI4+jxvdOpD7gslZrVQBKhh8H9eD7J/LJX4zakrw23W+dITQTDqdt43xVcJw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", + "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.0.4", - "@vitest/utils": "2.0.4", + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", "chai": "^5.1.1", "tinyrainbow": "^1.2.0" }, @@ -4218,9 +4218,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.4.tgz", - "integrity": "sha512-RYZl31STbNGqf4l2eQM1nvKPXE0NhC6Eq0suTTePc4mtMQ1Fn8qZmjV4emZdEdG2NOWGKSCrHZjmTqDCDoeFBw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4231,13 +4231,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.4.tgz", - "integrity": "sha512-Gk+9Su/2H2zNfNdeJR124gZckd5st4YoSuhF1Rebi37qTXKnqYyFCd9KP4vl2cQHbtuVKjfEKrNJxHHCW8thbQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz", + "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.0.4", + "@vitest/utils": "2.0.5", "pathe": "^1.1.2" }, "funding": { @@ -4245,13 +4245,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.4.tgz", - "integrity": "sha512-or6Mzoz/pD7xTvuJMFYEtso1vJo1S5u6zBTinfl+7smGUhqybn6VjzCDMhmTyVOFWwkCMuNjmNNxnyXPgKDoPw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.4", + "@vitest/pretty-format": "2.0.5", "magic-string": "^0.30.10", "pathe": "^1.1.2" }, @@ -4260,9 +4260,9 @@ } }, "node_modules/@vitest/spy": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.4.tgz", - "integrity": "sha512-uTXU56TNoYrTohb+6CseP8IqNwlNdtPwEO0AWl+5j7NelS6x0xZZtP0bDWaLvOfUbaYwhhWp1guzXUxkC7mW7Q==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", + "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", "dev": true, "license": "MIT", "dependencies": { @@ -4273,13 +4273,13 @@ } }, "node_modules/@vitest/utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.4.tgz", - "integrity": "sha512-Zc75QuuoJhOBnlo99ZVUkJIuq4Oj0zAkrQ2VzCqNCx6wAwViHEh5Fnp4fiJTE9rA+sAoXRf00Z9xGgfEzV6fzQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", + "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.4", + "@vitest/pretty-format": "2.0.5", "estree-walker": "^3.0.3", "loupe": "^3.1.1", "tinyrainbow": "^1.2.0" @@ -11392,9 +11392,9 @@ } }, "node_modules/vite": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz", - "integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", + "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", "dev": true, "license": "MIT", "dependencies": { @@ -11448,9 +11448,9 @@ } }, "node_modules/vite-node": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.4.tgz", - "integrity": "sha512-ZpJVkxcakYtig5iakNeL7N3trufe3M6vGuzYAr4GsbCTwobDeyPJpE4cjDhhPluv8OvQCFzu2LWp6GkoKRITXA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", + "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", "dev": true, "license": "MIT", "dependencies": { @@ -11471,19 +11471,19 @@ } }, "node_modules/vitest": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.4.tgz", - "integrity": "sha512-luNLDpfsnxw5QSW4bISPe6tkxVvv5wn2BBs/PuDRkhXZ319doZyLOBr1sjfB5yCEpTiU7xCAdViM8TNVGPwoog==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz", + "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", - "@vitest/expect": "2.0.4", - "@vitest/pretty-format": "^2.0.4", - "@vitest/runner": "2.0.4", - "@vitest/snapshot": "2.0.4", - "@vitest/spy": "2.0.4", - "@vitest/utils": "2.0.4", + "@vitest/expect": "2.0.5", + "@vitest/pretty-format": "^2.0.5", + "@vitest/runner": "2.0.5", + "@vitest/snapshot": "2.0.5", + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", "chai": "^5.1.1", "debug": "^4.3.5", "execa": "^8.0.1", @@ -11494,7 +11494,7 @@ "tinypool": "^1.0.0", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.0.4", + "vite-node": "2.0.5", "why-is-node-running": "^2.3.0" }, "bin": { @@ -11509,8 +11509,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.0.4", - "@vitest/ui": "2.0.4", + "@vitest/browser": "2.0.5", + "@vitest/ui": "2.0.5", "happy-dom": "*", "jsdom": "*" }, diff --git a/client/package.json b/client/package.json index 8ce0ed0cbdb..fe8836e8d36 100644 --- a/client/package.json +++ b/client/package.json @@ -29,16 +29,16 @@ }, "devDependencies": { "@graphql-codegen/cli": "5.0.2", - "@graphql-codegen/client-preset": "4.3.2", + "@graphql-codegen/client-preset": "4.3.3", "@graphql-codegen/introspection": "4.0.3", "@parcel/watcher": "2.4.1", "@testing-library/react": "16.0.0", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", - "@typescript-eslint/eslint-plugin": "7.17.0", - "@typescript-eslint/parser": "7.17.0", + "@typescript-eslint/eslint-plugin": "7.18.0", + "@typescript-eslint/parser": "7.18.0", "@vitejs/plugin-react": "4.3.1", - "@vitest/coverage-v8": "2.0.4", + "@vitest/coverage-v8": "2.0.5", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "2.29.1", @@ -49,7 +49,7 @@ "jsdom": "24.1.1", "prettier": "3.3.3", "typescript": "5.5.4", - "vite": "5.3.4", - "vitest": "2.0.4" + "vite": "5.3.5", + "vitest": "2.0.5" } } From d04c26c2212597f5a4efef2d55f6ace8c731ed86 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 31 Jul 2024 22:24:36 +0200 Subject: [PATCH 088/148] Automerge truth, fixes #5988 [ci skip] --- renovate.json5 | 1 + 1 file changed, 1 insertion(+) diff --git a/renovate.json5 b/renovate.json5 index d8ba10984e5..5ac10731e05 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -114,6 +114,7 @@ "com.tngtech.archunit:archunit", "org.apache.maven.plugins:maven-surefire-plugin", "me.fabriciorby:maven-surefire-junit5-tree-reporter", + "com.google.truth:truth", "org.jacoco:jacoco-maven-plugin", // coverage plugin "org.apache.commons:commons-compress", // only used by tests // maven plugins From b04d1d56052fbd199e76d004d9d48afbc592bef0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 31 Jul 2024 22:36:37 +0200 Subject: [PATCH 089/148] Schedule test dependency upgrades once a month [ci skip] --- renovate.json5 | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/renovate.json5 b/renovate.json5 index 5ac10731e05..98ec1f24fbe 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -44,6 +44,23 @@ }, // some dependencies that we auto-merge release very often and even the auto-merges create a lot of // noise, so we slow it down a bit + { + "matchPackageNames": [ + "org.mockito:mockito-core", + "com.tngtech.archunit:archunit", + "org.apache.maven.plugins:maven-surefire-plugin", + "me.fabriciorby:maven-surefire-junit5-tree-reporter", + "com.google.truth:truth", + "org.jacoco:jacoco-maven-plugin", // coverage plugin + "org.apache.commons:commons-compress" // only used by tests + ], + "matchPackagePrefixes": [ + "org.junit.jupiter:", + ], + "groupName": "Test dependencies", + "automerge": true, + "schedule": "on the 17th day of the month" + }, { "matchPackageNames": [ "org.mobilitydata:gbfs-java-model" @@ -110,13 +127,6 @@ { "description": "automatically merge test, logging and build dependencies", "matchPackageNames": [ - "org.mockito:mockito-core", - "com.tngtech.archunit:archunit", - "org.apache.maven.plugins:maven-surefire-plugin", - "me.fabriciorby:maven-surefire-junit5-tree-reporter", - "com.google.truth:truth", - "org.jacoco:jacoco-maven-plugin", // coverage plugin - "org.apache.commons:commons-compress", // only used by tests // maven plugins "org.codehaus.mojo:build-helper-maven-plugin", "org.apache.maven.plugins:maven-source-plugin", @@ -128,7 +138,6 @@ "org.sonatype.plugins:nexus-staging-maven-plugin" ], "matchPackagePrefixes": [ - "org.junit.jupiter:", "org.slf4j:" ], "automerge": true, From 669d042ae3aad3c8b03762f464c67b73b5ae5646 Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Thu, 1 Aug 2024 06:46:11 +0000 Subject: [PATCH 090/148] Upgrade debug client to version 2024/08/2024-08-01T06:45 --- src/client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/index.html b/src/client/index.html index fd0c2e937f7..d1734898ca0 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +
From 9f5aa460bfa0059e67b58d687bba07a1404cf127 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 2 Aug 2024 09:43:26 +0200 Subject: [PATCH 091/148] Remove unused field in TransitModel --- .../raptoradapter/transit/TransitLayer.java | 16 ---------------- .../transit/mappers/TransitLayerMapper.java | 1 - .../mapping/RaptorPathToItineraryMapperTest.java | 1 - .../raptoradapter/transit/TransitLayerTest.java | 5 ----- 4 files changed, 23 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayer.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayer.java index c92bbd750ad..e5f0544f584 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayer.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayer.java @@ -1,7 +1,6 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit; import java.time.LocalDate; -import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -51,8 +50,6 @@ public class TransitLayer { private final StopModel stopModel; - private final ZoneId transitDataZoneId; - private final RaptorRequestTransferCache transferCache; private ConstrainedTransfersForPatterns constrainedTransfers; @@ -73,7 +70,6 @@ public TransitLayer(TransitLayer transitLayer) { transitLayer.transfersByStopIndex, transitLayer.transferService, transitLayer.stopModel, - transitLayer.transitDataZoneId, transitLayer.transferCache, transitLayer.constrainedTransfers, transitLayer.transferIndexGenerator, @@ -86,7 +82,6 @@ public TransitLayer( List> transfersByStopIndex, TransferService transferService, StopModel stopModel, - ZoneId transitDataZoneId, RaptorRequestTransferCache transferCache, ConstrainedTransfersForPatterns constrainedTransfers, TransferIndexGenerator transferIndexGenerator, @@ -96,7 +91,6 @@ public TransitLayer( this.transfersByStopIndex = transfersByStopIndex; this.transferService = transferService; this.stopModel = stopModel; - this.transitDataZoneId = transitDataZoneId; this.transferCache = transferCache; this.constrainedTransfers = constrainedTransfers; this.transferIndexGenerator = transferIndexGenerator; @@ -117,16 +111,6 @@ public Collection getTripPatternsForRunningDate(LocalDate da return tripPatternsRunningOnDate.getOrDefault(date, List.of()); } - /** - * This is the time zone which is used for interpreting all local "service" times (in transfers, - * trip schedules and so on). This is the time zone of the internal OTP time - which is used in - * logging and debugging. This is independent of the time zone of imported data and of the time - * zone used on any API - it can be the same, but it does not need to. - */ - public ZoneId getTransitDataZoneId() { - return transitDataZoneId; - } - public int getStopCount() { return stopModel.stopIndexSize(); } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java index 8ce328fe1b6..d375b4c546c 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java @@ -103,7 +103,6 @@ private TransitLayer map(TransitTuningParameters tuningParameters) { transferByStopIndex, transitService.getTransferService(), stopModel, - transitService.getTimeZone(), transferCache, constrainedTransfers, transferIndexGenerator, diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java index 5da127de2ba..8f5d1a0208e 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java @@ -237,7 +237,6 @@ private static TransitLayer getTransitLayer() { null, null, null, - null, null ); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayerTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayerTest.java index 7c674252e6a..58a56ccb96f 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayerTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/TransitLayerTest.java @@ -65,7 +65,6 @@ void testGetTripPatternsRunningOnDateCopy() { null, null, null, - null, null ); var runningOnDate = transitLayer.getTripPatternsRunningOnDateCopy(date); @@ -95,7 +94,6 @@ void testGetTripPatternsForRunningDate() { null, null, null, - null, null ); var runningOnDate = transitLayer.getTripPatternsForRunningDate(date); @@ -124,7 +122,6 @@ void testGetTripPatternsOnServiceDateCopyWithSameRunningAndServiceDate() { null, null, null, - null, null ); var startingOnDate = transitLayer.getTripPatternsOnServiceDateCopy(date); @@ -153,7 +150,6 @@ void testGetTripPatternsOnServiceDateCopyWithServiceRunningAfterMidnight() { null, null, null, - null, null ); var startingOnDate = transitLayer.getTripPatternsOnServiceDateCopy(serviceDate); @@ -187,7 +183,6 @@ void testGetTripPatternsOnServiceDateCopyWithServiceRunningBeforeAndAfterMidnigh null, null, null, - null, null ); var startingOnDate = transitLayer.getTripPatternsOnServiceDateCopy(firstRunningDate); From 46133837081419414c729ee2902f037479923e5d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 14:29:24 +0200 Subject: [PATCH 092/148] First implementation of SIRI-FM updater --- .../bikeep/BikeepUpdaterParameters.java | 7 ++ .../bikely/BikelyUpdaterParameters.java | 7 ++ .../hslpark/HslParkUpdaterParameters.java | 7 ++ .../noi/NoiUpdaterParameters.java | 7 ++ .../parkapi/ParkAPIUpdaterParameters.java | 9 +- .../vehicleparking/sirifm/SiriFmUpdater.java | 37 ++++++ .../sirifm/SiriFmUpdaterParameters.java | 34 ++++++ .../vehicle_parking/VehicleParking.java | 33 +++-- .../updaters/VehicleParkingUpdaterConfig.java | 12 ++ .../configure/UpdaterConfigurator.java | 37 ++++-- .../AvailabilityDatasourceFactory.java | 23 ++++ .../vehicle_parking/AvailabiltyUpdate.java | 12 ++ .../VehicleParkingAvailabilityUpdater.java | 114 ++++++++++++++++++ .../VehicleParkingDataSourceFactory.java | 1 + .../VehicleParkingSourceType.java | 1 + .../VehicleParkingUpdaterParameters.java | 6 + .../VehicleParkingUpdaterTest.java | 5 + 17 files changed, 334 insertions(+), 18 deletions(-) create mode 100644 src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java create mode 100644 src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java create mode 100644 src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java create mode 100644 src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java create mode 100644 src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java index be937ecdd5e..86e03731dd8 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.bikeep; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.net.URI; import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; @@ -22,4 +24,9 @@ public record BikeepUpdaterParameters( public VehicleParkingSourceType sourceType() { return VehicleParkingSourceType.BIKEEP; } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java index 26e40f4ec4a..73f26a43aa4 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikely/BikelyUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.bikely; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.net.URI; import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; @@ -22,4 +24,9 @@ public record BikelyUpdaterParameters( public VehicleParkingSourceType sourceType() { return VehicleParkingSourceType.BIKELY; } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java index 4b75d38ea6f..b4ad24ae080 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/hslpark/HslParkUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.hslpark; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.time.Duration; import java.time.ZoneId; import org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType; @@ -25,4 +27,9 @@ public record HslParkUpdaterParameters( public Duration frequency() { return Duration.ofSeconds(utilizationsFrequencySec); } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java index 371ffdc9f33..b34769c9dd2 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/noi/NoiUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.noi; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.net.URI; import java.time.Duration; import org.opentripplanner.updater.spi.HttpHeaders; @@ -22,4 +24,9 @@ public record NoiUpdaterParameters( public VehicleParkingSourceType sourceType() { return VehicleParkingSourceType.NOI_OPEN_DATA_HUB; } + + @Override + public UpdateType updateType() { + return FULL; + } } diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java index 263987c60d0..3014d932e08 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdaterParameters.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vehicleparking.parkapi; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.FULL; + import java.time.Duration; import java.time.ZoneId; import java.util.List; @@ -21,4 +23,9 @@ public record ParkAPIUpdaterParameters( VehicleParkingSourceType sourceType, ZoneId timeZone ) - implements VehicleParkingUpdaterParameters {} + implements VehicleParkingUpdaterParameters { + @Override + public UpdateType updateType() { + return FULL; + } +} diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java new file mode 100644 index 00000000000..a30249b2b92 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -0,0 +1,37 @@ +package org.opentripplanner.ext.vehicleparking.sirifm; + +import java.util.List; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.updater.spi.DataSource; +import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SiriFmUpdater implements DataSource { + + private static final Logger LOG = LoggerFactory.getLogger(SiriFmUpdater.class); + private final SiriFmUpdaterParameters params; + + public SiriFmUpdater(SiriFmUpdaterParameters params) { + this.params = params; + } + + @Override + public String toString() { + return ToStringBuilder + .of(this.getClass()) + .addStr("url", this.params.url().toString()) + .toString(); + } + + @Override + public boolean update() { + LOG.error("RUNNING {}", this); + return true; + } + + @Override + public List getUpdates() { + return List.of(); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java new file mode 100644 index 00000000000..ef4015029ff --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java @@ -0,0 +1,34 @@ +package org.opentripplanner.ext.vehicleparking.sirifm; + +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType.SIRI_FM; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.AVAILABILITY; + +import java.net.URI; +import java.time.Duration; +import org.opentripplanner.ext.vehicleparking.noi.NoiUpdater; +import org.opentripplanner.updater.spi.HttpHeaders; +import org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType; +import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters; + +/** + * Class that extends {@link VehicleParkingUpdaterParameters} with parameters required by {@link + * NoiUpdater}. + */ +public record SiriFmUpdaterParameters( + String configRef, + URI url, + String feedId, + Duration frequency, + HttpHeaders httpHeaders +) + implements VehicleParkingUpdaterParameters { + @Override + public VehicleParkingSourceType sourceType() { + return SIRI_FM; + } + + @Override + public UpdateType updateType() { + return AVAILABILITY; + } +} diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index d5dfc4ce8c1..69f7f08eaa5 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -239,23 +239,40 @@ public boolean hasRealTimeDataForMode( return false; } - switch (traverseMode) { - case BICYCLE: - return availability.getBicycleSpaces() != null; - case CAR: + return switch (traverseMode) { + case BICYCLE -> availability.getBicycleSpaces() != null; + case CAR -> { var places = wheelchairAccessibleCarPlaces ? availability.getWheelchairAccessibleCarSpaces() : availability.getCarSpaces(); - return places != null; - default: - return false; - } + yield places != null; + } + default -> false; + }; } + /** + * The only mutable method in this class: it allows to update the available parking spaces during + * real-time updates. + */ public void updateAvailability(VehicleParkingSpaces vehicleParkingSpaces) { this.availability = vehicleParkingSpaces; } + public void close() { + var builder = VehicleParkingSpaces.builder(); + if (hasCarPlaces()) { + builder.carSpaces(0); + } + if (hasWheelchairAccessibleCarPlaces()) { + builder.wheelchairAccessibleCarSpaces(0); + } + if (hasBicyclePlaces()) { + builder.bicycleSpaces(0); + } + updateAvailability(builder.build()); + } + @Override public int hashCode() { return Objects.hash( diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java index c687899009f..6ef8c465bc3 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java @@ -13,6 +13,7 @@ import org.opentripplanner.ext.vehicleparking.hslpark.HslParkUpdaterParameters; import org.opentripplanner.ext.vehicleparking.noi.NoiUpdaterParameters; import org.opentripplanner.ext.vehicleparking.parkapi.ParkAPIUpdaterParameters; +import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdaterParameters; import org.opentripplanner.standalone.config.framework.json.NodeAdapter; import org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType; import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters; @@ -100,6 +101,17 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap .asDuration(Duration.ofMinutes(1)), HttpHeadersConfig.headers(c, V2_6) ); + case SIRI_FM -> new SiriFmUpdaterParameters( + updaterRef, + c.of("url").since(V2_6).summary("URL of the SIRI-FM Light endpoint.").asUri(), + feedId, + c + .of("frequency") + .since(V2_6) + .summary("How often to update the source.") + .asDuration(Duration.ofMinutes(1)), + HttpHeadersConfig.headers(c, V2_6) + ); }; } diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index 103120b7ecb..4726ab8b4ff 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -25,6 +25,8 @@ import org.opentripplanner.updater.trip.MqttGtfsRealtimeUpdater; import org.opentripplanner.updater.trip.PollingTripUpdater; import org.opentripplanner.updater.trip.TimetableSnapshotSource; +import org.opentripplanner.updater.vehicle_parking.AvailabilityDatasourceFactory; +import org.opentripplanner.updater.vehicle_parking.VehicleParkingAvailabilityUpdater; import org.opentripplanner.updater.vehicle_parking.VehicleParkingDataSourceFactory; import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdater; import org.opentripplanner.updater.vehicle_position.PollingVehiclePositionUpdater; @@ -187,15 +189,32 @@ private List createUpdatersFromConfig() { ); } for (var configItem : updatersParameters.getVehicleParkingUpdaterParameters()) { - var source = VehicleParkingDataSourceFactory.create(configItem, openingHoursCalendarService); - updaters.add( - new VehicleParkingUpdater( - configItem, - source, - graph.getLinker(), - graph.getVehicleParkingService() - ) - ); + switch (configItem.updateType()) { + case AVAILABILITY -> { + var source = VehicleParkingDataSourceFactory.create( + configItem, + openingHoursCalendarService + ); + updaters.add( + new VehicleParkingUpdater( + configItem, + source, + graph.getLinker(), + graph.getVehicleParkingService() + ) + ); + } + case FULL -> { + var source = AvailabilityDatasourceFactory.create(configItem); + updaters.add( + new VehicleParkingAvailabilityUpdater( + configItem, + source, + graph.getVehicleParkingService() + ) + ); + } + } } for (var configItem : updatersParameters.getSiriAzureETUpdaterParameters()) { updaters.add( diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java new file mode 100644 index 00000000000..cb62332747b --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java @@ -0,0 +1,23 @@ +package org.opentripplanner.updater.vehicle_parking; + +import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdater; +import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdaterParameters; +import org.opentripplanner.updater.spi.DataSource; + +/** + * Class that can be used to return a custom vehicle parking {@link DataSource}. + */ +public class AvailabilityDatasourceFactory { + + public static DataSource create(VehicleParkingUpdaterParameters parameters) { + return switch (parameters.sourceType()) { + case SIRI_FM -> new SiriFmUpdater((SiriFmUpdaterParameters) parameters); + case PARK_API, + BICYCLE_PARK_API, + HSL_PARK, + BIKEEP, + NOI_OPEN_DATA_HUB, + BIKELY -> throw new IllegalArgumentException("Cannot instantiate SIRI-FM data source"); + }; + } +} diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java new file mode 100644 index 00000000000..404fcb9f47b --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java @@ -0,0 +1,12 @@ +package org.opentripplanner.updater.vehicle_parking; + +import org.opentripplanner.transit.model.framework.FeedScopedId; + +public sealed interface AvailabiltyUpdate { + FeedScopedId vehicleParkingId(); + + record AvailabilityUpdated(FeedScopedId vehicleParkingId, int spacesAvailable) + implements AvailabiltyUpdate {} + + record ParkingClosed(FeedScopedId vehicleParkingId) implements AvailabiltyUpdate {} +} diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java new file mode 100644 index 00000000000..cf890213d76 --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -0,0 +1,114 @@ +package org.opentripplanner.updater.vehicle_parking; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.updater.GraphWriterRunnable; +import org.opentripplanner.updater.spi.DataSource; +import org.opentripplanner.updater.spi.PollingGraphUpdater; +import org.opentripplanner.updater.spi.WriteToGraphCallback; +import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate.AvailabilityUpdated; +import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate.ParkingClosed; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Graph updater that dynamically sets availability information on vehicle parking lots. This + * updater fetches data from a single {@link DataSource}. + */ +public class VehicleParkingAvailabilityUpdater extends PollingGraphUpdater { + + private static final Logger LOG = LoggerFactory.getLogger( + VehicleParkingAvailabilityUpdater.class + ); + private final DataSource source; + private WriteToGraphCallback saveResultOnGraph; + + private final VehicleParkingService vehicleParkingService; + + public VehicleParkingAvailabilityUpdater( + VehicleParkingUpdaterParameters parameters, + DataSource source, + VehicleParkingService vehicleParkingService + ) { + super(parameters); + this.source = source; + this.vehicleParkingService = vehicleParkingService; + + LOG.info("Creating vehicle-parking updater running every {}: {}", pollingPeriod(), source); + } + + @Override + public void setup(WriteToGraphCallback writeToGraphCallback) { + this.saveResultOnGraph = writeToGraphCallback; + } + + @Override + protected void runPolling() { + LOG.debug("Updating parking availability from {}", source); + if (!source.update()) { + LOG.debug("No updates"); + } else { + var updates = source.getUpdates(); + + var graphWriterRunnable = new VehicleParkingGraphWriterRunnable(updates); + saveResultOnGraph.execute(graphWriterRunnable); + } + } + + private class VehicleParkingGraphWriterRunnable implements GraphWriterRunnable { + + private final List updates; + private final Map parkingById; + + private VehicleParkingGraphWriterRunnable(List updates) { + this.updates = List.copyOf(updates); + this.parkingById = + vehicleParkingService + .getVehicleParkings() + .collect(Collectors.toUnmodifiableMap(VehicleParking::getId, Function.identity())); + } + + @Override + public void run(Graph graph, TransitModel ignored) { + updates.forEach(this::handleUpdate); + } + + private void handleUpdate(AvailabiltyUpdate update) { + if (!parkingById.containsKey(update.vehicleParkingId())) { + LOG.error( + "Parking with id {} does not exist. Skipping availability update.", + update.vehicleParkingId() + ); + } + var parking = parkingById.get(update.vehicleParkingId()); + + switch (update) { + case ParkingClosed closed -> parking.close(); + case AvailabilityUpdated availabilityUpdated -> { + var builder = VehicleParkingSpaces.builder(); + if (parking.hasCarPlaces()) { + builder.carSpaces(availabilityUpdated.spacesAvailable()); + } + if (parking.hasBicyclePlaces()) { + builder.bicycleSpaces(availabilityUpdated.spacesAvailable()); + } + parking.updateAvailability(builder.build()); + } + } + } + } + + @Override + public String toString() { + return ToStringBuilder.of(this.getClass()).addObj("source", source).toString(); + } +} diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java index a956fda0d87..09ecb67a54d 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java @@ -42,6 +42,7 @@ public static DataSource create( case BIKELY -> new BikelyUpdater((BikelyUpdaterParameters) parameters); case NOI_OPEN_DATA_HUB -> new NoiUpdater((NoiUpdaterParameters) parameters); case BIKEEP -> new BikeepUpdater((BikeepUpdaterParameters) parameters); + case SIRI_FM -> throw new IllegalArgumentException("Cannot instantiate SIRI-FM data source"); }; } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java index f6a28177d8e..1601245b16c 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java @@ -7,4 +7,5 @@ public enum VehicleParkingSourceType { BIKELY, NOI_OPEN_DATA_HUB, BIKEEP, + SIRI_FM, } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java index 3422ec3e300..500a0ff6bcc 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java @@ -8,4 +8,10 @@ */ public interface VehicleParkingUpdaterParameters extends PollingGraphUpdaterParameters { VehicleParkingSourceType sourceType(); + UpdateType updateType(); + + enum UpdateType { + FULL, + AVAILABILITY, + } } diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java index a31b5cfb387..f49299eb4ea 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterTest.java @@ -54,6 +54,11 @@ public VehicleParkingSourceType sourceType() { return null; } + @Override + public UpdateType updateType() { + return UpdateType.FULL; + } + @Override public Duration frequency() { return Duration.ZERO; From 67c97e939dc84c4efe7264819361422fa51051ac Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 14:48:01 +0200 Subject: [PATCH 093/148] Flesh out Siri importer --- .../vehicleparking/sirifm/SiriFmUpdater.java | 38 +++++++++++++++++-- .../configure/UpdaterConfigurator.java | 4 +- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java index a30249b2b92..366179d73d0 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -1,9 +1,14 @@ package org.opentripplanner.ext.vehicleparking.sirifm; import java.util.List; +import java.util.Map; +import org.opentripplanner.framework.io.OtpHttpClient; +import org.opentripplanner.framework.io.OtpHttpClientFactory; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.updater.spi.DataSource; +import org.opentripplanner.updater.spi.HttpHeaders; import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate; +import org.rutebanken.siri20.util.SiriXml; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,9 +16,14 @@ public class SiriFmUpdater implements DataSource { private static final Logger LOG = LoggerFactory.getLogger(SiriFmUpdater.class); private final SiriFmUpdaterParameters params; + private final OtpHttpClient httpClient; + private final Map headers; + private List updates = List.of(); - public SiriFmUpdater(SiriFmUpdaterParameters params) { - this.params = params; + public SiriFmUpdater(SiriFmUpdaterParameters parameters) { + params = parameters; + headers = HttpHeaders.of().acceptApplicationXML().add(parameters.httpHeaders()).build().asMap(); + httpClient = new OtpHttpClientFactory().create(LOG); } @Override @@ -27,11 +37,33 @@ public String toString() { @Override public boolean update() { LOG.error("RUNNING {}", this); + + updates = + httpClient.getAndMap( + params.url(), + headers, + resp -> { + var siri = SiriXml.parseXml(resp); + + var conditions = siri + .getServiceDelivery() + .getFacilityMonitoringDeliveries() + .stream() + .flatMap(d -> d.getFacilityConditions().stream()) + .toList(); + + conditions.forEach(c -> { + LOG.error("{}", c.getFacilityRef().getValue()); + }); + + return List.of(); + } + ); return true; } @Override public List getUpdates() { - return List.of(); + return updates; } } diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index 4726ab8b4ff..f95c485f8d2 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -190,7 +190,7 @@ private List createUpdatersFromConfig() { } for (var configItem : updatersParameters.getVehicleParkingUpdaterParameters()) { switch (configItem.updateType()) { - case AVAILABILITY -> { + case FULL -> { var source = VehicleParkingDataSourceFactory.create( configItem, openingHoursCalendarService @@ -204,7 +204,7 @@ private List createUpdatersFromConfig() { ) ); } - case FULL -> { + case AVAILABILITY -> { var source = AvailabilityDatasourceFactory.create(configItem); updaters.add( new VehicleParkingAvailabilityUpdater( From 84cd9be82570c618ef274103c8fc7327ef33a098 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 17:33:28 +0200 Subject: [PATCH 094/148] Implement Siri code --- .../vehicleparking/sirifm/SiriFmUpdater.java | 69 +++++++++++++++---- .../sirifm/SiriFmUpdaterParameters.java | 4 +- .../configure/UpdaterConfigurator.java | 2 +- .../VehicleParkingAvailabilityUpdater.java | 27 ++++---- .../VehicleParkingUpdaterParameters.java | 2 +- 5 files changed, 75 insertions(+), 29 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java index 366179d73d0..ada96edff58 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -1,16 +1,26 @@ package org.opentripplanner.ext.vehicleparking.sirifm; +import static uk.org.siri.siri21.CountingTypeEnumeration.PRESENT_COUNT; + +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import java.io.InputStream; import java.util.List; import java.util.Map; +import java.util.stream.Stream; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; import org.opentripplanner.framework.io.OtpHttpClient; import org.opentripplanner.framework.io.OtpHttpClientFactory; import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.updater.spi.DataSource; import org.opentripplanner.updater.spi.HttpHeaders; import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate; -import org.rutebanken.siri20.util.SiriXml; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import uk.org.siri.siri21.FacilityConditionStructure; +import uk.org.siri.siri21.Siri; public class SiriFmUpdater implements DataSource { @@ -20,6 +30,16 @@ public class SiriFmUpdater implements DataSource { private final Map headers; private List updates = List.of(); + private static final JAXBContext jaxbContext; + + static { + try { + jaxbContext = JAXBContext.newInstance(Siri.class); + } catch (JAXBException e) { + throw new RuntimeException(e); + } + } + public SiriFmUpdater(SiriFmUpdaterParameters parameters) { params = parameters; headers = HttpHeaders.of().acceptApplicationXML().add(parameters.httpHeaders()).build().asMap(); @@ -43,25 +63,50 @@ public boolean update() { params.url(), headers, resp -> { - var siri = SiriXml.parseXml(resp); + var siri = parseXml(resp); - var conditions = siri - .getServiceDelivery() - .getFacilityMonitoringDeliveries() - .stream() + return Stream + .ofNullable(siri.getServiceDelivery()) + .flatMap(sd -> sd.getFacilityMonitoringDeliveries().stream()) .flatMap(d -> d.getFacilityConditions().stream()) + .filter(this::conformsToItalianProfile) + .map(this::mapToUpdate) .toList(); - - conditions.forEach(c -> { - LOG.error("{}", c.getFacilityRef().getValue()); - }); - - return List.of(); } ); return true; } + private AvailabiltyUpdate mapToUpdate(FacilityConditionStructure c) { + var id = new FeedScopedId(params.feedId(), c.getFacilityRef().getValue()); + var available = c.getMonitoredCountings().getFirst().getCount().intValue(); + return new AvailabiltyUpdate.AvailabilityUpdated(id, available); + } + + /** + * Checks if the {@link FacilityConditionStructure} contains all the necessary information that + * are required by the Italian Siri-FM profile. + */ + private boolean conformsToItalianProfile(FacilityConditionStructure c) { + return ( + c.getFacilityRef() != null && + c.getFacilityRef().getValue() != null && + c.getMonitoredCountings().size() == 1 && + c.getMonitoredCountings().stream().anyMatch(mc -> mc.getCountingType() == PRESENT_COUNT) + ); + } + + private Siri parseXml(InputStream stream) { + try { + var xmlif = XMLInputFactory.newInstance(); + var jaxbUnmarshaller = jaxbContext.createUnmarshaller(); + var streamReader = xmlif.createXMLStreamReader(stream); + return (Siri) jaxbUnmarshaller.unmarshal(streamReader); + } catch (JAXBException | XMLStreamException e) { + throw new RuntimeException(e); + } + } + @Override public List getUpdates() { return updates; diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java index ef4015029ff..5afdffb7067 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterParameters.java @@ -1,7 +1,7 @@ package org.opentripplanner.ext.vehicleparking.sirifm; import static org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType.SIRI_FM; -import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.AVAILABILITY; +import static org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters.UpdateType.AVAILABILITY_ONLY; import java.net.URI; import java.time.Duration; @@ -29,6 +29,6 @@ public VehicleParkingSourceType sourceType() { @Override public UpdateType updateType() { - return AVAILABILITY; + return AVAILABILITY_ONLY; } } diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index f95c485f8d2..6662bae362b 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -204,7 +204,7 @@ private List createUpdatersFromConfig() { ) ); } - case AVAILABILITY -> { + case AVAILABILITY_ONLY -> { var source = AvailabilityDatasourceFactory.create(configItem); updaters.add( new VehicleParkingAvailabilityUpdater( diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java index cf890213d76..8d38890b16c 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -88,20 +88,21 @@ private void handleUpdate(AvailabiltyUpdate update) { "Parking with id {} does not exist. Skipping availability update.", update.vehicleParkingId() ); - } - var parking = parkingById.get(update.vehicleParkingId()); - - switch (update) { - case ParkingClosed closed -> parking.close(); - case AvailabilityUpdated availabilityUpdated -> { - var builder = VehicleParkingSpaces.builder(); - if (parking.hasCarPlaces()) { - builder.carSpaces(availabilityUpdated.spacesAvailable()); - } - if (parking.hasBicyclePlaces()) { - builder.bicycleSpaces(availabilityUpdated.spacesAvailable()); + } else { + var parking = parkingById.get(update.vehicleParkingId()); + + switch (update) { + case ParkingClosed closed -> parking.close(); + case AvailabilityUpdated availabilityUpdated -> { + var builder = VehicleParkingSpaces.builder(); + if (parking.hasCarPlaces()) { + builder.carSpaces(availabilityUpdated.spacesAvailable()); + } + if (parking.hasBicyclePlaces()) { + builder.bicycleSpaces(availabilityUpdated.spacesAvailable()); + } + parking.updateAvailability(builder.build()); } - parking.updateAvailability(builder.build()); } } } diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java index 500a0ff6bcc..bff0022383f 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingUpdaterParameters.java @@ -12,6 +12,6 @@ public interface VehicleParkingUpdaterParameters extends PollingGraphUpdaterPara enum UpdateType { FULL, - AVAILABILITY, + AVAILABILITY_ONLY, } } From e80ec16d686b554872206848e6c0fdc0b6272003 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 17:42:25 +0200 Subject: [PATCH 095/148] Make code more robust --- .../vehicleparking/sirifm/SiriFmUpdater.java | 2 +- .../vehicle_parking/AvailabiltyUpdate.java | 9 +----- .../VehicleParkingAvailabilityUpdater.java | 28 +++++++------------ 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java index ada96edff58..1413df407e1 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -80,7 +80,7 @@ public boolean update() { private AvailabiltyUpdate mapToUpdate(FacilityConditionStructure c) { var id = new FeedScopedId(params.feedId(), c.getFacilityRef().getValue()); var available = c.getMonitoredCountings().getFirst().getCount().intValue(); - return new AvailabiltyUpdate.AvailabilityUpdated(id, available); + return new AvailabiltyUpdate(id, available); } /** diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java index 404fcb9f47b..17d273ac237 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java @@ -2,11 +2,4 @@ import org.opentripplanner.transit.model.framework.FeedScopedId; -public sealed interface AvailabiltyUpdate { - FeedScopedId vehicleParkingId(); - - record AvailabilityUpdated(FeedScopedId vehicleParkingId, int spacesAvailable) - implements AvailabiltyUpdate {} - - record ParkingClosed(FeedScopedId vehicleParkingId) implements AvailabiltyUpdate {} -} +public record AvailabiltyUpdate(FeedScopedId vehicleParkingId, int spacesAvailable) {} diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java index 8d38890b16c..77790b1a084 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -15,8 +15,6 @@ import org.opentripplanner.updater.spi.DataSource; import org.opentripplanner.updater.spi.PollingGraphUpdater; import org.opentripplanner.updater.spi.WriteToGraphCallback; -import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate.AvailabilityUpdated; -import org.opentripplanner.updater.vehicle_parking.AvailabiltyUpdate.ParkingClosed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,17 +57,17 @@ protected void runPolling() { } else { var updates = source.getUpdates(); - var graphWriterRunnable = new VehicleParkingGraphWriterRunnable(updates); + var graphWriterRunnable = new UpdateAvailabilities(updates); saveResultOnGraph.execute(graphWriterRunnable); } } - private class VehicleParkingGraphWriterRunnable implements GraphWriterRunnable { + private class UpdateAvailabilities implements GraphWriterRunnable { private final List updates; private final Map parkingById; - private VehicleParkingGraphWriterRunnable(List updates) { + private UpdateAvailabilities(List updates) { this.updates = List.copyOf(updates); this.parkingById = vehicleParkingService @@ -90,20 +88,14 @@ private void handleUpdate(AvailabiltyUpdate update) { ); } else { var parking = parkingById.get(update.vehicleParkingId()); - - switch (update) { - case ParkingClosed closed -> parking.close(); - case AvailabilityUpdated availabilityUpdated -> { - var builder = VehicleParkingSpaces.builder(); - if (parking.hasCarPlaces()) { - builder.carSpaces(availabilityUpdated.spacesAvailable()); - } - if (parking.hasBicyclePlaces()) { - builder.bicycleSpaces(availabilityUpdated.spacesAvailable()); - } - parking.updateAvailability(builder.build()); - } + var builder = VehicleParkingSpaces.builder(); + if (parking.hasCarPlaces()) { + builder.carSpaces(update.spacesAvailable()); + } + if (parking.hasBicyclePlaces()) { + builder.bicycleSpaces(update.spacesAvailable()); } + parking.updateAvailability(builder.build()); } } } From ad8b74d8afc193ade38cc41fb3d1698e588e9300 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 18:07:22 +0200 Subject: [PATCH 096/148] Add test for Siri-FM parking updater --- .../sirifm/SiriFmUpdaterTest.java | 28 ++++++++++ .../ext/vehicleparking/sirifm/siri-fm.xml | 56 +++++++++++++++++++ .../vehicleparking/sirifm/SiriFmUpdater.java | 4 +- 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java create mode 100644 src/ext-test/resources/org/opentripplanner/ext/vehicleparking/sirifm/siri-fm.xml diff --git a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java new file mode 100644 index 00000000000..5078eedb427 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java @@ -0,0 +1,28 @@ +package org.opentripplanner.ext.vehicleparking.sirifm; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.opentripplanner.test.support.ResourceLoader; +import org.opentripplanner.updater.spi.HttpHeaders; + +class SiriFmUpdaterTest { + + @Test + void parse() { + var uri = ResourceLoader.of(this).uri("siri-fm.xml"); + var parameters = new SiriFmUpdaterParameters( + "noi", + uri, + "noi", + Duration.ofSeconds(30), + HttpHeaders.empty() + ); + var updater = new SiriFmUpdater(parameters); + updater.update(); + var updates = updater.getUpdates(); + + assertEquals(4, updates.size()); + } +} diff --git a/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/sirifm/siri-fm.xml b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/sirifm/siri-fm.xml new file mode 100644 index 00000000000..f4595488284 --- /dev/null +++ b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/sirifm/siri-fm.xml @@ -0,0 +1,56 @@ + + + 2024-07-17T11:07:40Z + RAP Alto Adige - Open Data Hub + + 2024-07-17T11:07:40Z + RAP Alto Adige - Open Data Hub + + IT:ITH10:Parking:105 + + available + + + presentCount + bays + 33 + + + + IT:ITH10:Parking:TRENTO_areaexsitviacanestrinip1 + + notAvailable + + + presentCount + bays + 300 + + + + IT:ITH10:Parking:TRENTO_autosilobuonconsigliop3 + + notAvailable + + + presentCount + bays + 633 + + + + IT:ITH10:Parking:TRENTO_cteviabomportop6 + + notAvailable + + + presentCount + bays + 250 + + + + + \ No newline at end of file diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java index 1413df407e1..d4ccefa1845 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -56,8 +56,6 @@ public String toString() { @Override public boolean update() { - LOG.error("RUNNING {}", this); - updates = httpClient.getAndMap( params.url(), @@ -92,7 +90,7 @@ private boolean conformsToItalianProfile(FacilityConditionStructure c) { c.getFacilityRef() != null && c.getFacilityRef().getValue() != null && c.getMonitoredCountings().size() == 1 && - c.getMonitoredCountings().stream().anyMatch(mc -> mc.getCountingType() == PRESENT_COUNT) + c.getMonitoredCountings().getFirst().getCountingType() == PRESENT_COUNT ); } From c228692c9518ec2d4ea4f2b1bb761393c813fd90 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 17 Jul 2024 22:04:24 +0200 Subject: [PATCH 097/148] Simplify Siri updater --- .../vehicleparking/sirifm/SiriFmUpdater.java | 33 ++++--------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java index d4ccefa1845..96e8e455185 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java @@ -2,14 +2,10 @@ import static uk.org.siri.siri21.CountingTypeEnumeration.PRESENT_COUNT; -import jakarta.xml.bind.JAXBContext; -import jakarta.xml.bind.JAXBException; -import java.io.InputStream; import java.util.List; import java.util.Map; import java.util.stream.Stream; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamException; +import org.entur.siri21.util.SiriXml; import org.opentripplanner.framework.io.OtpHttpClient; import org.opentripplanner.framework.io.OtpHttpClientFactory; import org.opentripplanner.framework.tostring.ToStringBuilder; @@ -20,8 +16,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.org.siri.siri21.FacilityConditionStructure; -import uk.org.siri.siri21.Siri; +/** + * Parses SIRI 2.1 XML data into parking availability updates. The data needs to conform to the + * Italian profile of SIRI-FM. + */ public class SiriFmUpdater implements DataSource { private static final Logger LOG = LoggerFactory.getLogger(SiriFmUpdater.class); @@ -30,16 +29,6 @@ public class SiriFmUpdater implements DataSource { private final Map headers; private List updates = List.of(); - private static final JAXBContext jaxbContext; - - static { - try { - jaxbContext = JAXBContext.newInstance(Siri.class); - } catch (JAXBException e) { - throw new RuntimeException(e); - } - } - public SiriFmUpdater(SiriFmUpdaterParameters parameters) { params = parameters; headers = HttpHeaders.of().acceptApplicationXML().add(parameters.httpHeaders()).build().asMap(); @@ -61,7 +50,7 @@ public boolean update() { params.url(), headers, resp -> { - var siri = parseXml(resp); + var siri = SiriXml.parseXml(resp); return Stream .ofNullable(siri.getServiceDelivery()) @@ -94,16 +83,6 @@ private boolean conformsToItalianProfile(FacilityConditionStructure c) { ); } - private Siri parseXml(InputStream stream) { - try { - var xmlif = XMLInputFactory.newInstance(); - var jaxbUnmarshaller = jaxbContext.createUnmarshaller(); - var streamReader = xmlif.createXMLStreamReader(stream); - return (Siri) jaxbUnmarshaller.unmarshal(streamReader); - } catch (JAXBException | XMLStreamException e) { - throw new RuntimeException(e); - } - } @Override public List getUpdates() { From 64f6fc62f0fc45f462ee0af122b8713e33ce3b39 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 19 Jul 2024 13:15:48 +0200 Subject: [PATCH 098/148] Add test for SIRI parking updater --- docs/sandbox/VehicleParking.md | 10 +- .../sirifm/SiriFmUpdaterTest.java | 2 +- ...riFmUpdater.java => SiriFmDatasource.java} | 7 +- .../vehicle_parking/VehicleParking.java | 14 --- .../AvailabilityDatasourceFactory.java | 4 +- .../vehicle_parking/AvailabiltyUpdate.java | 9 +- ...VehicleParkingAvailabilityUpdaterTest.java | 108 ++++++++++++++++++ 7 files changed, 127 insertions(+), 27 deletions(-) rename src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/{SiriFmUpdater.java => SiriFmDatasource.java} (94%) create mode 100644 src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index a5adde1d4c2..ae83dba2b41 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -61,7 +61,7 @@ This will end up in the API responses as the feed id of of the parking lot. **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[2] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -131,7 +131,7 @@ This will end up in the API responses as the feed id of of the parking lot. **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[3] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -216,7 +216,7 @@ This will end up in the API responses as the feed id of of the parking lot. **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[4] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -281,7 +281,7 @@ This will end up in the API responses as the feed id of of the parking lot. **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[5] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. @@ -342,7 +342,7 @@ This will end up in the API responses as the feed id of of the parking lot. **Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` **Path:** /updaters/[14] -**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` The source of the vehicle updates. diff --git a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java index 5078eedb427..07502b597d9 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdaterTest.java @@ -19,7 +19,7 @@ void parse() { Duration.ofSeconds(30), HttpHeaders.empty() ); - var updater = new SiriFmUpdater(parameters); + var updater = new SiriFmDatasource(parameters); updater.update(); var updates = updater.getUpdates(); diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmDatasource.java similarity index 94% rename from src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java rename to src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmDatasource.java index 96e8e455185..abfdf4d29be 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/sirifm/SiriFmDatasource.java @@ -21,15 +21,15 @@ * Parses SIRI 2.1 XML data into parking availability updates. The data needs to conform to the * Italian profile of SIRI-FM. */ -public class SiriFmUpdater implements DataSource { +public class SiriFmDatasource implements DataSource { - private static final Logger LOG = LoggerFactory.getLogger(SiriFmUpdater.class); + private static final Logger LOG = LoggerFactory.getLogger(SiriFmDatasource.class); private final SiriFmUpdaterParameters params; private final OtpHttpClient httpClient; private final Map headers; private List updates = List.of(); - public SiriFmUpdater(SiriFmUpdaterParameters parameters) { + public SiriFmDatasource(SiriFmUpdaterParameters parameters) { params = parameters; headers = HttpHeaders.of().acceptApplicationXML().add(parameters.httpHeaders()).build().asMap(); httpClient = new OtpHttpClientFactory().create(LOG); @@ -83,7 +83,6 @@ private boolean conformsToItalianProfile(FacilityConditionStructure c) { ); } - @Override public List getUpdates() { return updates; diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index 69f7f08eaa5..b50c583c79a 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -259,20 +259,6 @@ public void updateAvailability(VehicleParkingSpaces vehicleParkingSpaces) { this.availability = vehicleParkingSpaces; } - public void close() { - var builder = VehicleParkingSpaces.builder(); - if (hasCarPlaces()) { - builder.carSpaces(0); - } - if (hasWheelchairAccessibleCarPlaces()) { - builder.wheelchairAccessibleCarSpaces(0); - } - if (hasBicyclePlaces()) { - builder.bicycleSpaces(0); - } - updateAvailability(builder.build()); - } - @Override public int hashCode() { return Objects.hash( diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java index cb62332747b..876cd303c78 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabilityDatasourceFactory.java @@ -1,6 +1,6 @@ package org.opentripplanner.updater.vehicle_parking; -import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdater; +import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmDatasource; import org.opentripplanner.ext.vehicleparking.sirifm.SiriFmUpdaterParameters; import org.opentripplanner.updater.spi.DataSource; @@ -11,7 +11,7 @@ public class AvailabilityDatasourceFactory { public static DataSource create(VehicleParkingUpdaterParameters parameters) { return switch (parameters.sourceType()) { - case SIRI_FM -> new SiriFmUpdater((SiriFmUpdaterParameters) parameters); + case SIRI_FM -> new SiriFmDatasource((SiriFmUpdaterParameters) parameters); case PARK_API, BICYCLE_PARK_API, HSL_PARK, diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java index 17d273ac237..a9d352cbaf2 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/AvailabiltyUpdate.java @@ -1,5 +1,12 @@ package org.opentripplanner.updater.vehicle_parking; +import java.util.Objects; +import org.opentripplanner.framework.lang.IntUtils; import org.opentripplanner.transit.model.framework.FeedScopedId; -public record AvailabiltyUpdate(FeedScopedId vehicleParkingId, int spacesAvailable) {} +public record AvailabiltyUpdate(FeedScopedId vehicleParkingId, int spacesAvailable) { + public AvailabiltyUpdate { + Objects.requireNonNull(vehicleParkingId); + IntUtils.requireNotNegative(spacesAvailable); + } +} diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java new file mode 100644 index 00000000000..60cfc3faa1c --- /dev/null +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -0,0 +1,108 @@ +package org.opentripplanner.updater.vehicle_parking; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.transit.model._data.TransitModelForTest.id; + +import com.google.common.util.concurrent.Futures; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.Future; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.framework.i18n.I18NString; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.vehicle_parking.VehicleParking; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; +import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.updater.GraphUpdaterManager; +import org.opentripplanner.updater.GraphWriterRunnable; +import org.opentripplanner.updater.spi.DataSource; +import org.opentripplanner.updater.spi.GraphUpdater; + +class VehicleParkingAvailabilityUpdaterTest { + + private static final VehicleParkingUpdaterParameters PARAMETERS = new VehicleParkingUpdaterParameters() { + @Override + public VehicleParkingSourceType sourceType() { + return VehicleParkingSourceType.SIRI_FM; + } + + @Override + public UpdateType updateType() { + return UpdateType.AVAILABILITY_ONLY; + } + + @Override + public Duration frequency() { + return Duration.ZERO; + } + + @Override + public String configRef() { + return null; + } + }; + private static final FeedScopedId ID = id("parking1"); + + @Test + void updateAvailability() { + var service = new VehicleParkingService(); + + var parking = VehicleParking + .builder() + .id(ID) + .name(I18NString.of("parking")) + .coordinate(WgsCoordinate.GREENWICH) + .carPlaces(true) + .capacity(VehicleParkingSpaces.builder().carSpaces(10).build()) + .build(); + service.updateVehicleParking(List.of(parking), List.of()); + + var updater = new VehicleParkingAvailabilityUpdater(PARAMETERS, new StubDatasource(), service); + + runUpdaterOnce(updater); + + var updated = service.getVehicleParkings().toList().getFirst(); + assertEquals(ID, updated.getId()); + assertEquals(8, updated.getAvailability().getCarSpaces()); + assertNull(updated.getAvailability().getBicycleSpaces()); + } + + private void runUpdaterOnce(VehicleParkingAvailabilityUpdater updater) { + class GraphUpdaterMock extends GraphUpdaterManager { + + private static final Graph GRAPH = new Graph(); + private static final TransitModel TRANSIT_MODEL = new TransitModel(); + + public GraphUpdaterMock(List updaters) { + super(GRAPH, TRANSIT_MODEL, updaters); + } + + @Override + public Future execute(GraphWriterRunnable runnable) { + runnable.run(GRAPH, TRANSIT_MODEL); + return Futures.immediateVoidFuture(); + } + } + + var graphUpdaterManager = new GraphUpdaterMock(List.of(updater)); + graphUpdaterManager.startUpdaters(); + graphUpdaterManager.stop(false); + } + + private static class StubDatasource implements DataSource { + + @Override + public boolean update() { + return true; + } + + @Override + public List getUpdates() { + return List.of(new AvailabiltyUpdate(ID, 8)); + } + } +} From cbddcc4ca8a012e94dde7ed530acd07d8274f32b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 19 Jul 2024 15:01:01 +0200 Subject: [PATCH 099/148] Improve test --- .../VehicleParkingAvailabilityUpdater.java | 5 +- ...VehicleParkingAvailabilityUpdaterTest.java | 82 +++++++++++++++---- 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java index 77790b1a084..86e7b72c9ff 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -51,10 +51,7 @@ public void setup(WriteToGraphCallback writeToGraphCallback) { @Override protected void runPolling() { - LOG.debug("Updating parking availability from {}", source); - if (!source.update()) { - LOG.debug("No updates"); - } else { + if (source.update()) { var updates = source.getUpdates(); var graphWriterRunnable = new UpdateAvailabilities(updates); diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java index 60cfc3faa1c..713b6d01664 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -46,22 +46,16 @@ public String configRef() { } }; private static final FeedScopedId ID = id("parking1"); + private static final AvailabiltyUpdate DEFAULT_UPDATE = new AvailabiltyUpdate(ID, 8); @Test - void updateAvailability() { - var service = new VehicleParkingService(); - - var parking = VehicleParking - .builder() - .id(ID) - .name(I18NString.of("parking")) - .coordinate(WgsCoordinate.GREENWICH) - .carPlaces(true) - .capacity(VehicleParkingSpaces.builder().carSpaces(10).build()) - .build(); - service.updateVehicleParking(List.of(parking), List.of()); - - var updater = new VehicleParkingAvailabilityUpdater(PARAMETERS, new StubDatasource(), service); + void updateCarAvailability() { + var service = buildParkingService(VehicleParkingSpaces.builder().carSpaces(10).build()); + var updater = new VehicleParkingAvailabilityUpdater( + PARAMETERS, + new StubDatasource(DEFAULT_UPDATE), + service + ); runUpdaterOnce(updater); @@ -71,6 +65,58 @@ void updateAvailability() { assertNull(updated.getAvailability().getBicycleSpaces()); } + @Test + void updateBicycleAvailability() { + var service = buildParkingService(VehicleParkingSpaces.builder().bicycleSpaces(15).build()); + var updater = new VehicleParkingAvailabilityUpdater( + PARAMETERS, + new StubDatasource(DEFAULT_UPDATE), + service + ); + + runUpdaterOnce(updater); + + var updated = service.getVehicleParkings().toList().getFirst(); + assertEquals(ID, updated.getId()); + assertEquals(8, updated.getAvailability().getBicycleSpaces()); + assertNull(updated.getAvailability().getCarSpaces()); + } + @Test + void notFound() { + var service = buildParkingService(VehicleParkingSpaces.builder().bicycleSpaces(15).build()); + var updater = new VehicleParkingAvailabilityUpdater( + PARAMETERS, + new StubDatasource(new AvailabiltyUpdate(id("not-found"), 100)), + service + ); + + runUpdaterOnce(updater); + + var updated = service.getVehicleParkings().toList().getFirst(); + assertEquals(ID, updated.getId()); + assertNull(updated.getAvailability()); + } + + private static VehicleParkingService buildParkingService(VehicleParkingSpaces capacity) { + var service = new VehicleParkingService(); + + var parking = parkingBuilder() + .carPlaces(capacity.getCarSpaces() != null) + .bicyclePlaces(capacity.getBicycleSpaces() != null) + .capacity(capacity) + .build(); + service.updateVehicleParking(List.of(parking), List.of()); + return service; + } + + private static VehicleParking.VehicleParkingBuilder parkingBuilder() { + return VehicleParking + .builder() + .id(ID) + .name(I18NString.of("parking")) + .coordinate(WgsCoordinate.GREENWICH); + } + private void runUpdaterOnce(VehicleParkingAvailabilityUpdater updater) { class GraphUpdaterMock extends GraphUpdaterManager { @@ -95,6 +141,12 @@ public Future execute(GraphWriterRunnable runnable) { private static class StubDatasource implements DataSource { + private final AvailabiltyUpdate update; + + private StubDatasource(AvailabiltyUpdate update) { + this.update = update; + } + @Override public boolean update() { return true; @@ -102,7 +154,7 @@ public boolean update() { @Override public List getUpdates() { - return List.of(new AvailabiltyUpdate(ID, 8)); + return List.of(update); } } } From b70dc7473fcb9c3d44a1fca6d5d42b381b5f7bd2 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 19 Jul 2024 15:10:54 +0200 Subject: [PATCH 100/148] Add documentation --- doc-templates/VehicleParking.md | 9 ++ docs/RouterConfiguration.md | 6 ++ docs/sandbox/VehicleParking.md | 89 +++++++++++++++++-- .../updaters/VehicleParkingUpdaterConfig.java | 16 +++- ...VehicleParkingAvailabilityUpdaterTest.java | 1 + .../standalone/config/router-config.json | 7 ++ 6 files changed, 121 insertions(+), 7 deletions(-) diff --git a/doc-templates/VehicleParking.md b/doc-templates/VehicleParking.md index 5d149e40f9a..507894b97e8 100644 --- a/doc-templates/VehicleParking.md +++ b/doc-templates/VehicleParking.md @@ -48,6 +48,15 @@ All updaters have the following parameters in common: +## SIRI-FM + +The SIRI-FM updaters works slighly differently from the other in that it only updates the availability +of parking but does not create new lots in realtime. + +The data source must conform to the [Italian SIRI-FM](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf) profile. + + + ## Changelog - Create initial sandbox implementation (January 2022, [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796)) diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md index a3042e7b91f..0f91875e542 100644 --- a/docs/RouterConfiguration.md +++ b/docs/RouterConfiguration.md @@ -875,6 +875,12 @@ Used to group requests when monitoring OTP. "feedId" : "bikeep", "sourceType" : "bikeep", "url" : "https://services.bikeep.com/location/v1/public-areas/no-baia-mobility/locations" + }, + { + "type" : "vehicle-parking", + "feedId" : "parking", + "sourceType" : "siri-fm", + "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" } ], "rideHailingServices" : [ diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index ae83dba2b41..06f520b0972 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -55,7 +55,7 @@ All updaters have the following parameters in common: The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

@@ -125,7 +125,7 @@ Used for converting abstract opening hours into concrete points in time. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

@@ -210,7 +210,7 @@ Tags to add to the parking lots. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

@@ -275,7 +275,7 @@ HTTP headers to add to the request. Any header key, value can be inserted. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

@@ -336,7 +336,7 @@ HTTP headers to add to the request. Any header key, value can be inserted. The id of the data source, which will be the prefix of the parking lot's id. -This will end up in the API responses as the feed id of of the parking lot. +This will end up in the API responses as the feed id of the parking lot.

sourceType

@@ -373,6 +373,85 @@ HTTP headers to add to the request. Any header key, value can be inserted. +## SIRI-FM + +The SIRI-FM updaters works slighly differently from the other in that it only updates the availability +of parking but does not create new lots in realtime. + +The data source must conform to the [Italian SIRI-FM](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf) profile. + + + + +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|----------------------------------|:---------------:|------------------------------------------------------------------------------|:----------:|---------------|:-----:| +| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 | +| [feedId](#u__15__feedId) | `string` | The id of the data source, which will be the prefix of the parking lot's id. | *Required* | | 2.2 | +| frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.6 | +| [sourceType](#u__15__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 | +| [url](#u__15__url) | `uri` | URL of the SIRI-FM Light endpoint. | *Required* | | 2.6 | +| [headers](#u__15__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.6 | + + +#### Details + +

feedId

+ +**Since version:** `2.2` ∙ **Type:** `string` ∙ **Cardinality:** `Required` +**Path:** /updaters/[15] + +The id of the data source, which will be the prefix of the parking lot's id. + +This will end up in the API responses as the feed id of the parking lot. + +

sourceType

+ +**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required` +**Path:** /updaters/[15] +**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep` | `siri-fm` + +The source of the vehicle updates. + +

url

+ +**Since version:** `2.6` ∙ **Type:** `uri` ∙ **Cardinality:** `Required` +**Path:** /updaters/[15] + +URL of the SIRI-FM Light endpoint. + +SIRI Light means that it must be available as a HTTP GET request rather than the usual +SIRI request mechanism of HTTP POST. + +The contents must also conform to the [Italian SIRI profile](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf). + + +

headers

+ +**Since version:** `2.6` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional` +**Path:** /updaters/[15] + +HTTP headers to add to the request. Any header key, value can be inserted. + + + +##### Example configuration + +```JSON +// router-config.json +{ + "updaters" : [ + { + "type" : "vehicle-parking", + "feedId" : "parking", + "sourceType" : "siri-fm", + "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" + } + ] +} +``` + + + ## Changelog - Create initial sandbox implementation (January 2022, [#3796](https://github.com/opentripplanner/OpenTripPlanner/pull/3796)) diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java index 6ef8c465bc3..60f794f2f2a 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java @@ -30,7 +30,7 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap .of("feedId") .since(V2_2) .summary("The id of the data source, which will be the prefix of the parking lot's id.") - .description("This will end up in the API responses as the feed id of of the parking lot.") + .description("This will end up in the API responses as the feed id of the parking lot.") .asString(); return switch (sourceType) { case HSL_PARK -> new HslParkUpdaterParameters( @@ -103,7 +103,19 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap ); case SIRI_FM -> new SiriFmUpdaterParameters( updaterRef, - c.of("url").since(V2_6).summary("URL of the SIRI-FM Light endpoint.").asUri(), + c + .of("url") + .since(V2_6) + .summary("URL of the SIRI-FM Light endpoint.") + .description( + """ + SIRI Light means that it must be available as a HTTP GET request rather than the usual + SIRI request mechanism of HTTP POST. + + The contents must also conform to the [Italian SIRI profile](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf). + """ + ) + .asUri(), feedId, c .of("frequency") diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java index 713b6d01664..0af58378f90 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -81,6 +81,7 @@ void updateBicycleAvailability() { assertEquals(8, updated.getAvailability().getBicycleSpaces()); assertNull(updated.getAvailability().getCarSpaces()); } + @Test void notFound() { var service = buildParkingService(VehicleParkingSpaces.builder().bicycleSpaces(15).build()); diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json index 3a3ef9b4cf0..c526de423c1 100644 --- a/src/test/resources/standalone/config/router-config.json +++ b/src/test/resources/standalone/config/router-config.json @@ -438,6 +438,13 @@ "feedId": "bikeep", "sourceType": "bikeep", "url": "https://services.bikeep.com/location/v1/public-areas/no-baia-mobility/locations" + }, + // SIRI-FM vehicle parking updater + { + "type": "vehicle-parking", + "feedId": "parking", + "sourceType": "siri-fm", + "url": "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" } ], "rideHailingServices": [ From 7363899c074dfc8bebb03caf32e166414169e073 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 2 Aug 2024 10:23:15 +0200 Subject: [PATCH 101/148] Mention SIRI 2.1 --- doc-templates/VehicleParking.md | 3 ++- docs/sandbox/VehicleParking.md | 6 ++++-- .../routerconfig/updaters/VehicleParkingUpdaterConfig.java | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/doc-templates/VehicleParking.md b/doc-templates/VehicleParking.md index 507894b97e8..9efb6b66522 100644 --- a/doc-templates/VehicleParking.md +++ b/doc-templates/VehicleParking.md @@ -53,7 +53,8 @@ All updaters have the following parameters in common: The SIRI-FM updaters works slighly differently from the other in that it only updates the availability of parking but does not create new lots in realtime. -The data source must conform to the [Italian SIRI-FM](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf) profile. +The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile +requires SIRI 2.1. diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index 06f520b0972..45a2dc28ea8 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -378,7 +378,8 @@ HTTP headers to add to the request. Any header key, value can be inserted. The SIRI-FM updaters works slighly differently from the other in that it only updates the availability of parking but does not create new lots in realtime. -The data source must conform to the [Italian SIRI-FM](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf) profile. +The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile +requires SIRI 2.1. @@ -422,7 +423,8 @@ URL of the SIRI-FM Light endpoint. SIRI Light means that it must be available as a HTTP GET request rather than the usual SIRI request mechanism of HTTP POST. -The contents must also conform to the [Italian SIRI profile](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf). +The contents must also conform to the [Italian SIRI profile](https://github.com/5Tsrl/siri-italian-profile) +which requires SIRI 2.1.

headers

diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java index 60f794f2f2a..88b47aac4eb 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java @@ -112,7 +112,8 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap SIRI Light means that it must be available as a HTTP GET request rather than the usual SIRI request mechanism of HTTP POST. - The contents must also conform to the [Italian SIRI profile](https://github.com/noi-techpark/sta-nap-export/files/15302688/240502_SpecificaSIRI_v.1.0.3.pdf). + The contents must also conform to the [Italian SIRI profile](https://github.com/5Tsrl/siri-italian-profile) + which requires SIRI 2.1. """ ) .asUri(), From 3a76485ba656f3c6e1547c75504c8a2be93894b4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 2 Aug 2024 20:49:32 +0200 Subject: [PATCH 102/148] Update doc-templates/VehicleParking.md Co-authored-by: Joel Lappalainen --- doc-templates/VehicleParking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc-templates/VehicleParking.md b/doc-templates/VehicleParking.md index 9efb6b66522..a8a46d7875d 100644 --- a/doc-templates/VehicleParking.md +++ b/doc-templates/VehicleParking.md @@ -50,7 +50,7 @@ All updaters have the following parameters in common: ## SIRI-FM -The SIRI-FM updaters works slighly differently from the other in that it only updates the availability +The SIRI-FM updaters works slighly differently from the others in that it only updates the availability of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile From fbca53de934767fd84f7f4f7a091fb044d20d31d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 Aug 2024 22:42:55 +0000 Subject: [PATCH 103/148] Update jersey monorepo to v3.1.8 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 73ef86f3be9..a9e591f0d3a 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ 31.3 2.51.1 2.17.2 - 3.1.7 + 3.1.8 5.10.3 1.13.2 5.6.0 From db62d62d046a017833a516836edd45c2abd3f82f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sun, 4 Aug 2024 23:26:23 +0200 Subject: [PATCH 104/148] Fix spelling [ci skip] --- docs/RouteRequest.md | 4 ++-- .../standalone/config/routerequest/RouteRequestConfig.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/RouteRequest.md b/docs/RouteRequest.md index 14d937eeb63..a8baaf1603b 100644 --- a/docs/RouteRequest.md +++ b/docs/RouteRequest.md @@ -254,7 +254,7 @@ This is a performance limit and should therefore be set high. Results close to t guaranteed to be optimal. Use itinerary-filters to limit what is presented to the client. The duration can be set per mode(`maxDirectStreetDurationForMode`), because some street modes searches are much more resource intensive than others. A default value is applied if the mode specific value -do not exist." +does not exist."

maxJourneyDuration

@@ -403,7 +403,7 @@ This is a performance limit and should therefore be set high. Results close to t guaranteed to be optimal. Use itinerary-filters to limit what is presented to the client. The duration can be set per mode(`maxDurationForMode`), because some street modes searches are much more resource intensive than others. A default value is applied if the mode specific value -do not exist. +does not exist.

maxStopCount

diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java index 6fa33aac267..dc91388458a 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java @@ -514,7 +514,7 @@ the access legs used. In other cases where the access(CAR) is faster than transi guaranteed to be optimal. Use itinerary-filters to limit what is presented to the client. The duration can be set per mode(`maxDurationForMode`), because some street modes searches are much more resource intensive than others. A default value is applied if the mode specific value -do not exist. +does not exist. """ ) .asDuration(dftAccessEgress.maxDuration().defaultValue()), @@ -554,7 +554,7 @@ duration can be set per mode(`maxDurationForMode`), because some street modes se guaranteed to be optimal. Use itinerary-filters to limit what is presented to the client. The duration can be set per mode(`maxDirectStreetDurationForMode`), because some street modes searches are much more resource intensive than others. A default value is applied if the mode specific value -do not exist." +does not exist." """ ) .asDuration(dft.maxDirectDuration().defaultValue()), From 1ba10c05df1d030adc7cba3c5566aa58b893ebd4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 5 Aug 2024 14:13:04 +0200 Subject: [PATCH 105/148] Compile docs --- docs/sandbox/VehicleParking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index 45a2dc28ea8..9ca4dcc4ee3 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -375,7 +375,7 @@ HTTP headers to add to the request. Any header key, value can be inserted. ## SIRI-FM -The SIRI-FM updaters works slighly differently from the other in that it only updates the availability +The SIRI-FM updaters works slighly differently from the others in that it only updates the availability of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile From a2214eb5882b494c0754f097ff7694f619fbf879 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 5 Aug 2024 14:18:01 +0200 Subject: [PATCH 106/148] Rename class --- .../vehicle_parking/VehicleParkingAvailabilityUpdater.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java index 86e7b72c9ff..2a5de121893 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -54,17 +54,17 @@ protected void runPolling() { if (source.update()) { var updates = source.getUpdates(); - var graphWriterRunnable = new UpdateAvailabilities(updates); + var graphWriterRunnable = new AvailabilityUpdater(updates); saveResultOnGraph.execute(graphWriterRunnable); } } - private class UpdateAvailabilities implements GraphWriterRunnable { + private class AvailabilityUpdater implements GraphWriterRunnable { private final List updates; private final Map parkingById; - private UpdateAvailabilities(List updates) { + private AvailabilityUpdater(List updates) { this.updates = List.copyOf(updates); this.parkingById = vehicleParkingService From 1837b79b2390d4a44f4c536c1aa19d11a048c5e8 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 5 Aug 2024 16:58:20 +0200 Subject: [PATCH 107/148] Apply review suggestions --- doc-templates/VehicleParking.md | 2 +- docs/sandbox/VehicleParking.md | 2 +- .../configure/UpdaterConfigurator.java | 4 --- ...VehicleParkingAvailabilityUpdaterTest.java | 36 ++++++++----------- 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/doc-templates/VehicleParking.md b/doc-templates/VehicleParking.md index a8a46d7875d..e3fcf84dc6e 100644 --- a/doc-templates/VehicleParking.md +++ b/doc-templates/VehicleParking.md @@ -50,7 +50,7 @@ All updaters have the following parameters in common: ## SIRI-FM -The SIRI-FM updaters works slighly differently from the others in that it only updates the availability +The SIRI-FM updater works slightly differently from the others in that it only updates the availability of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index 9ca4dcc4ee3..926a12b88a5 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -375,7 +375,7 @@ HTTP headers to add to the request. Any header key, value can be inserted. ## SIRI-FM -The SIRI-FM updaters works slighly differently from the others in that it only updates the availability +The SIRI-FM updater works slightly differently from the others in that it only updates the availability of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile diff --git a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java index 6662bae362b..83e0bd0fe85 100644 --- a/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java +++ b/src/main/java/org/opentripplanner/updater/configure/UpdaterConfigurator.java @@ -32,8 +32,6 @@ import org.opentripplanner.updater.vehicle_position.PollingVehiclePositionUpdater; import org.opentripplanner.updater.vehicle_rental.VehicleRentalUpdater; import org.opentripplanner.updater.vehicle_rental.datasources.VehicleRentalDataSourceFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Sets up and starts all the graph updaters. @@ -44,8 +42,6 @@ */ public class UpdaterConfigurator { - private static final Logger LOG = LoggerFactory.getLogger(UpdaterConfigurator.class); - private final Graph graph; private final TransitModel transitModel; private final UpdatersParameters updatersParameters; diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java index 0af58378f90..01fdb5425ee 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -2,10 +2,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.standalone.config.framework.json.JsonSupport.newNodeAdapterForTest; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; import com.google.common.util.concurrent.Futures; -import java.time.Duration; import java.util.List; import java.util.concurrent.Future; import org.junit.jupiter.api.Test; @@ -15,6 +15,7 @@ import org.opentripplanner.routing.vehicle_parking.VehicleParking; import org.opentripplanner.routing.vehicle_parking.VehicleParkingService; import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces; +import org.opentripplanner.standalone.config.routerconfig.updaters.VehicleParkingUpdaterConfig; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.service.TransitModel; import org.opentripplanner.updater.GraphUpdaterManager; @@ -24,27 +25,20 @@ class VehicleParkingAvailabilityUpdaterTest { - private static final VehicleParkingUpdaterParameters PARAMETERS = new VehicleParkingUpdaterParameters() { - @Override - public VehicleParkingSourceType sourceType() { - return VehicleParkingSourceType.SIRI_FM; - } - - @Override - public UpdateType updateType() { - return UpdateType.AVAILABILITY_ONLY; - } - - @Override - public Duration frequency() { - return Duration.ZERO; - } + private static final VehicleParkingUpdaterParameters PARAMETERS = VehicleParkingUpdaterConfig.create( + "ref", + newNodeAdapterForTest( + """ + { + "type" : "vehicle-parking", + "feedId" : "parking", + "sourceType" : "siri-fm", + "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" + } + """ + ) + ); - @Override - public String configRef() { - return null; - } - }; private static final FeedScopedId ID = id("parking1"); private static final AvailabiltyUpdate DEFAULT_UPDATE = new AvailabiltyUpdate(ID, 8); From f2ba2c8b951a25c7eddb0b1ed889a2fd772c23b9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 5 Aug 2024 17:23:30 +0200 Subject: [PATCH 108/148] Lower frequency --- .../vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java index 01fdb5425ee..87222eeba8f 100644 --- a/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java +++ b/src/test/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdaterTest.java @@ -33,6 +33,7 @@ class VehicleParkingAvailabilityUpdaterTest { "type" : "vehicle-parking", "feedId" : "parking", "sourceType" : "siri-fm", + "frequency": "0s", "url" : "https://transmodel.api.opendatahub.com/siri-lite/fm/parking" } """ From 42e45e19041ff6d4e350bd273dda5d71a54d2855 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 25 Jul 2024 16:11:42 +0200 Subject: [PATCH 109/148] Use original realtime pattern as key --- .../DefaultRealtimeVehicleService.java | 7 ++- .../DefaultRealtimeVehicleServiceTest.java | 55 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleServiceTest.java diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java index 07fa48fa84f..8b87fc432a9 100644 --- a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java @@ -8,7 +8,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nonnull; import org.opentripplanner.service.realtimevehicles.RealtimeVehicleRepository; @@ -35,6 +34,9 @@ public DefaultRealtimeVehicleService(TransitService transitService) { @Override public void setRealtimeVehicles(TripPattern pattern, List updates) { + if (pattern.getOriginalTripPattern() != null) { + pattern = pattern.getOriginalTripPattern(); + } vehicles.put(pattern, List.copyOf(updates)); } @@ -45,6 +47,9 @@ public void clearRealtimeVehicles(TripPattern pattern) { @Override public List getRealtimeVehicles(@Nonnull TripPattern pattern) { + if (pattern.getOriginalTripPattern() != null) { + pattern = pattern.getOriginalTripPattern(); + } // the list is made immutable during insertion, so we can safely return them return vehicles.getOrDefault(pattern, List.of()); } diff --git a/src/test/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleServiceTest.java b/src/test/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleServiceTest.java new file mode 100644 index 00000000000..a1c9dddaab0 --- /dev/null +++ b/src/test/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleServiceTest.java @@ -0,0 +1,55 @@ +package org.opentripplanner.service.realtimevehicles.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.framework.geometry.WgsCoordinate.GREENWICH; +import static org.opentripplanner.transit.model._data.TransitModelForTest.route; +import static org.opentripplanner.transit.model._data.TransitModelForTest.tripPattern; + +import java.time.Instant; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.opentripplanner.service.realtimevehicles.model.RealtimeVehicle; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.network.StopPattern; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.TransitModel; + +class DefaultRealtimeVehicleServiceTest { + + private static final Route ROUTE = route("r1").build(); + private static final TransitModelForTest MODEL = TransitModelForTest.of(); + private static final StopPattern STOP_PATTERN = TransitModelForTest.stopPattern( + MODEL.stop("1").build(), + MODEL.stop("2").build() + ); + private static final TripPattern ORIGINAL = tripPattern("original", ROUTE) + .withStopPattern(STOP_PATTERN) + .build(); + private static final Instant TIME = Instant.ofEpochSecond(1000); + private static final List VEHICLES = List.of( + RealtimeVehicle.builder().withTime(TIME).withCoordinates(GREENWICH).build() + ); + + @Test + void originalPattern() { + var service = new DefaultRealtimeVehicleService(new DefaultTransitService(new TransitModel())); + service.setRealtimeVehicles(ORIGINAL, VEHICLES); + var updates = service.getRealtimeVehicles(ORIGINAL); + assertEquals(VEHICLES, updates); + } + + @Test + void realtimeAddedPattern() { + var service = new DefaultRealtimeVehicleService(new DefaultTransitService(new TransitModel())); + var realtimePattern = tripPattern("realtime-added", ROUTE) + .withStopPattern(STOP_PATTERN) + .withOriginalTripPattern(ORIGINAL) + .withCreatedByRealtimeUpdater(true) + .build(); + service.setRealtimeVehicles(realtimePattern, VEHICLES); + var updates = service.getRealtimeVehicles(ORIGINAL); + assertEquals(VEHICLES, updates); + } +} From e81aa53ca03884de761f4f6dcfde200a75655a5b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 6 Aug 2024 12:40:03 +0200 Subject: [PATCH 110/148] Add Javadoc --- .../internal/DefaultRealtimeVehicleService.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java index 8b87fc432a9..0058cdd9e15 100644 --- a/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java +++ b/src/main/java/org/opentripplanner/service/realtimevehicles/internal/DefaultRealtimeVehicleService.java @@ -32,6 +32,11 @@ public DefaultRealtimeVehicleService(TransitService transitService) { this.transitService = transitService; } + /** + * Stores the relationship between a list of realtime vehicles with a pattern. If the pattern is + * a realtime-added one, then the original (scheduled) one is used as the key for the map storing + * the information. + */ @Override public void setRealtimeVehicles(TripPattern pattern, List updates) { if (pattern.getOriginalTripPattern() != null) { @@ -45,6 +50,13 @@ public void clearRealtimeVehicles(TripPattern pattern) { vehicles.remove(pattern); } + /** + * Gets the realtime vehicles for a given pattern. If the pattern is a realtime-added one + * then the original (scheduled) one is used for the lookup instead, so you receive the correct + * result no matter if you use the realtime or static information. + * + * @see DefaultRealtimeVehicleService#setRealtimeVehicles(TripPattern, List) + */ @Override public List getRealtimeVehicles(@Nonnull TripPattern pattern) { if (pattern.getOriginalTripPattern() != null) { From 0ceb95109ea758f177c14cd8dcf9b69c4ea5d238 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 6 Aug 2024 13:09:38 +0200 Subject: [PATCH 111/148] Fix conversion of parent station id in vector tiles --- .../layers/stops/StopsLayerTest.java | 75 ++++++++++--------- .../stops/DigitransitStopPropertyMapper.java | 7 +- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java index 4c3e60701bd..7760eee13b8 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerTest.java @@ -1,56 +1,60 @@ package org.opentripplanner.ext.vectortiles.layers.stops; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.transit.model._data.TransitModelForTest.id; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opentripplanner.ext.vectortiles.layers.TestTransitService; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.framework.i18n.I18NString; import org.opentripplanner.framework.i18n.TranslatedString; import org.opentripplanner.transit.model.framework.Deduplicator; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.Station; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.TransitModel; public class StopsLayerTest { - private RegularStop stop; + private static final I18NString NAME_TRANSLATIONS = TranslatedString.getI18NString( + new HashMap<>() { + { + put(null, "name"); + put("de", "nameDE"); + } + }, + false, + false + ); + private static final I18NString DESC_TRANSLATIONS = TranslatedString.getI18NString( + new HashMap<>() { + { + put(null, "desc"); + put("de", "descDE"); + } + }, + false, + false + ); - @BeforeEach - public void setUp() { - var nameTranslations = TranslatedString.getI18NString( - new HashMap<>() { - { - put(null, "name"); - put("de", "nameDE"); - } - }, - false, - false - ); - var descTranslations = TranslatedString.getI18NString( - new HashMap<>() { - { - put(null, "desc"); - put("de", "descDE"); - } - }, - false, - false - ); - stop = - StopModel - .of() - .regularStop(new FeedScopedId("F", "name")) - .withName(nameTranslations) - .withDescription(descTranslations) - .withCoordinate(50, 10) - .build(); - } + private static final Station STATION = Station + .of(id("station1")) + .withCoordinate(WgsCoordinate.GREENWICH) + .withName(I18NString.of("A Station")) + .build(); + private static final RegularStop STOP = StopModel + .of() + .regularStop(new FeedScopedId("F", "name")) + .withName(NAME_TRANSLATIONS) + .withDescription(DESC_TRANSLATIONS) + .withCoordinate(50, 10) + .withParentStation(STATION) + .build(); @Test public void digitransitStopPropertyMapperTest() { @@ -65,12 +69,13 @@ public void digitransitStopPropertyMapperTest() { ); Map map = new HashMap<>(); - mapper.map(stop).forEach(o -> map.put(o.key(), o.value())); + mapper.map(STOP).forEach(o -> map.put(o.key(), o.value())); assertEquals("F:name", map.get("gtfsId")); assertEquals("name", map.get("name")); assertEquals("desc", map.get("desc")); assertEquals("[{\"gtfsType\":100}]", map.get("routes")); + assertEquals(STATION.getId().toString(), map.get("parentStation")); } @Test @@ -86,7 +91,7 @@ public void digitransitStopPropertyMapperTranslationTest() { ); Map map = new HashMap<>(); - mapper.map(stop).forEach(o -> map.put(o.key(), o.value())); + mapper.map(STOP).forEach(o -> map.put(o.key(), o.value())); assertEquals("nameDE", map.get("name")); assertEquals("descDE", map.get("desc")); diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java index d10e221b1d5..edf9c7d8188 100644 --- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java +++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/DigitransitStopPropertyMapper.java @@ -1,5 +1,7 @@ package org.opentripplanner.ext.vectortiles.layers.stops; +import static org.opentripplanner.inspector.vector.KeyValue.kv; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Collection; @@ -52,10 +54,7 @@ protected static Collection getBaseKeyValues( new KeyValue("desc", i18NStringMapper.mapToApi(stop.getDescription())), new KeyValue("type", getType(transitService, stop)), new KeyValue("routes", getRoutes(transitService, stop)), - new KeyValue( - "parentStation", - stop.getParentStation() != null ? stop.getParentStation().getId() : null - ) + kv("parentStation", stop.getParentStation() != null ? stop.getParentStation().getId() : null) ); } From e5044fd9ec66a1ef47919a514e4fffc3d5d4827d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 00:04:17 +0000 Subject: [PATCH 112/148] Update dependency @graphql-codegen/add to v5.0.3 --- .../opentripplanner/apis/gtfs/generated/package.json | 2 +- .../org/opentripplanner/apis/gtfs/generated/yarn.lock | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json index 6a840640ca9..6dd08bb8041 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json @@ -10,7 +10,7 @@ }, "license": "LGPL-3.0", "dependencies": { - "@graphql-codegen/add": "5.0.2", + "@graphql-codegen/add": "5.0.3", "@graphql-codegen/cli": "5.0.2", "@graphql-codegen/java": "4.0.1", "@graphql-codegen/java-resolvers": "3.0.0", diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock index 77829ecc911..41bc851882b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock @@ -699,7 +699,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@graphql-codegen/add@5.0.2", "@graphql-codegen/add@^5.0.2": +"@graphql-codegen/add@5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-5.0.3.tgz#1ede6bac9a93661ed7fa5808b203d079e1b1d215" + integrity sha512-SxXPmramkth8XtBlAHu4H4jYcYXM/o3p01+psU+0NADQowA8jtYkK6MW5rV6T+CxkEaNZItfSmZRPgIuypcqnA== + dependencies: + "@graphql-codegen/plugin-helpers" "^5.0.3" + tslib "~2.6.0" + +"@graphql-codegen/add@^5.0.2": version "5.0.2" resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-5.0.2.tgz#71b3ae0465a4537172dddb84531b6967ca5545f2" integrity sha512-ouBkSvMFUhda5VoKumo/ZvsZM9P5ZTyDsI8LW18VxSNWOjrTeLXBWHG8Gfaai0HwhflPtCYVABbriEcOmrRShQ== From 14e7b3a9ddc9ca29944fcf229586c06bcb29d4ff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 21:51:59 +0000 Subject: [PATCH 113/148] Update slf4j monorepo to v2.0.14 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e3437b2a063..89fb83240d1 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ 5.6.0 1.5.6 9.11.1 - 2.0.13 + 2.0.14 2.0.15 1.27 4.0.5 From 2dcee7703c8caafe98f6a4ab62aafd67ba820b63 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 6 Aug 2024 12:30:11 +0200 Subject: [PATCH 114/148] Add test case --- .../ext/geocoder/LuceneIndexTest.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 3e6c2b15195..4403d44679f 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -98,6 +98,10 @@ class LuceneIndexTest { .withCoordinate(52.52277, 13.41046) .build(); + static final RegularStop MERIDIAN_AVE = TEST_MODEL.stop("Meridian Ave N & N 148th").build(); + + static final RegularStop MERIDIAN_1 = TEST_MODEL.stop("Meridian N & Spencer").build(); + static LuceneIndex index; static StopClusterMapper mapper; @@ -113,7 +117,9 @@ static void setup() { LICHTERFELDE_OST_2, WESTHAFEN, ARTS_CENTER, - ARTHUR + ARTHUR, + MERIDIAN_AVE, + MERIDIAN_1 ) .forEach(stopModel::withRegularStop); List @@ -295,6 +301,15 @@ void agenciesAndFeedPublisher() { assertEquals(List.of(StopClusterMapper.toAgency(BVG)), cluster.primary().agencies()); assertEquals("A Publisher", cluster.primary().feedPublisher().name()); } + + @Test + void numbers() { + var result = index + .queryStopClusters("Meridian Ave N & N 148") + .map(s -> s.primary().name()) + .toList(); + assertEquals(List.of("Meridian Ave N & N 148th", "Meridian N & Spencer"), result); + } } private static @Nonnull Function primaryId() { From 1a641dda4b47cb01b804e90badfa7a6ea455189b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 6 Aug 2024 12:59:09 +0200 Subject: [PATCH 115/148] Flesh out tests --- .../ext/geocoder/LuceneIndexTest.java | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 4403d44679f..582fb2409d7 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -12,7 +12,6 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import javax.annotation.Nonnull; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -98,9 +97,33 @@ class LuceneIndexTest { .withCoordinate(52.52277, 13.41046) .build(); - static final RegularStop MERIDIAN_AVE = TEST_MODEL.stop("Meridian Ave N & N 148th").build(); + static final RegularStop MERIDIAN_AVE = TEST_MODEL + .stop("Meridian Ave N & N 148th St") + .withId(FeedScopedId.parse("kcm:16340")) + .withCode("16340") + .withCoordinate(47.736145, -122.33445) + .build(); + + static final RegularStop MERIDIAN_N_1 = TEST_MODEL + .stop("Meridian N & Spencer") + .withId(FeedScopedId.parse("pierce:13268")) + .withCode("4168") + .withCoordinate(47.209366,-122.293999) + .build(); - static final RegularStop MERIDIAN_1 = TEST_MODEL.stop("Meridian N & Spencer").build(); + static final RegularStop MERIDIAN_N_2 = TEST_MODEL + .stop("Meridian N & Spencer") + .withId(FeedScopedId.parse("pierce:30976")) + .withCode("4169") + .withCoordinate(47.209316,-122.293841) + .build(); + + static final RegularStop MERIDIAN_N_3 = TEST_MODEL + .stop("N 205th St & Meridian Ave N") + .withId(FeedScopedId.parse("commtrans:490")) + .withCode("490") + .withCoordinate(47.209316,-122.293841) + .build(); static LuceneIndex index; @@ -118,8 +141,10 @@ static void setup() { WESTHAFEN, ARTS_CENTER, ARTHUR, - MERIDIAN_AVE, - MERIDIAN_1 + MERIDIAN_N_1, + MERIDIAN_N_2, + MERIDIAN_N_3, + MERIDIAN_AVE ) .forEach(stopModel::withRegularStop); List @@ -303,16 +328,13 @@ void agenciesAndFeedPublisher() { } @Test - void numbers() { - var result = index - .queryStopClusters("Meridian Ave N & N 148") - .map(s -> s.primary().name()) - .toList(); - assertEquals(List.of("Meridian Ave N & N 148th", "Meridian N & Spencer"), result); + void number() { + var names = index.queryStopClusters("Meridian Ave N & N 148").map(c -> c.primary().name()).toList(); + assertEquals(List.of(MERIDIAN_AVE.getName().toString(), MERIDIAN_N_1.getName().toString()), names); } } - private static @Nonnull Function primaryId() { + private static Function primaryId() { return c -> c.primary().id(); } } From 9c60311511a9a534bac2480b95029b0f3bc1fc01 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 6 Aug 2024 13:53:33 +0200 Subject: [PATCH 116/148] Add test for ampersand ngram --- .../geocoder/EnglishNgramAnalyzerTest.java | 34 +++++++++++++++++++ .../ext/geocoder/LuceneIndexTest.java | 16 ++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java index 615ef90cbbd..b4b145063f8 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java @@ -82,6 +82,40 @@ void ngram() throws IOException { ); } + @Test + void ampersand() throws IOException { + var analyzer = new EnglishNGramAnalyzer(); + List result = analyze("Meridian Ave N & N 148th St", analyzer); + + assertEquals( + List.of( + "Meri", + "Merid", + "Meridi", + "Meridia", + "Meridian", + "erid", + "eridi", + "eridia", + "eridian", + "ridi", + "ridia", + "ridian", + "idia", + "idian", + "dian", + "Av", + "N", + "N", + "148t", + "148th", + "48th", + "St" + ), + result + ); + } + public List analyze(String text, Analyzer analyzer) throws IOException { List result = new ArrayList<>(); TokenStream tokenStream = analyzer.tokenStream("name", text); diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 582fb2409d7..80899c9d5d8 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -108,21 +108,21 @@ class LuceneIndexTest { .stop("Meridian N & Spencer") .withId(FeedScopedId.parse("pierce:13268")) .withCode("4168") - .withCoordinate(47.209366,-122.293999) + .withCoordinate(47.209366, -122.293999) .build(); static final RegularStop MERIDIAN_N_2 = TEST_MODEL .stop("Meridian N & Spencer") .withId(FeedScopedId.parse("pierce:30976")) .withCode("4169") - .withCoordinate(47.209316,-122.293841) + .withCoordinate(47.209316, -122.293841) .build(); static final RegularStop MERIDIAN_N_3 = TEST_MODEL .stop("N 205th St & Meridian Ave N") .withId(FeedScopedId.parse("commtrans:490")) .withCode("490") - .withCoordinate(47.209316,-122.293841) + .withCoordinate(47.777632, -122.3346) .build(); static LuceneIndex index; @@ -329,8 +329,14 @@ void agenciesAndFeedPublisher() { @Test void number() { - var names = index.queryStopClusters("Meridian Ave N & N 148").map(c -> c.primary().name()).toList(); - assertEquals(List.of(MERIDIAN_AVE.getName().toString(), MERIDIAN_N_1.getName().toString()), names); + var names = index + .queryStopClusters("Meridian Ave N & N 148") + .map(c -> c.primary().name()) + .toList(); + assertEquals( + List.of(MERIDIAN_AVE.getName().toString(), MERIDIAN_N_1.getName().toString()), + names + ); } } From e61d1c187cc625cb94b9743a4f831f7be748f74e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 6 Aug 2024 15:36:17 +0200 Subject: [PATCH 117/148] Finetune fuzzyness of lucene indexing --- .../geocoder/EnglishNgramAnalyzerTest.java | 24 ++++++++++++++++++- .../ext/geocoder/LuceneIndexTest.java | 18 +++++++------- .../ext/geocoder/EnglishNGramAnalyzer.java | 2 +- .../ext/geocoder/LuceneIndex.java | 2 +- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java index b4b145063f8..a1194c46e90 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; @@ -17,9 +18,9 @@ void ngram() throws IOException { var analyzer = new EnglishNGramAnalyzer(); List result = analyze("Alexanderplatz", analyzer); - //System.out.println(result.stream().collect(Collectors.joining("\",\"", "\"", "\""))); assertEquals( List.of( + "Ale", "Alex", "Alexa", "Alexan", @@ -27,6 +28,7 @@ void ngram() throws IOException { "Alexande", "Alexander", "Alexanderp", + "lex", "lexa", "lexan", "lexand", @@ -34,6 +36,7 @@ void ngram() throws IOException { "lexander", "lexanderp", "lexanderpl", + "exa", "exan", "exand", "exande", @@ -41,6 +44,7 @@ void ngram() throws IOException { "exanderp", "exanderpl", "exanderpla", + "xan", "xand", "xande", "xander", @@ -48,6 +52,7 @@ void ngram() throws IOException { "xanderpl", "xanderpla", "xanderplat", + "and", "ande", "ander", "anderp", @@ -55,27 +60,34 @@ void ngram() throws IOException { "anderpla", "anderplat", "anderplatz", + "nde", "nder", "nderp", "nderpl", "nderpla", "nderplat", "nderplatz", + "der", "derp", "derpl", "derpla", "derplat", "derplatz", + "erp", "erpl", "erpla", "erplat", "erplatz", + "rpl", "rpla", "rplat", "rplatz", + "pla", "plat", "platz", + "lat", "latz", + "atz", "Alexanderplatz" ), result @@ -87,29 +99,39 @@ void ampersand() throws IOException { var analyzer = new EnglishNGramAnalyzer(); List result = analyze("Meridian Ave N & N 148th St", analyzer); + System.out.println(result.stream().collect(Collectors.joining("\",\"", "\"", "\""))); assertEquals( List.of( + "Mer", "Meri", "Merid", "Meridi", "Meridia", "Meridian", + "eri", "erid", "eridi", "eridia", "eridian", + "rid", "ridi", "ridia", "ridian", + "idi", "idia", "idian", + "dia", "dian", + "ian", "Av", "N", "N", + "148", "148t", "148th", + "48t", "48th", + "8th", "St" ), result diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 80899c9d5d8..e94532a0589 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -12,6 +12,7 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -259,7 +260,7 @@ void stopClustersWithTypos(String searchTerm) { @Test void fuzzyStopClusters() { var result1 = index.queryStopClusters("arts").map(primaryId()).toList(); - assertEquals(List.of(ARTS_CENTER.getId()), result1); + assertEquals(List.of(ARTS_CENTER.getId(), ARTHUR.getId()), result1); } @Test @@ -327,14 +328,15 @@ void agenciesAndFeedPublisher() { assertEquals("A Publisher", cluster.primary().feedPublisher().name()); } - @Test - void number() { - var names = index - .queryStopClusters("Meridian Ave N & N 148") - .map(c -> c.primary().name()) - .toList(); + @ParameterizedTest + @ValueSource(strings = { "Meridian Ave N & N 148th", "Meridian Ave N & N 148" }) + void shortTokens(String query) { + var names = index.queryStopClusters(query).map(c -> c.primary().name()).toList(); assertEquals( - List.of(MERIDIAN_AVE.getName().toString(), MERIDIAN_N_1.getName().toString()), + Stream + .of(MERIDIAN_AVE, MERIDIAN_N_3, MERIDIAN_N_1) + .map(s -> s.getName().toString()) + .toList(), names ); } diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java index ffe46604744..26f6723a8b2 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java @@ -28,7 +28,7 @@ protected TokenStreamComponents createComponents(String fieldName) { result = new StopFilter(result, EnglishAnalyzer.ENGLISH_STOP_WORDS_SET); result = new PorterStemFilter(result); result = new CapitalizationFilter(result); - result = new NGramTokenFilter(result, 4, 10, true); + result = new NGramTokenFilter(result, 3, 10, true); return new TokenStreamComponents(src, result); } } diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java index fe7bef8ad13..71b80ac58a6 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/LuceneIndex.java @@ -288,7 +288,7 @@ private Stream matchingDocuments( } }); } else { - var nameParser = new QueryParser(NAME, analyzer); + var nameParser = new QueryParser(NAME_NGRAM, analyzer); var nameQuery = nameParser.parse(searchTerms); var ngramNameQuery = new TermQuery( From 2c13785294270ca3e39f2fa07f96f12c5589abb9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 6 Aug 2024 15:47:33 +0200 Subject: [PATCH 118/148] Add more test instances --- .../org/opentripplanner/ext/geocoder/LuceneIndexTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index e94532a0589..6117b56ebdd 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -329,7 +329,9 @@ void agenciesAndFeedPublisher() { } @ParameterizedTest - @ValueSource(strings = { "Meridian Ave N & N 148th", "Meridian Ave N & N 148" }) + @ValueSource( + strings = { "Meridian Ave N & N 148th", "Meridian Ave N & N 148", "Meridian Ave N N 148" } + ) void shortTokens(String query) { var names = index.queryStopClusters(query).map(c -> c.primary().name()).toList(); assertEquals( From e7ec0f78e6d4efe1be132be32e8b9115049417bb Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 6 Aug 2024 15:50:33 +0200 Subject: [PATCH 119/148] Simplify test setup --- .../ext/geocoder/LuceneIndexTest.java | 40 +++---------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 6117b56ebdd..f14faa9b274 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -98,33 +98,9 @@ class LuceneIndexTest { .withCoordinate(52.52277, 13.41046) .build(); - static final RegularStop MERIDIAN_AVE = TEST_MODEL - .stop("Meridian Ave N & N 148th St") - .withId(FeedScopedId.parse("kcm:16340")) - .withCode("16340") - .withCoordinate(47.736145, -122.33445) - .build(); - - static final RegularStop MERIDIAN_N_1 = TEST_MODEL - .stop("Meridian N & Spencer") - .withId(FeedScopedId.parse("pierce:13268")) - .withCode("4168") - .withCoordinate(47.209366, -122.293999) - .build(); - - static final RegularStop MERIDIAN_N_2 = TEST_MODEL - .stop("Meridian N & Spencer") - .withId(FeedScopedId.parse("pierce:30976")) - .withCode("4169") - .withCoordinate(47.209316, -122.293841) - .build(); - - static final RegularStop MERIDIAN_N_3 = TEST_MODEL - .stop("N 205th St & Meridian Ave N") - .withId(FeedScopedId.parse("commtrans:490")) - .withCode("490") - .withCoordinate(47.777632, -122.3346) - .build(); + static final RegularStop MERIDIAN_AVE = TEST_MODEL.stop("Meridian Ave N & N 148th St").build(); + static final RegularStop MERIDIAN_N1 = TEST_MODEL.stop("Meridian N & Spencer").build(); + static final RegularStop MERIDIAN_N2 = TEST_MODEL.stop("N 205th St & Meridian Ave N").build(); static LuceneIndex index; @@ -142,9 +118,8 @@ static void setup() { WESTHAFEN, ARTS_CENTER, ARTHUR, - MERIDIAN_N_1, - MERIDIAN_N_2, - MERIDIAN_N_3, + MERIDIAN_N1, + MERIDIAN_N2, MERIDIAN_AVE ) .forEach(stopModel::withRegularStop); @@ -335,10 +310,7 @@ void agenciesAndFeedPublisher() { void shortTokens(String query) { var names = index.queryStopClusters(query).map(c -> c.primary().name()).toList(); assertEquals( - Stream - .of(MERIDIAN_AVE, MERIDIAN_N_3, MERIDIAN_N_1) - .map(s -> s.getName().toString()) - .toList(), + Stream.of(MERIDIAN_AVE, MERIDIAN_N2, MERIDIAN_N1).map(s -> s.getName().toString()).toList(), names ); } From 5b8cdc5a877afd52ef320bb56d21b855a7986c4a Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 6 Aug 2024 17:42:46 +0200 Subject: [PATCH 120/148] Move back the debug line --- .../opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java index a1194c46e90..e859069dbca 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; @@ -18,6 +17,7 @@ void ngram() throws IOException { var analyzer = new EnglishNGramAnalyzer(); List result = analyze("Alexanderplatz", analyzer); + //System.out.println(result.stream().collect(Collectors.joining("\",\"", "\"", "\""))); assertEquals( List.of( "Ale", @@ -99,7 +99,6 @@ void ampersand() throws IOException { var analyzer = new EnglishNGramAnalyzer(); List result = analyze("Meridian Ave N & N 148th St", analyzer); - System.out.println(result.stream().collect(Collectors.joining("\",\"", "\"", "\""))); assertEquals( List.of( "Mer", From d8c31dbcfe40d008d8ef45856c3a45729baa03f5 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 7 Aug 2024 09:02:29 +0200 Subject: [PATCH 121/148] Add more test cases --- .../opentripplanner/ext/geocoder/LuceneIndexTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index f14faa9b274..6b9ed853ad1 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -305,7 +305,15 @@ void agenciesAndFeedPublisher() { @ParameterizedTest @ValueSource( - strings = { "Meridian Ave N & N 148th", "Meridian Ave N & N 148", "Meridian Ave N N 148" } + strings = { + "Meridian Ave N & N 148th", + "Meridian Ave N & N 148", + "Meridian Ave N N 148", + "Meridian Ave N 148", + "Meridian & N 148", + "Meridian Ave 148", + "Meridian Av 148", + } ) void shortTokens(String query) { var names = index.queryStopClusters(query).map(c -> c.primary().name()).toList(); From edea05e139f12b434bf5244e8d74e4daf432c64b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 7 Aug 2024 11:48:22 +0200 Subject: [PATCH 122/148] Tokenize number suffixes --- .../geocoder/EnglishNgramAnalyzerTest.java | 80 ++++++++++--------- .../ext/geocoder/LuceneIndexTest.java | 3 +- .../ext/geocoder/EnglishNGramAnalyzer.java | 13 ++- 3 files changed, 55 insertions(+), 41 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java index e859069dbca..77917399647 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java @@ -5,22 +5,21 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; class EnglishNgramAnalyzerTest { @Test - void ngram() throws IOException { - var analyzer = new EnglishNGramAnalyzer(); - List result = analyze("Alexanderplatz", analyzer); + void ngram() { + List result = tokenize("Alexanderplatz"); //System.out.println(result.stream().collect(Collectors.joining("\",\"", "\"", "\""))); assertEquals( List.of( - "Ale", "Alex", "Alexa", "Alexan", @@ -28,7 +27,6 @@ void ngram() throws IOException { "Alexande", "Alexander", "Alexanderp", - "lex", "lexa", "lexan", "lexand", @@ -36,7 +34,6 @@ void ngram() throws IOException { "lexander", "lexanderp", "lexanderpl", - "exa", "exan", "exand", "exande", @@ -44,7 +41,6 @@ void ngram() throws IOException { "exanderp", "exanderpl", "exanderpla", - "xan", "xand", "xande", "xander", @@ -52,7 +48,6 @@ void ngram() throws IOException { "xanderpl", "xanderpla", "xanderplat", - "and", "ande", "ander", "anderp", @@ -60,34 +55,27 @@ void ngram() throws IOException { "anderpla", "anderplat", "anderplatz", - "nde", "nder", "nderp", "nderpl", "nderpla", "nderplat", "nderplatz", - "der", "derp", "derpl", "derpla", "derplat", "derplatz", - "erp", "erpl", "erpla", "erplat", "erplatz", - "rpl", "rpla", "rplat", "rplatz", - "pla", "plat", "platz", - "lat", "latz", - "atz", "Alexanderplatz" ), result @@ -95,56 +83,72 @@ void ngram() throws IOException { } @Test - void ampersand() throws IOException { - var analyzer = new EnglishNGramAnalyzer(); - List result = analyze("Meridian Ave N & N 148th St", analyzer); + void ampersand() { + List result = tokenize("Meridian Ave N & N 148th St"); assertEquals( List.of( - "Mer", "Meri", "Merid", "Meridi", "Meridia", "Meridian", - "eri", "erid", "eridi", "eridia", "eridian", - "rid", "ridi", "ridia", "ridian", - "idi", "idia", "idian", - "dia", "dian", - "ian", "Av", "N", "N", "148", - "148t", - "148th", - "48t", - "48th", - "8th", "St" ), result ); } - public List analyze(String text, Analyzer analyzer) throws IOException { - List result = new ArrayList<>(); - TokenStream tokenStream = analyzer.tokenStream("name", text); - CharTermAttribute attr = tokenStream.addAttribute(CharTermAttribute.class); - tokenStream.reset(); - while (tokenStream.incrementToken()) { - result.add(attr.toString()); + @ParameterizedTest + @CsvSource( + value = { + "1st:1", + "2nd:2", + "3rd:3", + "4th:4", + "6th:6", + "148th:148", + "102nd:102", + "1003rd:1003", + "St:St", + "S3:S3", + "Aard:Aard", + }, + delimiter = ':' + ) + void numberSuffixes(String input, String expected) { + var result = tokenize(input); + assertEquals(List.of(expected), result); + } + + public List tokenize(String text) { + try (var analyzer = new EnglishNGramAnalyzer()) { + List result; + TokenStream tokenStream; + result = new ArrayList<>(); + tokenStream = analyzer.tokenStream("name", text); + CharTermAttribute attr = tokenStream.addAttribute(CharTermAttribute.class); + tokenStream.reset(); + while (tokenStream.incrementToken()) { + result.add(attr.toString()); + } + return result; + } catch (IOException e) { + throw new RuntimeException(e); } - return result; } } diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 6b9ed853ad1..f3af08f29fe 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -235,7 +235,7 @@ void stopClustersWithTypos(String searchTerm) { @Test void fuzzyStopClusters() { var result1 = index.queryStopClusters("arts").map(primaryId()).toList(); - assertEquals(List.of(ARTS_CENTER.getId(), ARTHUR.getId()), result1); + assertEquals(List.of(ARTS_CENTER.getId()), result1); } @Test @@ -313,6 +313,7 @@ void agenciesAndFeedPublisher() { "Meridian & N 148", "Meridian Ave 148", "Meridian Av 148", + "meridian av 148", } ) void shortTokens(String query) { diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java index 26f6723a8b2..922108427e8 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java @@ -1,14 +1,16 @@ package org.opentripplanner.ext.geocoder; +import java.util.regex.Pattern; import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.LowerCaseFilter; import org.apache.lucene.analysis.TokenStream; -import org.apache.lucene.analysis.core.LowerCaseFilter; import org.apache.lucene.analysis.core.StopFilter; import org.apache.lucene.analysis.en.EnglishAnalyzer; import org.apache.lucene.analysis.en.EnglishPossessiveFilter; import org.apache.lucene.analysis.en.PorterStemFilter; import org.apache.lucene.analysis.miscellaneous.CapitalizationFilter; import org.apache.lucene.analysis.ngram.NGramTokenFilter; +import org.apache.lucene.analysis.pattern.PatternReplaceFilter; import org.apache.lucene.analysis.standard.StandardTokenizer; /** @@ -17,18 +19,25 @@ * of a stop name can be matched efficiently. *

* For example the query of "exanderpl" will match the stop name "Alexanderplatz". + *

+ * It also removes number suffixes in the American street names, like "147th Street", which will + * be tokenized to "147 Street". */ class EnglishNGramAnalyzer extends Analyzer { + // matches one or more numbers followed by the English suffixes "st", "nd", "rd", "th" + private static final Pattern NUMBER_SUFFIX_PATTERN = Pattern.compile("(\\d+)[st|nd|rd|th]+"); + @Override protected TokenStreamComponents createComponents(String fieldName) { StandardTokenizer src = new StandardTokenizer(); TokenStream result = new EnglishPossessiveFilter(src); result = new LowerCaseFilter(result); + result = new PatternReplaceFilter(result, NUMBER_SUFFIX_PATTERN, "$1", true); result = new StopFilter(result, EnglishAnalyzer.ENGLISH_STOP_WORDS_SET); result = new PorterStemFilter(result); result = new CapitalizationFilter(result); - result = new NGramTokenFilter(result, 3, 10, true); + result = new NGramTokenFilter(result, 4, 10, true); return new TokenStreamComponents(src, result); } } From 25808af7b622bee1e770edf6281d526f0b2c76fb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:55:48 +0000 Subject: [PATCH 123/148] Update Debug UI dependencies (non-major) --- client/package-lock.json | 62 ++++++++++++++++++++++++---------------- client/package.json | 4 +-- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 9e2798660ec..87139a1a909 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,7 +12,7 @@ "bootstrap": "5.3.3", "graphql": "16.9.0", "graphql-request": "7.1.0", - "maplibre-gl": "4.5.0", + "maplibre-gl": "4.5.1", "react": "18.3.1", "react-bootstrap": "2.10.4", "react-dom": "18.3.1", @@ -40,7 +40,7 @@ "jsdom": "24.1.1", "prettier": "3.3.3", "typescript": "5.5.4", - "vite": "5.3.5", + "vite": "5.4.0", "vitest": "2.0.5" } }, @@ -3846,11 +3846,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/junit-report-builder": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/junit-report-builder/-/junit-report-builder-3.0.2.tgz", - "integrity": "sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==" - }, "node_modules/@types/mapbox__point-geometry": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", @@ -5701,9 +5696,10 @@ } }, "node_modules/earcut": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", - "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.0.tgz", + "integrity": "sha512-41Fs7Q/PLq1SDbqjsgcY7GA42T0jvaCNGXgGtsNdvg+Yv8eIu06bxv4/PoREkZ9nMDNwnUSG9OFB9+yv8eKhDg==", + "license": "ISC" }, "node_modules/eastasianwidth": { "version": "0.2.0", @@ -8731,9 +8727,9 @@ } }, "node_modules/maplibre-gl": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.5.0.tgz", - "integrity": "sha512-qOS1hn4d/pn2i0uva4S5Oz+fACzTkgBKq+NpwT/Tqzi4MSyzcWNtDELzLUSgWqHfNIkGCl5CZ/w7dtis+t4RCw==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.5.1.tgz", + "integrity": "sha512-pKFDK8ZU2atwZWC8gdPVhN7Bf5HIPgtA+IG/iQ7J6WgmqSwCSmylc5q3stahWqXfx9PYUwVNJITrp1Hw96SUiA==", "license": "BSD-3-Clause", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", @@ -8746,22 +8742,21 @@ "@maplibre/maplibre-gl-style-spec": "^20.3.0", "@types/geojson": "^7946.0.14", "@types/geojson-vt": "3.2.5", - "@types/junit-report-builder": "^3.0.2", "@types/mapbox__point-geometry": "^0.1.4", "@types/mapbox__vector-tile": "^1.3.4", "@types/pbf": "^3.0.5", "@types/supercluster": "^7.1.3", - "earcut": "^2.2.4", + "earcut": "^3.0.0", "geojson-vt": "^4.0.2", "gl-matrix": "^3.4.3", "global-prefix": "^3.0.0", "kdbush": "^4.0.2", "murmurhash-js": "^1.0.0", - "pbf": "^3.2.1", + "pbf": "^3.3.0", "potpack": "^2.0.0", - "quickselect": "^2.0.0", + "quickselect": "^3.0.0", "supercluster": "^8.0.1", - "tinyqueue": "^2.0.3", + "tinyqueue": "^3.0.0", "vt-pbf": "^3.1.3" }, "engines": { @@ -8772,6 +8767,18 @@ "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" } }, + "node_modules/maplibre-gl/node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, + "node_modules/maplibre-gl/node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -9495,9 +9502,10 @@ } }, "node_modules/pbf": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", - "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", + "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", + "license": "BSD-3-Clause", "dependencies": { "ieee754": "^1.1.12", "resolve-protobuf-schema": "^2.1.0" @@ -11392,14 +11400,14 @@ } }, "node_modules/vite": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", - "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz", + "integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.39", + "postcss": "^8.4.40", "rollup": "^4.13.0" }, "bin": { @@ -11419,6 +11427,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -11436,6 +11445,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/client/package.json b/client/package.json index fe8836e8d36..74b4ae8f0a2 100644 --- a/client/package.json +++ b/client/package.json @@ -21,7 +21,7 @@ "bootstrap": "5.3.3", "graphql": "16.9.0", "graphql-request": "7.1.0", - "maplibre-gl": "4.5.0", + "maplibre-gl": "4.5.1", "react": "18.3.1", "react-bootstrap": "2.10.4", "react-dom": "18.3.1", @@ -49,7 +49,7 @@ "jsdom": "24.1.1", "prettier": "3.3.3", "typescript": "5.5.4", - "vite": "5.3.5", + "vite": "5.4.0", "vitest": "2.0.5" } } From 998df59ae9d7f4ab82d08d6a354c9bad5ef6e695 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 10:42:53 +0200 Subject: [PATCH 124/148] Group some renovate updates together [ci skip] --- renovate.json5 | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/renovate.json5 b/renovate.json5 index 98ec1f24fbe..c544e2767c5 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -34,7 +34,7 @@ "matchFiles": ["client/package.json"], "matchUpdateTypes": ["patch", "minor"], "groupName": "Debug UI dependencies (non-major)", - "schedule": ["on the first day of the week"], + "schedule": ["on the 3rd and 17th day of the month"], "reviewers": ["testower"] }, { @@ -45,6 +45,8 @@ // some dependencies that we auto-merge release very often and even the auto-merges create a lot of // noise, so we slow it down a bit { + "description": "Automerge test dependencies in a single PR", + "groupName": "Test dependencies", "matchPackageNames": [ "org.mockito:mockito-core", "com.tngtech.archunit:archunit", @@ -57,7 +59,6 @@ "matchPackagePrefixes": [ "org.junit.jupiter:", ], - "groupName": "Test dependencies", "automerge": true, "schedule": "on the 17th day of the month" }, @@ -70,12 +71,22 @@ "automerge": true }, { + "description": "Automerge Maven plugins in a single PR", + "groupName": "Maven plugins", "matchPackageNames": [ "ch.qos.logback:logback-classic", "io.github.git-commit-id:git-commit-id-maven-plugin", - "org.apache.maven.plugins:maven-gpg-plugin" + "org.apache.maven.plugins:maven-gpg-plugin", + "org.codehaus.mojo:build-helper-maven-plugin", + "org.apache.maven.plugins:maven-source-plugin", + "com.hubspot.maven.plugins:prettier-maven-plugin", + "com.google.cloud.tools:jib-maven-plugin", + "org.apache.maven.plugins:maven-shade-plugin", + "org.apache.maven.plugins:maven-compiler-plugin", + "org.apache.maven.plugins:maven-jar-plugin", + "org.sonatype.plugins:nexus-staging-maven-plugin" ], - "schedule": "on the 19th day of the month", + "schedule": "on the 23rd day of the month", "automerge": true }, { @@ -125,18 +136,7 @@ "automerge": true }, { - "description": "automatically merge test, logging and build dependencies", - "matchPackageNames": [ - // maven plugins - "org.codehaus.mojo:build-helper-maven-plugin", - "org.apache.maven.plugins:maven-source-plugin", - "com.hubspot.maven.plugins:prettier-maven-plugin", - "com.google.cloud.tools:jib-maven-plugin", - "org.apache.maven.plugins:maven-shade-plugin", - "org.apache.maven.plugins:maven-compiler-plugin", - "org.apache.maven.plugins:maven-jar-plugin", - "org.sonatype.plugins:nexus-staging-maven-plugin" - ], + "description": "automatically merge slf4j", "matchPackagePrefixes": [ "org.slf4j:" ], From d4a95dbe29e15e51b3f6eb23b35407cc60cdaf3d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 10:51:06 +0200 Subject: [PATCH 125/148] Group code generation deps together [ci skip] --- renovate.json5 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/renovate.json5 b/renovate.json5 index c544e2767c5..2c405c51b2c 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -103,18 +103,17 @@ "org.onebusaway:onebusaway-gtfs", "com.google.cloud:libraries-bom", "com.google.guava:guava", - "@graphql-codegen/add", - "@graphql-codegen/cli", - "@graphql-codegen/java", - "@graphql-codegen/java-resolvers", - "graphql", "io.micrometer:micrometer-registry-prometheus", "io.micrometer:micrometer-registry-influx" ], - // we don't use the 'monthly' preset because that only fires on the first day of the month - // when there might already other PRs open "schedule": "on the 7th through 8th day of the month" }, + { + "groupName": "Update GTFS API code generation in a single PR", + "matchFiles": ["src/main/java/org/opentripplanner/apis/gtfs/generated/package.json"], + "reviewers": ["optionsome", "leonardehrenfried"], + "schedule": "on the 11th through 12th day of the month" + }, { "description": "in order to keep review burden low, don't update these quite so frequently", "matchPackagePrefixes": [ @@ -125,6 +124,7 @@ ] }, { + "groupName": "Automerge mkdocs in a single PR", "description": "automerge mkdocs-material every quarter", "matchPackageNames": [ "mkdocs", From 3eab7363287ed9b0845af121f45a1d1167363942 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 11:09:00 +0200 Subject: [PATCH 126/148] Slow down automerging of logging dependencies [ci skip] --- renovate.json5 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/renovate.json5 b/renovate.json5 index 2c405c51b2c..5a56c64cccb 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -74,7 +74,6 @@ "description": "Automerge Maven plugins in a single PR", "groupName": "Maven plugins", "matchPackageNames": [ - "ch.qos.logback:logback-classic", "io.github.git-commit-id:git-commit-id-maven-plugin", "org.apache.maven.plugins:maven-gpg-plugin", "org.codehaus.mojo:build-helper-maven-plugin", @@ -124,7 +123,7 @@ ] }, { - "groupName": "Automerge mkdocs in a single PR", + "groupName": "mkdocs", "description": "automerge mkdocs-material every quarter", "matchPackageNames": [ "mkdocs", @@ -136,12 +135,14 @@ "automerge": true }, { - "description": "automatically merge slf4j", + "description": "Automerge logging dependencies in a single PR", + "groupName": "logging", "matchPackagePrefixes": [ - "org.slf4j:" + "org.slf4j:", + "ch.qos.logback:" ], "automerge": true, - "schedule": "after 11pm and before 5am every weekday" + "schedule": "on the 4th day of the month" }, { "description": "give some projects time to publish a changelog before opening the PR", From 15a5bd24cf726314ea4a71e192c26d8ef1bb88aa Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Thu, 8 Aug 2024 10:12:15 +0000 Subject: [PATCH 127/148] Upgrade debug client to version 2024/08/2024-08-08T10:11 --- src/client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/index.html b/src/client/index.html index d1734898ca0..2d82a433279 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +

From e4199f1225f80fd399491318a4889cab7d4bed39 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 12:14:51 +0200 Subject: [PATCH 128/148] Apply review feedback --- .../routing/vehicle_parking/VehicleParking.java | 8 ++++++-- .../VehicleParkingAvailabilityUpdater.java | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index b50c583c79a..9cf198daa68 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -97,7 +97,7 @@ public class VehicleParking implements Serializable { /** * The currently available spaces at this vehicle parking. */ - private VehicleParkingSpaces availability; + private volatile VehicleParkingSpaces availability; /** * The vehicle parking group this parking belongs to. */ @@ -254,9 +254,13 @@ public boolean hasRealTimeDataForMode( /** * The only mutable method in this class: it allows to update the available parking spaces during * real-time updates. + * Since the entity is used both by writer threads (real-time updates) and reader threads + * (A* routing), the update process is synchronized. */ public void updateAvailability(VehicleParkingSpaces vehicleParkingSpaces) { - this.availability = vehicleParkingSpaces; + synchronized (this) { + this.availability = vehicleParkingSpaces; + } } @Override diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java index 2a5de121893..31074aafe38 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingAvailabilityUpdater.java @@ -79,7 +79,7 @@ public void run(Graph graph, TransitModel ignored) { private void handleUpdate(AvailabiltyUpdate update) { if (!parkingById.containsKey(update.vehicleParkingId())) { - LOG.error( + LOG.warn( "Parking with id {} does not exist. Skipping availability update.", update.vehicleParkingId() ); From 2e71dd62db8d5d88578fd26e125610f481ebb702 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 12:50:20 +0200 Subject: [PATCH 129/148] Fix spelling --- doc-templates/VehicleParking.md | 2 +- docs/sandbox/VehicleParking.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc-templates/VehicleParking.md b/doc-templates/VehicleParking.md index e3fcf84dc6e..721cbc2657a 100644 --- a/doc-templates/VehicleParking.md +++ b/doc-templates/VehicleParking.md @@ -54,7 +54,7 @@ The SIRI-FM updater works slightly differently from the others in that it only u of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile -requires SIRI 2.1. +which requires SIRI 2.1. diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index 926a12b88a5..db057bd9dbd 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -379,7 +379,7 @@ The SIRI-FM updater works slightly differently from the others in that it only u of parking but does not create new lots in realtime. The data source must conform to the [Italian SIRI-FM](https://github.com/5Tsrl/siri-italian-profile) profile -requires SIRI 2.1. +which requires SIRI 2.1. From 81985cddc98f06abfdad7a72e08290570a9559b6 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 15:34:24 +0200 Subject: [PATCH 130/148] Don't synchronize anymore --- .../routing/vehicle_parking/VehicleParking.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index 9cf198daa68..7fc81f4d395 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -258,9 +258,7 @@ public boolean hasRealTimeDataForMode( * (A* routing), the update process is synchronized. */ public void updateAvailability(VehicleParkingSpaces vehicleParkingSpaces) { - synchronized (this) { - this.availability = vehicleParkingSpaces; - } + this.availability = vehicleParkingSpaces; } @Override From 93205da889567b4207994507fa829b753880f24d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 15:46:59 +0200 Subject: [PATCH 131/148] Use volatile in parking service and document its use --- .../routing/vehicle_parking/VehicleParking.java | 2 ++ .../routing/vehicle_parking/VehicleParkingService.java | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index 7fc81f4d395..e64dc92c890 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -96,6 +96,8 @@ public class VehicleParking implements Serializable { private final List entrances = new ArrayList<>(); /** * The currently available spaces at this vehicle parking. + *

+ * The volatile keyword is used to ensure safe publication by clearing CPU caches. */ private volatile VehicleParkingSpaces availability; /** diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java index 639066871be..b0c08a2309b 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParkingService.java @@ -21,13 +21,17 @@ public class VehicleParkingService implements Serializable { /** * To ensure that his is thread-safe, the set stored here should always be immutable. + *

+ * The volatile keyword is used to ensure safe publication by clearing CPU caches. */ - private Set vehicleParkings = Set.of(); + private volatile Set vehicleParkings = Set.of(); /** * To ensure that his is thread-safe, {@link ImmutableListMultimap} is used. + *

+ * The volatile keyword is used to ensure safe publication by clearing CPU caches. */ - private ImmutableListMultimap vehicleParkingGroups = ImmutableListMultimap.of(); + private volatile ImmutableListMultimap vehicleParkingGroups = ImmutableListMultimap.of(); /** * Does atomic update of {@link VehicleParking} and index of {@link VehicleParkingGroup} in this From 1935ba0ff4e39a33010dc3788c13827d00897a9b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 8 Aug 2024 17:24:10 +0200 Subject: [PATCH 132/148] Update docs --- .../opentripplanner/routing/vehicle_parking/VehicleParking.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java index e64dc92c890..098af909296 100644 --- a/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java +++ b/src/main/java/org/opentripplanner/routing/vehicle_parking/VehicleParking.java @@ -257,7 +257,7 @@ public boolean hasRealTimeDataForMode( * The only mutable method in this class: it allows to update the available parking spaces during * real-time updates. * Since the entity is used both by writer threads (real-time updates) and reader threads - * (A* routing), the update process is synchronized. + * (A* routing), the variable holding the information is marked as volatile. */ public void updateAvailability(VehicleParkingSpaces vehicleParkingSpaces) { this.availability = vehicleParkingSpaces; From bcdd0b4cc4a9f7d8c87dd1cadc4fbdc356a6f1d8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 04:47:17 +0000 Subject: [PATCH 133/148] Update dependency com.google.cloud:libraries-bom to v26.44.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 89fb83240d1..788bb0eeaea 100644 --- a/pom.xml +++ b/pom.xml @@ -546,7 +546,7 @@ com.google.cloud libraries-bom - 26.40.0 + 26.44.0 pom import From f0a2c6c39c29455edff0ca9dc10ab10acaed30df Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 9 Aug 2024 10:37:08 +0200 Subject: [PATCH 134/148] Improve regexes and test names --- .../ext/geocoder/EnglishNgramAnalyzerTest.java | 8 +++++++- .../org/opentripplanner/ext/geocoder/LuceneIndexTest.java | 2 +- .../ext/geocoder/EnglishNGramAnalyzer.java | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java index 77917399647..2c352e0f760 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/EnglishNgramAnalyzerTest.java @@ -135,7 +135,13 @@ void numberSuffixes(String input, String expected) { assertEquals(List.of(expected), result); } - public List tokenize(String text) { + @Test + void wordBoundary() { + var result = tokenize("1stst"); + assertEquals(List.of("1sts", "1stst", "stst"), result); + } + + private List tokenize(String text) { try (var analyzer = new EnglishNGramAnalyzer()) { List result; TokenStream tokenStream; diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index f3af08f29fe..910c5080331 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -316,7 +316,7 @@ void agenciesAndFeedPublisher() { "meridian av 148", } ) - void shortTokens(String query) { + void numericAdjectives(String query) { var names = index.queryStopClusters(query).map(c -> c.primary().name()).toList(); assertEquals( Stream.of(MERIDIAN_AVE, MERIDIAN_N2, MERIDIAN_N1).map(s -> s.getName().toString()).toList(), diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java index 922108427e8..a3ef8440a18 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java @@ -26,7 +26,7 @@ class EnglishNGramAnalyzer extends Analyzer { // matches one or more numbers followed by the English suffixes "st", "nd", "rd", "th" - private static final Pattern NUMBER_SUFFIX_PATTERN = Pattern.compile("(\\d+)[st|nd|rd|th]+"); + private static final Pattern NUMBER_SUFFIX_PATTERN = Pattern.compile("(\\d+)(st|nd|rd|th)\\b"); @Override protected TokenStreamComponents createComponents(String fieldName) { From 2f4b2d4702471e031fb7b07f494862088ef657b2 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Fri, 9 Aug 2024 10:09:00 +0000 Subject: [PATCH 135/148] Add changelog entry for #5979 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 64840e36790..c8f6a9f1800 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -53,6 +53,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Keep at least one result for min-transfers and each transit-group in itinerary-group-filter [#5919](https://github.com/opentripplanner/OpenTripPlanner/pull/5919) - Extract parking lots from NeTEx feeds [#5946](https://github.com/opentripplanner/OpenTripPlanner/pull/5946) - Filter routes and patterns by service date in GTFS GraphQL API [#5869](https://github.com/opentripplanner/OpenTripPlanner/pull/5869) +- SIRI-FM vehicle parking updates [#5979](https://github.com/opentripplanner/OpenTripPlanner/pull/5979) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.5.0 (2024-03-13) From bf02dae9470adb2e68d5088e3f93cfcfd300c09e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 9 Aug 2024 14:55:29 +0200 Subject: [PATCH 136/148] Apply review comments --- .../java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java | 2 ++ .../org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java index 910c5080331..de6e600037c 100644 --- a/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java @@ -310,6 +310,8 @@ void agenciesAndFeedPublisher() { "Meridian Ave N & N 148", "Meridian Ave N N 148", "Meridian Ave N 148", + "Meridian & 148 N", + "148 N & Meridian", "Meridian & N 148", "Meridian Ave 148", "Meridian Av 148", diff --git a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java index a3ef8440a18..17bf529a559 100644 --- a/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java +++ b/src/ext/java/org/opentripplanner/ext/geocoder/EnglishNGramAnalyzer.java @@ -25,7 +25,7 @@ */ class EnglishNGramAnalyzer extends Analyzer { - // matches one or more numbers followed by the English suffixes "st", "nd", "rd", "th" + // Matches one or more numbers followed by the English suffixes "st", "nd", "rd", "th" private static final Pattern NUMBER_SUFFIX_PATTERN = Pattern.compile("(\\d+)(st|nd|rd|th)\\b"); @Override From 957206fbabefbf97e9fd028001ab0fd0419597e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 10 Aug 2024 23:33:52 +0000 Subject: [PATCH 137/148] Update dependency graphql to v16.9.0 --- .../org/opentripplanner/apis/gtfs/generated/package.json | 2 +- .../org/opentripplanner/apis/gtfs/generated/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json index 6dd08bb8041..d6cbf02d5e7 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json @@ -14,6 +14,6 @@ "@graphql-codegen/cli": "5.0.2", "@graphql-codegen/java": "4.0.1", "@graphql-codegen/java-resolvers": "3.0.0", - "graphql": "16.8.1" + "graphql": "16.9.0" } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock index 41bc851882b..25686abd94a 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock @@ -2181,10 +2181,10 @@ graphql-ws@^5.14.0: resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.14.0.tgz#766f249f3974fc2c48fae0d1fb20c2c4c79cd591" integrity sha512-itrUTQZP/TgswR4GSSYuwWUzrE/w5GhbwM2GX3ic2U7aw33jgEsayfIlvaj7/GcIvZgNMzsPTrE5hqPuFUiE5g== -graphql@16.8.1: - version "16.8.1" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" - integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== +graphql@16.9.0: + version "16.9.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.9.0.tgz#1c310e63f16a49ce1fbb230bd0a000e99f6f115f" + integrity sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw== has-flag@^3.0.0: version "3.0.0" From 2eb5842ca2bd4f4a40329f4e8542d48353377569 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 01:26:54 +0000 Subject: [PATCH 138/148] Update dependency com.graphql-java:graphql-java to v22.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 788bb0eeaea..96b43b871a3 100644 --- a/pom.xml +++ b/pom.xml @@ -859,7 +859,7 @@ com.graphql-java graphql-java - 22.1 + 22.2 com.graphql-java From 06144296887cb6456dadaf78fc6eb3272bf2d8c7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:47:48 +0000 Subject: [PATCH 139/148] Update google.dagger.version to v2.52 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 788bb0eeaea..e2c973107b8 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 156 31.3 - 2.51.1 + 2.52 2.17.2 3.1.8 5.10.3 From 15e960cdb909d1a8a8fe181226cdcfddec420244 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 Aug 2024 23:00:30 +0000 Subject: [PATCH 140/148] Update Debug UI dependencies (non-major) --- client/package-lock.json | 24 ++++++++++++------------ client/package.json | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 87139a1a909..09cdf3b18a2 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,7 +12,7 @@ "bootstrap": "5.3.3", "graphql": "16.9.0", "graphql-request": "7.1.0", - "maplibre-gl": "4.5.1", + "maplibre-gl": "4.5.2", "react": "18.3.1", "react-bootstrap": "2.10.4", "react-dom": "18.3.1", @@ -40,7 +40,7 @@ "jsdom": "24.1.1", "prettier": "3.3.3", "typescript": "5.5.4", - "vite": "5.4.0", + "vite": "5.4.1", "vitest": "2.0.5" } }, @@ -8727,9 +8727,9 @@ } }, "node_modules/maplibre-gl": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.5.1.tgz", - "integrity": "sha512-pKFDK8ZU2atwZWC8gdPVhN7Bf5HIPgtA+IG/iQ7J6WgmqSwCSmylc5q3stahWqXfx9PYUwVNJITrp1Hw96SUiA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.5.2.tgz", + "integrity": "sha512-vlWL9EY2bSGg5FAt0mKPfYqlfX15uLW5D3kKv4Xjn54nIVn01MKdfUJMAVIr+8fXVqfSX6c095Iy5XnV+T76kQ==", "license": "BSD-3-Clause", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", @@ -9543,9 +9543,9 @@ } }, "node_modules/postcss": { - "version": "8.4.40", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", - "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "dev": true, "funding": [ { @@ -11400,14 +11400,14 @@ } }, "node_modules/vite": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz", - "integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.1.tgz", + "integrity": "sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.40", + "postcss": "^8.4.41", "rollup": "^4.13.0" }, "bin": { diff --git a/client/package.json b/client/package.json index 74b4ae8f0a2..2dcbd623562 100644 --- a/client/package.json +++ b/client/package.json @@ -21,7 +21,7 @@ "bootstrap": "5.3.3", "graphql": "16.9.0", "graphql-request": "7.1.0", - "maplibre-gl": "4.5.1", + "maplibre-gl": "4.5.2", "react": "18.3.1", "react-bootstrap": "2.10.4", "react-dom": "18.3.1", @@ -49,7 +49,7 @@ "jsdom": "24.1.1", "prettier": "3.3.3", "typescript": "5.5.4", - "vite": "5.4.0", + "vite": "5.4.1", "vitest": "2.0.5" } } From 5b6fa5ab9063705f83d6c9da724ffa9fcb3e8ded Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 Aug 2024 23:00:36 +0000 Subject: [PATCH 141/148] Update Test dependencies --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 252bf20ec17..f5a7972ab8e 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ 2.52 2.17.2 3.1.8 - 5.10.3 + 5.11.0 1.13.2 5.6.0 1.5.6 @@ -694,7 +694,7 @@ com.google.truth truth - 1.4.2 + 1.4.4 test @@ -923,7 +923,7 @@ org.apache.commons commons-compress - 1.26.2 + 1.27.0 test From c3d1843063cd251d5bbfd0d8809d30cb2efd6d9d Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Sat, 17 Aug 2024 06:10:17 +0000 Subject: [PATCH 142/148] Upgrade debug client to version 2024/08/2024-08-17T06:09 --- src/client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/index.html b/src/client/index.html index 2d82a433279..84b69dd08b6 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +

From ff838eb6ea404958a336e1beda0cd67abd195311 Mon Sep 17 00:00:00 2001 From: a-limyr Date: Mon, 19 Aug 2024 13:40:00 +0200 Subject: [PATCH 143/148] Make itinerary list more compact --- .../src/components/ItineraryList/ItineraryLegDetails.tsx | 9 +++++---- client/src/style.css | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/client/src/components/ItineraryList/ItineraryLegDetails.tsx b/client/src/components/ItineraryList/ItineraryLegDetails.tsx index 56fdf430388..488ff34f197 100644 --- a/client/src/components/ItineraryList/ItineraryLegDetails.tsx +++ b/client/src/components/ItineraryList/ItineraryLegDetails.tsx @@ -10,14 +10,14 @@ export function ItineraryLegDetails({ leg, isLast }: { leg: Leg; isLast: boolean
{formatDistance(leg.distance)}, {formatDuration(leg.duration)}
-
+ - -
+
{leg.mode}{' '} {leg.line && ( @@ -28,10 +28,11 @@ export function ItineraryLegDetails({ leg, isLast }: { leg: Leg; isLast: boolean , {leg.authority?.name} )}{' '} -
+ {leg.mode !== Mode.Foot && ( <> - {leg.fromPlace.name} →{' '} +
+ {leg.fromPlace.name} →{' '} )}{' '} {!isLast && {leg.toPlace.name}} diff --git a/client/src/style.css b/client/src/style.css index 7dd2565c449..1a24ac2c072 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -86,7 +86,7 @@ } .itinerary-leg-details .mode { - margin-top: 10px; + margin-top: 2px; } .itinerary-header-itinerary-number { From eb94184d2112867a78473531058807b636b2a567 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Mon, 19 Aug 2024 14:15:50 +0000 Subject: [PATCH 144/148] Add changelog entry for #5994 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index c8f6a9f1800..c6f16da2f0e 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -54,6 +54,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Extract parking lots from NeTEx feeds [#5946](https://github.com/opentripplanner/OpenTripPlanner/pull/5946) - Filter routes and patterns by service date in GTFS GraphQL API [#5869](https://github.com/opentripplanner/OpenTripPlanner/pull/5869) - SIRI-FM vehicle parking updates [#5979](https://github.com/opentripplanner/OpenTripPlanner/pull/5979) +- Take realtime patterns into account when storing realtime vehicles [#5994](https://github.com/opentripplanner/OpenTripPlanner/pull/5994) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.5.0 (2024-03-13) From 5137512d402b49411a30c71a0b4b842480a62012 Mon Sep 17 00:00:00 2001 From: a-limyr Date: Tue, 20 Aug 2024 09:16:13 +0200 Subject: [PATCH 145/148] Make itinerary list more compact --- .../ItineraryList/ItineraryLegDetails.tsx | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/client/src/components/ItineraryList/ItineraryLegDetails.tsx b/client/src/components/ItineraryList/ItineraryLegDetails.tsx index 488ff34f197..e75813a4a45 100644 --- a/client/src/components/ItineraryList/ItineraryLegDetails.tsx +++ b/client/src/components/ItineraryList/ItineraryLegDetails.tsx @@ -10,14 +10,9 @@ export function ItineraryLegDetails({ leg, isLast }: { leg: Leg; isLast: boolean
{formatDistance(leg.distance)}, {formatDuration(leg.duration)}
- - - - - + + -{' '} +
{leg.mode}{' '} {leg.line && ( @@ -28,11 +23,10 @@ export function ItineraryLegDetails({ leg, isLast }: { leg: Leg; isLast: boolean , {leg.authority?.name} )}{' '} - {leg.mode !== Mode.Foot && ( <> -
- {leg.fromPlace.name} →{' '} +
+ {leg.fromPlace.name} →{' '} )}{' '} {!isLast && {leg.toPlace.name}} From f6d702df83df58b308091a7b75cf3c2839975108 Mon Sep 17 00:00:00 2001 From: OTP Bot Date: Thu, 22 Aug 2024 08:36:25 +0000 Subject: [PATCH 146/148] Upgrade debug client to version 2024/08/2024-08-22T08:35 --- src/client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/index.html b/src/client/index.html index 84b69dd08b6..42b2a26d059 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -5,8 +5,8 @@ OTP Debug Client - - + +
From d93d231dde002059c6d64dd24acf722f8dc4e8e5 Mon Sep 17 00:00:00 2001 From: OTP Changelog Bot Date: Thu, 22 Aug 2024 08:36:40 +0000 Subject: [PATCH 147/148] Add changelog entry for #6012 [ci skip] --- docs/Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index c6f16da2f0e..453e2cc6089 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -55,6 +55,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle - Filter routes and patterns by service date in GTFS GraphQL API [#5869](https://github.com/opentripplanner/OpenTripPlanner/pull/5869) - SIRI-FM vehicle parking updates [#5979](https://github.com/opentripplanner/OpenTripPlanner/pull/5979) - Take realtime patterns into account when storing realtime vehicles [#5994](https://github.com/opentripplanner/OpenTripPlanner/pull/5994) +- Debug client itinerary list style improvements [#6012](https://github.com/opentripplanner/OpenTripPlanner/pull/6012) [](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE) ## 2.5.0 (2024-03-13) From 8164d41b4f9ad7e5c946dfeec4e9dd567b38e6f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 22:34:42 +0000 Subject: [PATCH 148/148] Update dependency org.apache.maven.plugins:maven-gpg-plugin to v3.2.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f5a7972ab8e..581989f7ffa 100644 --- a/pom.xml +++ b/pom.xml @@ -1000,7 +1000,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.4 + 3.2.5 sign-artifacts