diff --git a/src/main/java/org/opentripplanner/model/plan/SortOrder.java b/src/main/java/org/opentripplanner/model/plan/SortOrder.java index 342f963ba00..543460ac72d 100644 --- a/src/main/java/org/opentripplanner/model/plan/SortOrder.java +++ b/src/main/java/org/opentripplanner/model/plan/SortOrder.java @@ -42,4 +42,16 @@ public enum SortOrder { public boolean isSortedByAscendingArrivalTime() { return this == STREET_AND_ARRIVAL_TIME; } + + /** + * The itineraries are sorted with by departure time with the latest departure time first. When + * paging we need to know which end of the list of itineraries we should crop. This method is used + * to decide that together with the current page type (next/previous). + *

+ * This returns {@code false} for the default depart-after search, and {@code true} for an + * arrive-by search. + */ + public boolean isSortedByDescendingDepartureTime() { + return this == STREET_AND_DEPARTURE_TIME; + } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java index 764ad69b59e..6d3d17d8299 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -473,7 +473,10 @@ public ItineraryListFilterChain build() { } if (earliestDepartureTime != null && filterDirectFlexByEarliestDeparture) { - addRemoveFilter(filters, new FlexSearchWindowFilter(earliestDepartureTime)); + addRemoveFilter( + filters, + new FlexSearchWindowFilter(earliestDepartureTime, searchWindow, sortOrder) + ); } // Remove itineraries present in the page retrieved before this page/search. diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilter.java index 47ca0e730e7..030bdfa7b78 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilter.java @@ -1,8 +1,10 @@ package org.opentripplanner.routing.algorithm.filterchain.filters.system; +import java.time.Duration; import java.time.Instant; import java.util.function.Predicate; import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.SortOrder; import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** @@ -18,9 +20,17 @@ public class FlexSearchWindowFilter implements RemoveItineraryFlagger { public static final String TAG = "outside-flex-window"; private final Instant earliestDepartureTime; - - public FlexSearchWindowFilter(Instant earliestDepartureTime) { + private final Instant latestArrivalTime; + private final SortOrder sortOrder; + + public FlexSearchWindowFilter( + Instant earliestDepartureTime, + Duration searchWindow, + SortOrder sortOrder + ) { this.earliestDepartureTime = earliestDepartureTime; + this.latestArrivalTime = earliestDepartureTime.plus(searchWindow); + this.sortOrder = sortOrder; } @Override @@ -31,9 +41,12 @@ public String name() { @Override public Predicate shouldBeFlaggedForRemoval() { return it -> { - if (it.isDirectFlex()) { + if (it.isDirectFlex() && sortOrder.isSortedByDescendingDepartureTime()) { var time = it.startTime().toInstant(); return time.isBefore(earliestDepartureTime); + } else if (it.isDirectFlex() && sortOrder.isSortedByAscendingArrivalTime()) { + var time = it.startTime().toInstant(); + return time.isAfter(latestArrivalTime); } else { return false; } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilterTest.java index 65f18405ea6..30c8872b859 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilterTest.java @@ -1,14 +1,17 @@ package org.opentripplanner.routing.algorithm.filterchain.filters.system; import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.opentripplanner.framework.time.TimeUtils.time; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; +import java.time.Duration; import java.time.Instant; import java.util.List; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.model.plan.SortOrder; import org.opentripplanner.model.plan.TestItineraryBuilder; class FlexSearchWindowFilterTest implements PlanTestConstants { @@ -19,9 +22,13 @@ class FlexSearchWindowFilterTest implements PlanTestConstants { @ParameterizedTest @ValueSource(strings = { "09:20", "09:21", "13:20" }) - void keepFlexItinerariesAfterLDT(String startTime) { + void keepArriveByFlexItinerariesAfterEDT(String startTime) { var edt = "9:20"; - var subject = new FlexSearchWindowFilter(TestItineraryBuilder.newTime(time(edt)).toInstant()); + var subject = new FlexSearchWindowFilter( + TestItineraryBuilder.newTime(time(edt)).toInstant(), + Duration.ofMinutes(30), + SortOrder.STREET_AND_DEPARTURE_TIME + ); var itin = newItinerary(A, time(startTime)) .flex(T11_00, T11_30, B) @@ -33,8 +40,12 @@ void keepFlexItinerariesAfterLDT(String startTime) { @ParameterizedTest @ValueSource(strings = { "00:00", "00:01", "09:19" }) - void removeFlexItinerariesBeforeLDT(String startTime) { - var subject = new FlexSearchWindowFilter(LATEST_DEPARTURE_TIME); + void removeArriveByFlexItinerariesBeforeEDT(String startTime) { + var subject = new FlexSearchWindowFilter( + LATEST_DEPARTURE_TIME, + Duration.ofMinutes(30), + SortOrder.STREET_AND_DEPARTURE_TIME + ); var itin = newItinerary(A, time(startTime)) .flex(T11_00, T11_30, B) @@ -43,4 +54,21 @@ void removeFlexItinerariesBeforeLDT(String startTime) { assertThat(subject.flagForRemoval(List.of(itin))).isEmpty(); } + + @ParameterizedTest + @ValueSource(strings = { "12:00" }) + void removeDepartAtFlexItinerariesAfterLAT(String startTime) { + var subject = new FlexSearchWindowFilter( + LATEST_DEPARTURE_TIME, + Duration.ofMinutes(30), + SortOrder.STREET_AND_ARRIVAL_TIME + ); + + var itin = newItinerary(A, time(startTime)) + .flex(T11_00, T11_30, B) + .withIsSearchWindowAware(false) + .build(); + + assertEquals(subject.flagForRemoval(List.of(itin)), List.of(itin)); + } }