From 33a74fae8b3e1be5b94561baee681a9cafcb5941 Mon Sep 17 00:00:00 2001 From: bartosz Date: Wed, 24 Jan 2024 12:04:18 +0100 Subject: [PATCH] Add implementation for Service Journey filter --- .../mapping/SelectRequestMapper.java | 5 + ...aptorRoutingRequestTransitDataCreator.java | 10 +- ...RouteRequestTransitDataProviderFilter.java | 8 ++ .../request/TransitDataProviderFilter.java | 2 + .../request/request/filter/SelectRequest.java | 46 ++++++- .../request/request/filter/TransitFilter.java | 4 + .../request/filter/TransitFilterRequest.java | 23 +++- ...rRoutingRequestTransitDataCreatorTest.java | 5 + ...eRequestTransitDataProviderFilterTest.java | 124 +++++++++++++++--- 9 files changed, 196 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/SelectRequestMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/SelectRequestMapper.java index 3d5de9bf9d0..86d09082b2e 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/SelectRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/SelectRequestMapper.java @@ -32,6 +32,11 @@ static SelectRequest mapSelectRequest(Map> input) { selectRequestBuilder.withGroupOfRoutes(mapIDsToDomainNullSafe(groupOfLines)); } + if (input.containsKey("serviceJourneys")) { + var serviceJourneys = (List) input.get("serviceJourneys"); + selectRequestBuilder.withTrips(mapIDsToDomainNullSafe(serviceJourneys)); + } + if (input.containsKey("transportModes")) { var tModes = new ArrayList(); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java index b8f915d6eb4..ee094d1d4cd 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreator.java @@ -181,18 +181,24 @@ private static List filterActiveTripPatterns( // TripPatternForDate objects that start on that particular day. This is to prevent duplicates. // This was previously a stream, but was unrolled for improved performance. + var hasTripFilters = filter.hasTripFilters(); Predicate tripTimesWithSubmodesPredicate = tripTimes -> - filter.tripTimesPredicate(tripTimes, filter.hasSubModeFilters()); + filter.tripTimesPredicate(tripTimes, filter.hasSubModeFilters() || hasTripFilters); + Predicate tripTimesWithoutSubmodesPredicate = tripTimes -> filter.tripTimesPredicate(tripTimes, false); + Collection tripPatternsForDate = transitLayer.getTripPatternsForDate(date); List result = new ArrayList<>(tripPatternsForDate.size()); + for (TripPatternForDate p : tripPatternsForDate) { if (firstDay || p.getStartOfRunningPeriod().equals(date)) { if (filter.tripPatternPredicate(p)) { - var tripTimesPredicate = p.getTripPattern().getPattern().getContainsMultipleModes() + var tripTimesPredicate = hasTripFilters || + p.getTripPattern().getPattern().getContainsMultipleModes() ? tripTimesWithSubmodesPredicate : tripTimesWithoutSubmodesPredicate; + TripPatternForDate tripPatternForDate = p.newWithFilteredTripTimes(tripTimesPredicate); if (tripPatternForDate != null) { result.add(tripPatternForDate); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilter.java index 44d9f3cdb3d..4c4791120e9 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilter.java @@ -38,6 +38,8 @@ public class RouteRequestTransitDataProviderFilter implements TransitDataProvide private final boolean hasSubModeFilters; + private final boolean hasTripFilters; + public RouteRequestTransitDataProviderFilter(RouteRequest request) { this( request.journey().transfer().mode() == StreetMode.BIKE, @@ -68,6 +70,7 @@ public RouteRequestTransitDataProviderFilter( this.bannedTrips = bannedTrips; this.filters = filters.toArray(TransitFilter[]::new); this.hasSubModeFilters = filters.stream().anyMatch(TransitFilter::isSubModePredicate); + this.hasTripFilters = filters.stream().anyMatch(TransitFilter::isTripPredicate); } @Override @@ -75,6 +78,11 @@ public boolean hasSubModeFilters() { return hasSubModeFilters; } + @Override + public boolean hasTripFilters() { + return hasTripFilters; + } + public static BikeAccess bikeAccessForTrip(Trip trip) { if (trip.getBikesAllowed() != BikeAccess.UNKNOWN) { return trip.getBikesAllowed(); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TransitDataProviderFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TransitDataProviderFilter.java index 76a1269013c..9413e593ad1 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TransitDataProviderFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/TransitDataProviderFilter.java @@ -20,6 +20,8 @@ public interface TransitDataProviderFilter { boolean hasSubModeFilters(); + boolean hasTripFilters(); + boolean tripTimesPredicate(TripTimes tripTimes, boolean withFilters); /** diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/filter/SelectRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/filter/SelectRequest.java index a1610ed8947..d8f612ca205 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/request/filter/SelectRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/request/filter/SelectRequest.java @@ -22,6 +22,7 @@ public static Builder of() { private final List agencies; private final List groupOfRoutes; private final List routes; + private final List trips; public SelectRequest(Builder builder) { if (builder.transportModes.isEmpty()) { @@ -35,7 +36,17 @@ public SelectRequest(Builder builder) { this.agencies = List.copyOf(builder.agencies); this.groupOfRoutes = List.copyOf(builder.groupOfRoutes); - this.routes = builder.routes; + this.routes = List.copyOf(builder.routes); + this.trips = List.copyOf(builder.trips); + } + + public boolean isTripsOnly() { + return ( + this.transportModeFilter == null && + this.agencies.isEmpty() && + this.groupOfRoutes.isEmpty() && + this.routes.isEmpty() + ); } public boolean matches(TripPattern tripPattern) { @@ -77,10 +88,15 @@ public boolean matches(TripPattern tripPattern) { public boolean matchesSelect(TripTimes tripTimes) { var trip = tripTimes.getTrip(); - return ( + var tripFilter = this.getTrips(); + + var matchesTrip = + (tripFilter == null || tripFilter.isEmpty()) || tripFilter.contains(trip.getId()); + var matchesTransportMode = this.transportModeFilter == null || - this.transportModeFilter.match(trip.getMode(), trip.getNetexSubMode()) - ); + this.transportModeFilter.match(trip.getMode(), trip.getNetexSubMode()); + + return matchesTrip && matchesTransportMode; } /** @@ -88,10 +104,16 @@ public boolean matchesSelect(TripTimes tripTimes) { */ public boolean matchesNot(TripTimes tripTimes) { var trip = tripTimes.getTrip(); - return ( + + var tripFilter = this.getTrips(); + + var matchesTrip = + (tripFilter != null && !tripFilter.isEmpty()) && tripFilter.contains(trip.getId()); + var matchesTransportMode = this.transportModeFilter != null && - this.transportModeFilter.match(trip.getMode(), trip.getNetexSubMode()) - ); + this.transportModeFilter.match(trip.getMode(), trip.getNetexSubMode()); + + return matchesTrip || matchesTransportMode; } @Override @@ -120,6 +142,10 @@ public List routes() { return routes; } + public List getTrips() { + return trips; + } + private String transportModesToString() { if (transportModes == null) { return null; @@ -146,6 +172,7 @@ public static class Builder { private List agencies = new ArrayList<>(); private List groupOfRoutes = new ArrayList<>(); private List routes = new ArrayList<>(); + private List trips = new ArrayList<>(); public Builder withTransportModes(List transportModes) { this.transportModes = transportModes; @@ -186,6 +213,11 @@ public Builder withGroupOfRoutes(List groupOfRoutes) { return this; } + public Builder withTrips(List trips) { + this.trips = trips; + return this; + } + public SelectRequest build() { return new SelectRequest(this); } diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitFilter.java b/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitFilter.java index 881c8d26b76..5688af48697 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitFilter.java +++ b/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitFilter.java @@ -17,4 +17,8 @@ public interface TransitFilter { default boolean isSubModePredicate() { return false; } + + default boolean isTripPredicate() { + return false; + } } diff --git a/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitFilterRequest.java b/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitFilterRequest.java index 38f79b82baa..9d248fcb737 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitFilterRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/request/filter/TransitFilterRequest.java @@ -57,9 +57,27 @@ public boolean isSubModePredicate() { return false; } + @Override + public boolean isTripPredicate() { + for (var selectRequest : select) { + if (selectRequest.getTrips() != null && !selectRequest.getTrips().isEmpty()) { + return true; + } + } + + for (var selectRequest : not) { + if (selectRequest.getTrips() != null && !selectRequest.getTrips().isEmpty()) { + return true; + } + } + return false; + } + @Override public boolean matchTripPattern(TripPattern tripPattern) { - if (select.length != 0) { + var tripPatternSelect = Arrays.stream(select).filter(s -> !s.isTripsOnly()).toArray(); + + if (tripPatternSelect.length != 0) { var anyMatch = false; for (SelectRequest s : select) { if (s.matches(tripPattern)) { @@ -73,7 +91,8 @@ public boolean matchTripPattern(TripPattern tripPattern) { } for (SelectRequest s : not) { - if (s.matches(tripPattern)) { + // We'll filter trips in the next step + if (!s.isTripsOnly() && s.matches(tripPattern)) { return false; } } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java index ed71b3de400..a4733a0e9f7 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitDataCreatorTest.java @@ -144,6 +144,11 @@ public boolean hasSubModeFilters() { return false; } + @Override + public boolean hasTripFilters() { + return false; + } + @Override public BitSet filterAvailableStops( RoutingTripPattern tripPattern, diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java index 4823ea84300..1588df9de63 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RouteRequestTransitDataProviderFilterTest.java @@ -53,7 +53,9 @@ class RouteRequestTransitDataProviderFilterTest { private static final Route ROUTE = TransitModelForTest.route("1").build(); - private static final FeedScopedId TRIP_ID = TransitModelForTest.id("T1"); + private static final FeedScopedId TRIP_ID_1 = TransitModelForTest.id("T1"); + + private static final FeedScopedId TRIP_ID_2 = TransitModelForTest.id("T2"); private static final RegularStop STOP_FOR_TEST = TEST_MODEL.stop("TEST:STOP", 0, 0).build(); @@ -241,7 +243,7 @@ void bannedRouteFilteringTest() { @Test void bannedTripFilteringTest() { TripTimes tripTimes = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS, @@ -256,7 +258,7 @@ void bannedTripFilteringTest() { DEFAULT_ACCESSIBILITY, false, false, - Set.of(TRIP_ID), + Set.of(TRIP_ID_1), filterForMode(TransitMode.BUS) ); @@ -381,7 +383,7 @@ void matchSelectedAgencyExcludedSubMode() { @Test void transitModeFilteringTest() { TripTimes tripTimes = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS, @@ -407,7 +409,7 @@ void transitModeFilteringTest() { @Test void notFilteringExpectedTripTimesTest() { TripTimes tripTimes = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS, @@ -434,7 +436,7 @@ void notFilteringExpectedTripTimesTest() { @Test void bikesAllowedFilteringTest() { TripTimes tripTimes = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS, @@ -461,7 +463,7 @@ void bikesAllowedFilteringTest() { @Test void removeInaccessibleTrip() { TripTimes tripTimes = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS, @@ -488,7 +490,7 @@ void removeInaccessibleTrip() { @Test void keepAccessibleTrip() { TripTimes wheelchairAccessibleTrip = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS, @@ -515,7 +517,7 @@ void keepAccessibleTrip() { @Test void keepRealTimeAccessibleTrip() { RealTimeTripTimes realTimeWheelchairAccessibleTrip = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS, @@ -544,7 +546,7 @@ void keepRealTimeAccessibleTrip() { @Test void includePlannedCancellationsTest() { TripTimes tripTimesWithCancellation = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS, @@ -553,7 +555,7 @@ void includePlannedCancellationsTest() { TripAlteration.CANCELLATION ); TripTimes tripTimesWithReplaced = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS, @@ -608,7 +610,7 @@ void includePlannedCancellationsTest() { @Test void includeRealtimeCancellationsTest() { TripTimes tripTimes = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS, @@ -618,7 +620,7 @@ void includeRealtimeCancellationsTest() { ); RealTimeTripTimes tripTimesWithCancellation = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS, @@ -710,7 +712,7 @@ void testBikesAllowed() { @Test void multipleFilteringTest() { TripTimes matchingTripTimes = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.ALLOWED, TransitMode.BUS, @@ -719,7 +721,7 @@ void multipleFilteringTest() { TripAlteration.PLANNED ); TripTimes failingTripTimes1 = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.ALLOWED, TransitMode.RAIL, @@ -728,7 +730,7 @@ void multipleFilteringTest() { TripAlteration.PLANNED ); TripTimes failingTripTimes2 = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.RAIL, @@ -737,7 +739,7 @@ void multipleFilteringTest() { TripAlteration.CANCELLATION ); TripTimes failingTripTimes3 = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.RAIL, @@ -746,7 +748,7 @@ void multipleFilteringTest() { TripAlteration.CANCELLATION ); TripTimes failingTripTimes4 = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.ALLOWED, TransitMode.BUS, @@ -755,7 +757,7 @@ void multipleFilteringTest() { TripAlteration.PLANNED ); TripTimes failingTripTimes5 = createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.ALLOWED, TransitMode.BUS, @@ -783,6 +785,88 @@ void multipleFilteringTest() { assertFalse(filter.tripTimesPredicate(failingTripTimes5, true)); } + @Test + public void selectTripFilteringTest() { + TripTimes tripTimes1 = createTestTripTimes( + TRIP_ID_1, + ROUTE, + BikeAccess.NOT_ALLOWED, + TransitMode.BUS, + null, + Accessibility.NOT_POSSIBLE, + null + ); + + TripTimes tripTimes2 = createTestTripTimes( + TRIP_ID_2, + ROUTE, + BikeAccess.NOT_ALLOWED, + TransitMode.BUS, + null, + Accessibility.NOT_POSSIBLE, + null + ); + + var filter = new RouteRequestTransitDataProviderFilter( + false, + false, + DEFAULT_ACCESSIBILITY, + false, + false, + Set.of(), + List.of( + TransitFilterRequest + .of() + .addSelect(SelectRequest.of().withTrips(List.of(TRIP_ID_1)).build()) + .build() + ) + ); + + assertTrue(filter.tripTimesPredicate(tripTimes1, true)); + assertFalse(filter.tripTimesPredicate(tripTimes2, true)); + } + + @Test + public void NotTripFilterTest() { + TripTimes tripTimes1 = createTestTripTimes( + TRIP_ID_1, + ROUTE, + BikeAccess.NOT_ALLOWED, + TransitMode.BUS, + null, + Accessibility.NOT_POSSIBLE, + null + ); + + TripTimes tripTimes2 = createTestTripTimes( + TRIP_ID_2, + ROUTE, + BikeAccess.NOT_ALLOWED, + TransitMode.BUS, + null, + Accessibility.NOT_POSSIBLE, + null + ); + + var filter = new RouteRequestTransitDataProviderFilter( + false, + false, + DEFAULT_ACCESSIBILITY, + false, + false, + Set.of(), + List.of( + TransitFilterRequest + .of() + .addNot(SelectRequest.of().withTrips(List.of(TRIP_ID_1)).build()) + .build() + ) + ); + + assertFalse(filter.tripTimesPredicate(tripTimes1, true)); + assertTrue(filter.tripTimesPredicate(tripTimes2, true)); + } + private boolean validateModesOnTripTimes( Collection allowedModes, TripTimes tripTimes @@ -891,7 +975,7 @@ private RealTimeTripTimes createTestTripTimes( private TripTimes createTestTripTimesWithSubmode(String submode) { return createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS,