From faf62059e5a7e84d884d1680da28cf8a85ea6925 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 | 15 ++- ...RouteRequestTransitDataProviderFilter.java | 10 ++ .../request/TransitDataProviderFilter.java | 2 + .../request/request/filter/SelectRequest.java | 41 ++++-- .../request/request/filter/TransitFilter.java | 4 + .../request/filter/TransitFilterRequest.java | 29 +++- .../routing/algorithm/FilterTest.java | 2 + ...rRoutingRequestTransitDataCreatorTest.java | 6 + ...eRequestTransitDataProviderFilterTest.java | 125 +++++++++++++++--- 10 files changed, 206 insertions(+), 33 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..3561200ce0a 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 @@ -176,23 +176,34 @@ private static List filterActiveTripPatterns( boolean firstDay, TransitDataProviderFilter filter ) { + System.out.println("filter active trip patterns"); + // On the first search day we want to add both TripPatternsForDate objects that start that day // and any previous day, while on subsequent search days we only want to add the // 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()); + + System.out.println("tripPatternsForDate: {}" + tripPatternsForDate.size()); + for (TripPatternForDate p : tripPatternsForDate) { if (firstDay || p.getStartOfRunningPeriod().equals(date)) { + System.out.println("*"); 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..b5c2cef371d 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(); @@ -85,6 +93,7 @@ public static BikeAccess bikeAccessForTrip(Trip trip) { @Override public boolean tripPatternPredicate(TripPatternForDate tripPatternForDate) { + System.out.println("tripPatternPredicate"); for (TransitFilter filter : filters) { if (filter.matchTripPattern(tripPatternForDate.getTripPattern().getPattern())) { return true; @@ -95,6 +104,7 @@ public boolean tripPatternPredicate(TripPatternForDate tripPatternForDate) { @Override public boolean tripTimesPredicate(TripTimes tripTimes, boolean withFilters) { + System.out.println("tripTimesPredicate"); final Trip trip = tripTimes.getTrip(); if (requireBikesAllowed) { 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..17d7f390a9d 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,14 @@ 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 +85,12 @@ public boolean matches(TripPattern tripPattern) { public boolean matchesSelect(TripTimes tripTimes) { var trip = tripTimes.getTrip(); - return ( - this.transportModeFilter == null || - this.transportModeFilter.match(trip.getMode(), trip.getNetexSubMode()) - ); + 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()); + + return matchesTrip && matchesTransportMode; } /** @@ -88,10 +98,13 @@ public boolean matchesSelect(TripTimes tripTimes) { */ public boolean matchesNot(TripTimes tripTimes) { var trip = tripTimes.getTrip(); - return ( - this.transportModeFilter != null && - this.transportModeFilter.match(trip.getMode(), trip.getNetexSubMode()) - ); + + 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()); + + return matchesTrip || matchesTransportMode; } @Override @@ -120,6 +133,10 @@ public List routes() { return routes; } + public List getTrips() { + return trips; + } + private String transportModesToString() { if (transportModes == null) { return null; @@ -146,6 +163,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 +204,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..c7088739553 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,33 @@ 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 +97,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/FilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java index de40617d3d3..cba5d40e89b 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/FilterTest.java @@ -769,4 +769,6 @@ public void testGroupOfLinesExcludeFunctionality() { assertEquals(1, bannedPatterns.size()); assertTrue(bannedPatterns.contains(id(JOURNEY_PATTERN_ID_1))); } + + } 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..4ec0c99b525 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, @@ -152,5 +157,6 @@ public BitSet filterAvailableStops( ) { return boardingPossible; } + } } 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..a781416fa31 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,89 @@ 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 +976,7 @@ private RealTimeTripTimes createTestTripTimes( private TripTimes createTestTripTimesWithSubmode(String submode) { return createTestTripTimes( - TRIP_ID, + TRIP_ID_1, ROUTE, BikeAccess.NOT_ALLOWED, TransitMode.BUS,