From c35c98be29d6b48e8c69798ac89f2ba4e3a57cbf Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 17 Sep 2024 16:37:27 +0200 Subject: [PATCH 01/35] Make it explicit which itineraries are timeWindowAware --- .../ext/emissions/EmissionsTest.java | 16 +++++++---- .../ext/fares/impl/OrcaFareServiceTest.java | 2 +- .../opentripplanner/model/plan/Itinerary.java | 10 +++++-- .../mapping/GraphPathToItineraryMapper.java | 2 +- .../mapping/RaptorPathToItineraryMapper.java | 5 ++-- .../model/plan/TestItineraryBuilder.java | 8 +++++- .../stoptimes/AlternativeLegsTest.java | 28 +++++++++++-------- 7 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index ff8a65ab494..e7cdb22d70a 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -63,35 +63,38 @@ static void SetUp() { @Test void testGetEmissionsForItinerary() { - Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS))); + Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS)), true); decorateWithEmission.decorate(i); assertEquals(new Grams(2223.902), i.getEmissionsPerPerson().getCo2()); } @Test void testGetEmissionsForCarRoute() { - Itinerary i = new Itinerary(List.of(STREET_LEG)); + Itinerary i = new Itinerary(List.of(STREET_LEG), true); decorateWithEmission.decorate(i); assertEquals(new Grams(28.0864), i.getEmissionsPerPerson().getCo2()); } @Test void testNoEmissionsForFeedWithoutEmissionsConfigured() { - Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED))); + Itinerary i = new Itinerary( + List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED)), + true + ); decorateWithEmission.decorate(i); assertNull(i.getEmissionsPerPerson()); } @Test void testZeroEmissionsForItineraryWithZeroEmissions() { - Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_ZERO_EMISSIONS))); + Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_ZERO_EMISSIONS)), true); decorateWithEmission.decorate(i); assertEquals(new Grams(0.0), i.getEmissionsPerPerson().getCo2()); } @Test void testGetEmissionsForCombinedRoute() { - Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS), STREET_LEG)); + Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS), STREET_LEG), true); decorateWithEmission.decorate(i); assertEquals(new Grams(2251.9884), i.getEmissionsPerPerson().getCo2()); } @@ -99,7 +102,8 @@ void testGetEmissionsForCombinedRoute() { @Test void testNoEmissionsForCombinedRouteWithoutTransitEmissions() { Itinerary i = new Itinerary( - List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED), STREET_LEG) + List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED), STREET_LEG), + true ); decorateWithEmission.decorate(i); var emissionsResult = i.getEmissionsPerPerson() != null diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java index e7a2610b42f..e1157aa7bac 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java @@ -79,7 +79,7 @@ public static void setUpClass() { * types. */ private static void calculateFare(List legs, FareType fareType, Money expectedPrice) { - var itinerary = new Itinerary(legs); + var itinerary = new Itinerary(legs, true); var itineraryFares = orcaFareService.calculateFares(itinerary); assertEquals( expectedPrice, diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index dee80addd91..388f8439965 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -64,12 +64,14 @@ public class Itinerary implements ItinerarySortKey { /* other properties */ private final List systemNotices = new ArrayList<>(); + private final boolean isSearchWindowAware; private List legs; private ItineraryFares fare = ItineraryFares.empty(); - public Itinerary(List legs) { + public Itinerary(List legs, boolean isSearchWindowAware) { setLegs(legs); + this.isSearchWindowAware = isSearchWindowAware; // Set aggregated data ItinerariesCalculateLegTotals totals = new ItinerariesCalculateLegTotals(legs); @@ -169,6 +171,10 @@ public boolean hasTransit() { .anyMatch(l -> l instanceof ScheduledTransitLeg || l instanceof FlexibleTransitLeg); } + public boolean isSearchWindowAware() { + return isSearchWindowAware; + } + public Leg firstLeg() { return getLegs().get(0); } @@ -215,7 +221,7 @@ public Itinerary withTimeShiftToStartAt(ZonedDateTime afterTime) { .stream() .map(leg -> leg.withTimeShift(duration)) .collect(Collectors.toList()); - var newItin = new Itinerary(timeShiftedLegs); + var newItin = new Itinerary(timeShiftedLegs, isSearchWindowAware); newItin.setGeneralizedCost(getGeneralizedCost()); return newItin; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java index 11302098f25..9abdc75d2b8 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java @@ -134,7 +134,7 @@ public Itinerary generateItinerary(GraphPath path) { } } - Itinerary itinerary = new Itinerary(legs); + Itinerary itinerary = new Itinerary(legs, false); calculateElevations(itinerary, path.edges); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java index 155394376ae..1907b8eb0a1 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java @@ -140,7 +140,7 @@ else if (pathLeg.isTransferLeg()) { Itinerary mapped = mapEgressLeg(egressPathLeg); legs.addAll(mapped == null ? List.of() : mapped.getLegs()); - Itinerary itinerary = new Itinerary(legs); + Itinerary itinerary = new Itinerary(legs, true); // Map general itinerary fields itinerary.setArrivedAtDestinationWithRentedVehicle( @@ -399,7 +399,8 @@ private Itinerary mapDirectPath(RaptorPath path) { createZonedDateTime(path.endTime()), path.numberOfTransfers() ) - ) + ), + true ); } diff --git a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index edaafabd753..e7be78badfe 100644 --- a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java +++ b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java @@ -73,6 +73,7 @@ public class TestItineraryBuilder implements PlanTestConstants { private int lastEndTime; private int c1 = 0; private int c2 = NOT_SET; + private boolean isSearchWindowAware = true; private TestItineraryBuilder(Place origin, int startTime) { this.lastPlace = origin; @@ -398,6 +399,11 @@ public TestItineraryBuilder withGeneralizedCost2(int c2) { return this; } + public TestItineraryBuilder withIsSearchWindowAware(boolean searchWindowAware) { + this.isSearchWindowAware = searchWindowAware; + return this; + } + public Itinerary egress(int walkDuration) { walk(walkDuration, null); return build(); @@ -413,7 +419,7 @@ public Itinerary build(int c1) { } public Itinerary build() { - Itinerary itinerary = new Itinerary(legs); + Itinerary itinerary = new Itinerary(legs, isSearchWindowAware); itinerary.setGeneralizedCost(c1); if (c2 != NOT_SET) { itinerary.setGeneralizedCost2(c2); diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java index fde87776909..deced3a5c4d 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java @@ -55,9 +55,7 @@ void testPreviousLegs() { AlternativeLegsFilter.NO_FILTER ); - var legs = Itinerary.toStr( - alternativeLegs.stream().map(Leg.class::cast).map(List::of).map(Itinerary::new).toList() - ); + var legs = getStr(alternativeLegs); var expected = "B ~ BUS 2 0:20 0:30 ~ C [C₁-1], " + @@ -68,6 +66,17 @@ void testPreviousLegs() { assertEquals(expected, legs); } + private static String getStr(List alternativeLegs) { + return Itinerary.toStr( + alternativeLegs + .stream() + .map(Leg.class::cast) + .map(List::of) + .map(l -> new Itinerary(l, true)) + .toList() + ); + } + @Test void testNextLegs() { var transitService = new DefaultTransitService(transitModel); @@ -91,9 +100,7 @@ void testNextLegs() { AlternativeLegsFilter.NO_FILTER ); - var legs = Itinerary.toStr( - alternativeLegs.stream().map(Leg.class::cast).map(List::of).map(Itinerary::new).toList() - ); + var legs = getStr(alternativeLegs); var expected = "B ~ BUS 3 1:00 1:10 ~ C [C₁-1], " + @@ -126,9 +133,8 @@ void testCircularRoutes() { false, AlternativeLegsFilter.NO_FILTER ); - var legs = Itinerary.toStr( - alternativeLegs.stream().map(Leg.class::cast).map(List::of).map(Itinerary::new).toList() - ); + + var legs = getStr(alternativeLegs); assertEquals("X ~ BUS 19 10:30 10:40 ~ Y [C₁-1], X ~ BUS 19 10:00 10:10 ~ Y [C₁-1]", legs); } @@ -155,9 +161,7 @@ void testComplexCircularRoutes() { false, AlternativeLegsFilter.NO_FILTER ); - var legs = Itinerary.toStr( - alternativeLegs.stream().map(Leg.class::cast).map(List::of).map(Itinerary::new).toList() - ); + var legs = getStr(alternativeLegs); var expected = String.join(", ", List.of("X ~ BUS 19 10:30 11:00 ~ B [C₁-1]")); assertEquals(expected, legs); From 9b161fb18dbbcdfdf2fc90b6a3f038bdd060f5cd Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 17 Sep 2024 16:56:04 +0200 Subject: [PATCH 02/35] Implement filtering of search window aware itineraries --- .../system/OutsideSearchWindowFilter.java | 8 +++-- .../system/OutsideSearchWindowFilterTest.java | 36 ++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java index ba3ef4b04f3..b82f1a91a11 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java @@ -35,8 +35,12 @@ public String name() { @Override public Predicate shouldBeFlaggedForRemoval() { return it -> { - var time = it.startTime().toInstant(); - return time.isBefore(earliestDepartureTime) || !time.isBefore(latestDepartureTime); + if (it.isSearchWindowAware()) { + var time = it.startTime().toInstant(); + return time.isBefore(earliestDepartureTime) || !time.isBefore(latestDepartureTime); + } else { + return false; + } }; } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java index 9752004560e..12cff4c86ed 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java @@ -1,17 +1,19 @@ 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.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.framework.time.TimeUtils.time; import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; import java.time.Duration; import java.util.List; +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; -import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.model.SystemNotice; import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.model.plan.PlanTestConstants; @@ -20,8 +22,8 @@ public class OutsideSearchWindowFilterTest implements PlanTestConstants { private static final Duration SEARCH_WINDOW_10m = Duration.ofMinutes(10); - private final int startTime = TimeUtils.time("09:30"); - private final int endTime = TimeUtils.time("09:40"); + private final int startTime = time("09:30"); + private final int endTime = time("09:40"); private final Itinerary itinerary = newItinerary(A).bus(32, startTime, endTime, E).build(); private final List input = List.of(itinerary); @@ -40,7 +42,7 @@ static List filterOnSearchWindowTestCases() { public void filterOnSearchWindow(String description, String edt, boolean flaggedForRemoval) { List expected = flaggedForRemoval ? input : List.of(); var subject = new OutsideSearchWindowFilter( - TestItineraryBuilder.newTime(TimeUtils.time(edt)).toInstant(), + TestItineraryBuilder.newTime(time(edt)).toInstant(), SEARCH_WINDOW_10m ); var result = subject.flagForRemoval(input); @@ -55,4 +57,30 @@ public void testTaggedBy() { it.flagForDeletion(new SystemNotice(OutsideSearchWindowFilter.TAG, "Text")); assertTrue(OutsideSearchWindowFilter.taggedBy(it)); } + + private static Stream onStreetTestCases() { + int t9_28 = time("9:28"); + int t9_38 = time("9:38"); + return Stream + .of( + newItinerary(A, t9_28).walk(D2m, B), + newItinerary(A, t9_38).walk(D12m, B), + newItinerary(A, t9_28).bicycle(t9_28, t9_38, B), + newItinerary(A, t9_28).flex(t9_28, t9_38, B), + newItinerary(A, t9_28).flex(t9_38, time("9:48"), B), + newItinerary(A, time("9:20")).flex(time("9:20"), t9_28, B).walk(D12m, C) + ) + .map(b -> b.withIsSearchWindowAware(true).build()); + } + + @ParameterizedTest + @MethodSource("onStreetTestCases") + void onStreetArriveByShouldNotBeRemoved(Itinerary itin) { + var edt = "9:20"; + var subject = new OutsideSearchWindowFilter( + TestItineraryBuilder.newTime(time(edt)).toInstant(), + SEARCH_WINDOW_10m + ); + assertThat(subject.flagForRemoval(List.of(itin))).isEmpty(); + } } From 8d8566e8d8ef519c8b2c09685b0b4f2ab71f6b35 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 17 Sep 2024 16:58:42 +0200 Subject: [PATCH 03/35] Add flex time windows in test builder --- .../org/opentripplanner/model/plan/TestItineraryBuilder.java | 4 ++++ .../filters/system/OutsideSearchWindowFilterTest.java | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index e7be78badfe..5d3a27aa4b8 100644 --- a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java +++ b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java @@ -200,9 +200,13 @@ public TestItineraryBuilder flex(int start, int end, Place to) { int legCost = 0; StopTime fromStopTime = new StopTime(); fromStopTime.setStop(lastPlace.stop); + fromStopTime.setFlexWindowStart(start); + fromStopTime.setFlexWindowEnd(end); StopTime toStopTime = new StopTime(); toStopTime.setStop(to.stop); + toStopTime.setFlexWindowStart(start); + toStopTime.setFlexWindowEnd(end); Trip trip = trip("1", route("flex").build()); diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java index 12cff4c86ed..dda01ac1840 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java @@ -70,7 +70,8 @@ private static Stream onStreetTestCases() { newItinerary(A, t9_28).flex(t9_38, time("9:48"), B), newItinerary(A, time("9:20")).flex(time("9:20"), t9_28, B).walk(D12m, C) ) - .map(b -> b.withIsSearchWindowAware(true).build()); + // results from the flex router are not aware of the search window + .map(b -> b.withIsSearchWindowAware(false).build()); } @ParameterizedTest From 9ba36ed11e126d18b89531c9386eb19821e7d385 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 17 Sep 2024 17:27:02 +0200 Subject: [PATCH 04/35] Flesh out tests --- .../opentripplanner/model/plan/Itinerary.java | 4 ++++ .../system/OutsideSearchWindowFilterTest.java | 2 +- .../GraphPathToItineraryMapperTest.java | 20 +++++++++++++++++ .../RaptorPathToItineraryMapperTest.java | 11 ++++++++++ .../stoptimes/AlternativeLegsTest.java | 22 +++++++++---------- 5 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapperTest.java diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index 388f8439965..69365a4dd66 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -171,6 +171,10 @@ public boolean hasTransit() { .anyMatch(l -> l instanceof ScheduledTransitLeg || l instanceof FlexibleTransitLeg); } + /** + * Returns true if this itinerary was produced by an algorithm that is aware of the search window. + * As of 2024 only the itineraries produced by RAPTOR that do that. + */ public boolean isSearchWindowAware() { return isSearchWindowAware; } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java index dda01ac1840..6e475ac5893 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilterTest.java @@ -70,7 +70,7 @@ private static Stream onStreetTestCases() { newItinerary(A, t9_28).flex(t9_38, time("9:48"), B), newItinerary(A, time("9:20")).flex(time("9:20"), t9_28, B).walk(D12m, C) ) - // results from the flex router are not aware of the search window + // results from the street & flex routers are not aware of the search window .map(b -> b.withIsSearchWindowAware(false).build()); } diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapperTest.java new file mode 100644 index 00000000000..3ff469396a2 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapperTest.java @@ -0,0 +1,20 @@ +package org.opentripplanner.routing.algorithm.mapping; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.junit.jupiter.api.Test; +import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.astar.model.GraphPath; +import org.opentripplanner.routing.services.notes.StreetNotesService; +import org.opentripplanner.street.search.state.TestStateBuilder; + +class GraphPathToItineraryMapperTest { + + @Test + void isSearchWindowAware() { + var mapper = new GraphPathToItineraryMapper(ZoneIds.UTC, new StreetNotesService(), 1); + var state = TestStateBuilder.ofWalking().streetEdge().streetEdge().streetEdge().build(); + var itin = mapper.generateItinerary(new GraphPath<>(state)); + assertFalse(itin.isSearchWindowAware()); + } +} 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 7b877abcf3a..48622a0912f 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.raptor._data.RaptorTestConstants.BOARD_SLACK; import java.time.Duration; @@ -246,6 +247,16 @@ private TestTripSchedule transferAtSameStopSchedule() { return data.getRoute(0).getTripSchedule(0); } + @Test + void isSearchWindowAware() { + var mapper = getRaptorPathToItineraryMapper(); + + var path = createTestTripSchedulePath(getTestTripSchedule()) + .egress(TestAccessEgress.free(2, RaptorCostConverter.toRaptorCost(100))); + var itinerary = mapper.createItinerary(path); + assertTrue(itinerary.isSearchWindowAware()); + } + private TripPattern getOriginalPattern(TestTripPattern pattern) { var stopModelBuilder = TEST_MODEL.stopModelBuilder(); ArrayList stopTimes = new ArrayList<>(); diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java index deced3a5c4d..676aec99cb7 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java @@ -66,17 +66,6 @@ void testPreviousLegs() { assertEquals(expected, legs); } - private static String getStr(List alternativeLegs) { - return Itinerary.toStr( - alternativeLegs - .stream() - .map(Leg.class::cast) - .map(List::of) - .map(l -> new Itinerary(l, true)) - .toList() - ); - } - @Test void testNextLegs() { var transitService = new DefaultTransitService(transitModel); @@ -166,4 +155,15 @@ void testComplexCircularRoutes() { var expected = String.join(", ", List.of("X ~ BUS 19 10:30 11:00 ~ B [C₁-1]")); assertEquals(expected, legs); } + + private static String getStr(List alternativeLegs) { + return Itinerary.toStr( + alternativeLegs + .stream() + .map(Leg.class::cast) + .map(List::of) + .map(l -> new Itinerary(l, true)) + .toList() + ); + } } From e7060768b83843db854040ff6ce90aab8363ecf9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Sep 2024 14:48:41 +0200 Subject: [PATCH 05/35] Fix spelling in paging README --- .../opentripplanner/model/plan/paging/cursor/readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/plan/paging/cursor/readme.md b/src/main/java/org/opentripplanner/model/plan/paging/cursor/readme.md index e98071cfa62..0eab721bcc3 100644 --- a/src/main/java/org/opentripplanner/model/plan/paging/cursor/readme.md +++ b/src/main/java/org/opentripplanner/model/plan/paging/cursor/readme.md @@ -13,7 +13,7 @@ moving on to the next. ## Terminology -- **search-window (sw)** The search window is the minutes Raptor iterate over and the time-window +- **search-window (sw)** The search window is the minutes Raptor iterates over and the time-window the itinerary must start within to be included in the result. The search-window may change from a request to the next page. **sw'** is the search window for the new next/previous page. The search window may change between requests, so we need to account for it when computing the next/previous @@ -28,7 +28,7 @@ moving on to the next. - **<< previous page** The trip search constructed to retrieve itineraries AFTER the original search. - **crop-search-window** If the `maxNumOfItineraries` limit is reached in the - `ItineraryFilterChain`, then one or more itineraries are removed. The filter remove itineraries + `ItineraryFilterChain`, then one or more itineraries are removed. The filter removes itineraries from the beginning or end of the list depending on the page cursor type (next/previous) and the sort order(arrival/departure time). @@ -55,8 +55,8 @@ _previous-page_ must reverse the itinerary-filtering: `crop itineraries at START - In this case the `<< Previous page` is the same as in [sort-by-arrival](#sort-by-arrival) and not shown. - For the `Next page >>` we must adjust the `edt'`. -- In rare cases we get duplicate itineraries. This happens if the `removed itinerary` depart before, - but arrive after the `duplicate`. +- In rare cases we get duplicate itineraries. This happens if the `removed itinerary` departs before, + but arrives after the `duplicate`. ### sort-by-arrival, crop-search-window & original-prev-page @@ -99,7 +99,7 @@ This is the basic `sort-by-departure` (arrive-by search) without removing itiner In this case the itineraries are dropped from the search results in the `Original Search` and the `<< Previous page` must be adjusted. We use the first removed itinerary to set both the `edt'` and the `lat'`. An `optimal itinarary` in the original search is lost (not found) in the previous page -if it departs AFTER the `remoed itinerary` and arrive before - hopefully this is a rare case. +if it departs AFTER the `removed itinerary` and arrives before - hopefully this is a rare case. The `Next page >>` is the same as the basic case [sort-by-departure](#sort-by-departure). From cf5266ed97ed36795eae81da3ddd83ac32c91f11 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Sep 2024 14:58:16 +0200 Subject: [PATCH 06/35] Add Javadoc for DirectStreetRouter, FilterTransitWhenStreetModeIsEmpty --- .../router/FilterTransitWhenDirectModeIsEmpty.java | 10 +++++----- .../router/street/DirectStreetRouter.java | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java index 01a8c9ab112..fc75337b7d0 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java @@ -6,12 +6,12 @@ /** *

* In OTP the street search and transit search is done as to separate searches. The results is then - * merged and filtered to remove none optimal itineraries. But, when the client do NOT provide a - * ´directMode´, OTP do not do the streetSearch. And, the removal of none optimal results is not + * merged and filtered to remove non-optimal itineraries. But, when the client does NOT provide a + * ´directMode´, OTP does not do the streetSearch. And, the removal of non-optimal results is not * done, there is not street results to use to prune bad transit results with. In other words OTP is * forced to return at least one itinerary with at least one transit leg. So, instead of walking - * maybe 100 meters, the OTP suggest you need to walk to the closest buss stop, take the bus one - * stop and walk back, oten with more walking than just those 100 meters. + * maybe 100 meters, the OTP suggest you need to walk to the closest bus stop, take the bus one + * stop and walk back, often with more walking than just those 100 meters. *

* Let say OTP produces these internal results: *

    @@ -31,7 +31,7 @@ *

    * If no directMode is set, the responsibility of this class it to always filter away itineraries * with a generalized-cost that is higher than the WALK-ALL-THE-WAY. We achieve this by setting the - * directMode before searching. This trigger the direct street search, and later the result is + * directMode before searching. This triggers the direct street search, and later the result is * passed into the filter chain where none optimal results are removed. Finally the street itinerary * is removed and the request street mode rest to the original state. *

    diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java index 11463902dcd..3038b81309a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java @@ -18,6 +18,12 @@ import org.opentripplanner.street.search.TemporaryVerticesContainer; import org.opentripplanner.street.search.state.State; +/** + * Generates "direct" street routes, i.e. those that do not use transit and are on the street + * network for the entire itinerary. + * + * @see DirectFlexRouter + */ public class DirectStreetRouter { public static List route(OtpServerRequestContext serverContext, RouteRequest request) { From f61cf33283185d52f7e9ddc59e96999e1f87b370 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Sep 2024 16:45:26 +0200 Subject: [PATCH 07/35] Add documentation why applying the page cursor unsets the direct mode --- .../opentripplanner/routing/api/request/RouteRequest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 76e5dcc558a..6774cf62205 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -182,6 +182,13 @@ public SortOrder itinerariesSortOrder() { * Adjust the 'dateTime' if the page cursor is set to "goto next/previous page". The date-time is * used for many things, for example finding the days to search, but the transit search is using * the cursor[if exist], not the date-time. + *

    + * The direct mode is also unset when there is a page cursor because for anything other than the + * initial page we don't want to see direct results. + *

    + * However, because we still want to have a walking result to compare the transit with, we + * temporarily set the direct mode in {@link org.opentripplanner.routing.algorithm.raptoradapter.router.FilterTransitWhenDirectModeIsEmpty} + * and then filter out this walking itinerary in {@link org.opentripplanner.routing.algorithm.filterchain.filters.street.RemoveWalkOnlyFilter}. */ public void applyPageCursor() { if (pageCursor != null) { From a851e50a7a217276bc860d35b55e8fb28cc50c0b Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Sep 2024 16:50:16 +0200 Subject: [PATCH 08/35] Remove outdated deprecation comment --- .../opentripplanner/routing/api/request/RouteRequest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 6774cf62205..6e9a39bb9b7 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -35,14 +35,6 @@ * All defaults should be specified here in the RouteRequest, NOT as annotations on query parameters * in web services that create RouteRequests. This establishes a priority chain for default values: * RouteRequest field initializers, then JSON router config, then query parameters. - * - * @Deprecated tag is added to all parameters that are not currently functional in either the Raptor - * router or other non-transit routing (walk, bike, car etc.) - *

    - * TODO OTP2 Many fields are deprecated in this class, the reason is documented in the - * RoutingResource class, not here. Eventually the field will be removed from this - * class, but we want to keep it in the RoutingResource as long as we support the - * REST API. */ public class RouteRequest implements Cloneable, Serializable { From 1a5a388e7e2589301aeeaa31bbeaf7bcd94ba8ba Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 18 Sep 2024 16:58:53 +0200 Subject: [PATCH 09/35] Make test more comprehensive --- .../GraphPathToItineraryMapperTest.java | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapperTest.java index 3ff469396a2..2d5c1b1770b 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapperTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapperTest.java @@ -2,18 +2,38 @@ import static org.junit.jupiter.api.Assertions.assertFalse; -import org.junit.jupiter.api.Test; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.astar.model.GraphPath; import org.opentripplanner.routing.services.notes.StreetNotesService; +import org.opentripplanner.street.search.state.State; import org.opentripplanner.street.search.state.TestStateBuilder; class GraphPathToItineraryMapperTest { - @Test - void isSearchWindowAware() { + private static Stream cases() { + return Stream + .of( + TestStateBuilder.ofWalking(), + TestStateBuilder.ofCycling(), + TestStateBuilder.ofDriving(), + TestStateBuilder.ofScooterRental().pickUpFreeFloatingScooter(), + TestStateBuilder.ofBikeAndRide(), + TestStateBuilder.parkAndRide() + ) + .map(b -> { + var state = b.streetEdge().streetEdge().build(); + return Arguments.argumentSet(state.currentMode().toString(), state); + }); + } + + @ParameterizedTest + @MethodSource("cases") + void isSearchWindowAware(State state) { var mapper = new GraphPathToItineraryMapper(ZoneIds.UTC, new StreetNotesService(), 1); - var state = TestStateBuilder.ofWalking().streetEdge().streetEdge().streetEdge().build(); var itin = mapper.generateItinerary(new GraphPath<>(state)); assertFalse(itin.isSearchWindowAware()); } From 318ea2fecd10df5dfbc05a62cfc3af309bad102f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 19 Sep 2024 22:49:02 +0200 Subject: [PATCH 10/35] Add filter just for direct flex itineraries --- .../opentripplanner/model/plan/Itinerary.java | 7 +++ .../ItineraryListFilterChainBuilder.java | 2 + .../system/FlexSearchWindowFilter.java | 46 +++++++++++++++++++ .../system/OutsideSearchWindowFilter.java | 6 ++- .../model/plan/ItineraryTest.java | 6 +++ 5 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilter.java diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index 69365a4dd66..ac98bcd8000 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -164,6 +164,13 @@ public boolean isOnStreetAllTheWay() { return isStreetOnly(); } + /** + * Returns true if this itinerary has only flex and walking legs. + */ + public boolean isFlexAndWalkOnly() { + return legs.stream().allMatch(l -> l.isFlexibleTrip() || l.isWalkingLeg()); + } + /** TRUE if at least one leg is a transit leg. */ public boolean hasTransit() { return legs 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 071814a7abf..7cffe128a99 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -25,6 +25,7 @@ import org.opentripplanner.routing.algorithm.filterchain.filters.street.RemoveNonTransitItinerariesBasedOnGeneralizedCost; import org.opentripplanner.routing.algorithm.filterchain.filters.street.RemoveParkAndRideWithMostlyWalkingFilter; import org.opentripplanner.routing.algorithm.filterchain.filters.street.RemoveWalkOnlyFilter; +import org.opentripplanner.routing.algorithm.filterchain.filters.system.FlexSearchWindowFilter; import org.opentripplanner.routing.algorithm.filterchain.filters.system.NumItinerariesFilter; import org.opentripplanner.routing.algorithm.filterchain.filters.system.OutsideSearchWindowFilter; import org.opentripplanner.routing.algorithm.filterchain.filters.system.PagingFilter; @@ -468,6 +469,7 @@ public ItineraryListFilterChain build() { filters, new OutsideSearchWindowFilter(earliestDepartureTime, searchWindow) ); + addRemoveFilter(filters, new FlexSearchWindowFilter(earliestDepartureTime)); } // 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 new file mode 100644 index 00000000000..bfacc1a815c --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilter.java @@ -0,0 +1,46 @@ +package org.opentripplanner.routing.algorithm.filterchain.filters.system; + +import java.time.Instant; +import java.util.function.Predicate; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; + +/** + * The flex router doesn't use the transit router's time window but nevertheless using it + * for filtering is useful when combining flex with transit. + *

    + * The flex router also searches the previous day (arrive by) or the next one (depart after). + * If you didn't do it you could get yesterday's or tomorrow's results where you would not expect it. + */ +public class FlexSearchWindowFilter implements RemoveItineraryFlagger { + + public static final String TAG = "outside-flex-window"; + + private final Instant earliestDepartureTime; + + public FlexSearchWindowFilter(Instant earliestDepartureTime) { + this.earliestDepartureTime = earliestDepartureTime; + } + + @Override + public String name() { + return TAG; + } + + @Override + public Predicate shouldBeFlaggedForRemoval() { + return it -> { + if (it.isFlexAndWalkOnly()) { + var time = it.startTime().toInstant(); + return time.isBefore(earliestDepartureTime); + } else { + return false; + } + }; + } + + @Override + public boolean skipAlreadyFlaggedItineraries() { + return false; + } +} diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java index b82f1a91a11..5f375c9f4d9 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java @@ -7,8 +7,10 @@ import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** - * This filter will remove all itineraries that are outside the search-window. In some - * cases the access is time-shifted after the end of the search-window. These results + * This filter will remove all itineraries that are both search-window aware and outside the + * search-window. Only those that use transit are search-window, street and flex itineraries are not. + *

    + * In some cases the access is time-shifted after the end of the search-window. These results * should appear again when paging to the next page. Hence, this filter will remove * such itineraries. The same is true for when paging to the previous page for arriveBy=true. *

    diff --git a/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java b/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java index 142ced31504..21531fbf591 100644 --- a/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java +++ b/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java @@ -258,6 +258,12 @@ void bothPenalties() { ); } + @Test + void flexAndWalk() { + assertFalse(itinerary().isFlexAndWalkOnly()); + assertTrue(newItinerary(A).flex(T11_10, T11_20, B).build().isFlexAndWalkOnly()); + } + private static Itinerary itinerary() { return newItinerary(A).bus(1, T11_04, T11_14, B).build(); } From d37c51c9007e721fc86ef47302ce4169b4d37767 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 20 Sep 2024 11:24:30 +0200 Subject: [PATCH 11/35] Add test for FlexWindowFilter --- .../system/FlexSearchWindowFilter.java | 3 +- .../system/FlexSearchWindowFilterTest.java | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilterTest.java 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 bfacc1a815c..724e05fc3c2 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 @@ -10,7 +10,8 @@ * for filtering is useful when combining flex with transit. *

    * The flex router also searches the previous day (arrive by) or the next one (depart after). - * If you didn't do it you could get yesterday's or tomorrow's results where you would not expect it. + * If you didn't didn't filter the flex results by something you could get yesterday's or tomorrow's + * trips where you would not expect it. */ public class FlexSearchWindowFilter implements RemoveItineraryFlagger { 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 new file mode 100644 index 00000000000..c3989ef8d0e --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilterTest.java @@ -0,0 +1,46 @@ +package org.opentripplanner.routing.algorithm.filterchain.filters.system; + +import static com.google.common.truth.Truth.assertThat; +import static org.opentripplanner.framework.time.TimeUtils.time; +import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; + +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.TestItineraryBuilder; + +class FlexSearchWindowFilterTest implements PlanTestConstants { + + private static final Instant LATEST_DEPARTURE_TIME = TestItineraryBuilder + .newTime(time("9:20")) + .toInstant(); + + @ParameterizedTest + @ValueSource(strings = { "9:20", "9:21", "13:20" }) + void keepFlexItinerariesAfterLDT(String startTime) { + var edt = "9:20"; + var subject = new FlexSearchWindowFilter(TestItineraryBuilder.newTime(time(edt)).toInstant()); + + var itin = newItinerary(A, time(startTime)) + .flex(T11_00, T11_30, B) + .withIsSearchWindowAware(false) + .build(); + + assertThat(subject.flagForRemoval(List.of(itin))).isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = { "0:0", "0:01", "9:19" }) + void removeFlexItinerariesBeforeLDT(String startTime) { + var subject = new FlexSearchWindowFilter(LATEST_DEPARTURE_TIME); + + var itin = newItinerary(A, time(startTime)) + .flex(T11_00, T11_30, B) + .withIsSearchWindowAware(false) + .build(); + + assertThat(subject.flagForRemoval(List.of(itin))).isEmpty(); + } +} From 0538587eec7906c208045f5f4a02a7a2860eef92 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 24 Sep 2024 14:39:36 +0200 Subject: [PATCH 12/35] Extract factory methods for creating itineraries --- .../ext/emissions/EmissionsTest.java | 23 +++++++++++-------- .../ext/fares/impl/OrcaFareServiceTest.java | 2 +- .../opentripplanner/model/plan/Itinerary.java | 17 +++++++++++++- .../mapping/GraphPathToItineraryMapper.java | 2 +- .../mapping/RaptorPathToItineraryMapper.java | 7 +++--- .../model/plan/TestItineraryBuilder.java | 7 +++++- .../stoptimes/AlternativeLegsTest.java | 2 +- 7 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index e7cdb22d70a..602bd0b87a8 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.opentripplanner.model.plan.Itinerary.createScheduledTransitItinerary; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; import java.time.OffsetDateTime; @@ -63,23 +64,22 @@ static void SetUp() { @Test void testGetEmissionsForItinerary() { - Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS)), true); + Itinerary i = createScheduledTransitItinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS))); decorateWithEmission.decorate(i); assertEquals(new Grams(2223.902), i.getEmissionsPerPerson().getCo2()); } @Test void testGetEmissionsForCarRoute() { - Itinerary i = new Itinerary(List.of(STREET_LEG), true); + Itinerary i = createScheduledTransitItinerary(List.of(STREET_LEG)); decorateWithEmission.decorate(i); assertEquals(new Grams(28.0864), i.getEmissionsPerPerson().getCo2()); } @Test void testNoEmissionsForFeedWithoutEmissionsConfigured() { - Itinerary i = new Itinerary( - List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED)), - true + Itinerary i = createScheduledTransitItinerary( + List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED)) ); decorateWithEmission.decorate(i); assertNull(i.getEmissionsPerPerson()); @@ -87,23 +87,26 @@ void testNoEmissionsForFeedWithoutEmissionsConfigured() { @Test void testZeroEmissionsForItineraryWithZeroEmissions() { - Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_ZERO_EMISSIONS)), true); + Itinerary i = Itinerary.createScheduledTransitItinerary( + List.of(createTransitLeg(ROUTE_WITH_ZERO_EMISSIONS)) + ); decorateWithEmission.decorate(i); assertEquals(new Grams(0.0), i.getEmissionsPerPerson().getCo2()); } @Test void testGetEmissionsForCombinedRoute() { - Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS), STREET_LEG), true); + Itinerary i = Itinerary.createScheduledTransitItinerary( + List.of(createTransitLeg(ROUTE_WITH_EMISSIONS), STREET_LEG) + ); decorateWithEmission.decorate(i); assertEquals(new Grams(2251.9884), i.getEmissionsPerPerson().getCo2()); } @Test void testNoEmissionsForCombinedRouteWithoutTransitEmissions() { - Itinerary i = new Itinerary( - List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED), STREET_LEG), - true + Itinerary i = Itinerary.createScheduledTransitItinerary( + List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED), STREET_LEG) ); decorateWithEmission.decorate(i); var emissionsResult = i.getEmissionsPerPerson() != null diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java index e1157aa7bac..63ed44131fd 100644 --- a/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/fares/impl/OrcaFareServiceTest.java @@ -79,7 +79,7 @@ public static void setUpClass() { * types. */ private static void calculateFare(List legs, FareType fareType, Money expectedPrice) { - var itinerary = new Itinerary(legs, true); + var itinerary = Itinerary.createScheduledTransitItinerary(legs); var itineraryFares = orcaFareService.calculateFares(itinerary); assertEquals( expectedPrice, diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index ac98bcd8000..f4f1253ddfa 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -69,7 +69,7 @@ public class Itinerary implements ItinerarySortKey { private ItineraryFares fare = ItineraryFares.empty(); - public Itinerary(List legs, boolean isSearchWindowAware) { + private Itinerary(List legs, boolean isSearchWindowAware) { setLegs(legs); this.isSearchWindowAware = isSearchWindowAware; @@ -89,6 +89,21 @@ public Itinerary(List legs, boolean isSearchWindowAware) { this.setElevationLost(totals.totalElevationLost); } + /** + * Creates an itinerary that contains scheduled transit which is aware of the search window. + */ + public static Itinerary createScheduledTransitItinerary(List legs) { + return new Itinerary(legs, true); + } + + /** + * Creates an itinerary that creates only street or flex results which are not aware of the + * time window. + */ + public static Itinerary createDirectItinerary(List legs) { + return new Itinerary(legs, false); + } + /** * Time that the trip departs. */ diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java index 9abdc75d2b8..2e7bf1b7040 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java @@ -134,7 +134,7 @@ public Itinerary generateItinerary(GraphPath path) { } } - Itinerary itinerary = new Itinerary(legs, false); + Itinerary itinerary = Itinerary.createDirectItinerary(legs); calculateElevations(itinerary, path.edges); diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java index 1907b8eb0a1..a81dd4e7083 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java @@ -140,7 +140,7 @@ else if (pathLeg.isTransferLeg()) { Itinerary mapped = mapEgressLeg(egressPathLeg); legs.addAll(mapped == null ? List.of() : mapped.getLegs()); - Itinerary itinerary = new Itinerary(legs, true); + Itinerary itinerary = Itinerary.createScheduledTransitItinerary(legs); // Map general itinerary fields itinerary.setArrivedAtDestinationWithRentedVehicle( @@ -390,7 +390,7 @@ private List mapNonTransitLeg( } private Itinerary mapDirectPath(RaptorPath path) { - return new Itinerary( + return Itinerary.createScheduledTransitItinerary( List.of( new UnknownTransitPathLeg( mapPlace(request.from()), @@ -399,8 +399,7 @@ private Itinerary mapDirectPath(RaptorPath path) { createZonedDateTime(path.endTime()), path.numberOfTransfers() ) - ), - true + ) ); } diff --git a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index 5d3a27aa4b8..2d2d6765fc9 100644 --- a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java +++ b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java @@ -423,7 +423,12 @@ public Itinerary build(int c1) { } public Itinerary build() { - Itinerary itinerary = new Itinerary(legs, isSearchWindowAware); + Itinerary itinerary; + if (isSearchWindowAware) { + itinerary = Itinerary.createScheduledTransitItinerary(legs); + } else { + itinerary = Itinerary.createDirectItinerary(legs); + } itinerary.setGeneralizedCost(c1); if (c2 != NOT_SET) { itinerary.setGeneralizedCost2(c2); diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java index 676aec99cb7..a8a66b39fb7 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java @@ -162,7 +162,7 @@ private static String getStr(List alternativeLegs) { .stream() .map(Leg.class::cast) .map(List::of) - .map(l -> new Itinerary(l, true)) + .map(Itinerary::createScheduledTransitItinerary) .toList() ); } From c2a5310ffa2c6a6322557671c78587eb0103260f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 24 Sep 2024 17:29:15 +0200 Subject: [PATCH 13/35] Apply suggestions from code review Co-authored-by: Thomas Gran --- src/main/java/org/opentripplanner/model/plan/Itinerary.java | 2 +- .../filterchain/filters/system/FlexSearchWindowFilter.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index f4f1253ddfa..ed088640ef5 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -64,7 +64,7 @@ public class Itinerary implements ItinerarySortKey { /* other properties */ private final List systemNotices = new ArrayList<>(); - private final boolean isSearchWindowAware; + private final boolean searchWindowAware; private List legs; private ItineraryFares fare = ItineraryFares.empty(); 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 724e05fc3c2..e49c6f422f8 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 @@ -10,7 +10,7 @@ * for filtering is useful when combining flex with transit. *

    * The flex router also searches the previous day (arrive by) or the next one (depart after). - * If you didn't didn't filter the flex results by something you could get yesterday's or tomorrow's + * If you didn't filter the flex results by something you could get yesterday's or tomorrow's * trips where you would not expect it. */ public class FlexSearchWindowFilter implements RemoveItineraryFlagger { From a6098d7d5d63335a0972ca6555743a86c55bbe4d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 24 Sep 2024 17:55:18 +0200 Subject: [PATCH 14/35] Rename isFlexAndWalkOnly to isDirectFlex --- .../java/org/opentripplanner/model/plan/Itinerary.java | 10 +++++----- .../filters/system/FlexSearchWindowFilter.java | 2 +- .../org/opentripplanner/model/plan/ItineraryTest.java | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index ed088640ef5..a928e0c5fff 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -69,9 +69,9 @@ public class Itinerary implements ItinerarySortKey { private ItineraryFares fare = ItineraryFares.empty(); - private Itinerary(List legs, boolean isSearchWindowAware) { + private Itinerary(List legs, boolean searchWindowAware) { setLegs(legs); - this.isSearchWindowAware = isSearchWindowAware; + this.searchWindowAware = searchWindowAware; // Set aggregated data ItinerariesCalculateLegTotals totals = new ItinerariesCalculateLegTotals(legs); @@ -182,7 +182,7 @@ public boolean isOnStreetAllTheWay() { /** * Returns true if this itinerary has only flex and walking legs. */ - public boolean isFlexAndWalkOnly() { + public boolean isDirectFlex() { return legs.stream().allMatch(l -> l.isFlexibleTrip() || l.isWalkingLeg()); } @@ -198,7 +198,7 @@ public boolean hasTransit() { * As of 2024 only the itineraries produced by RAPTOR that do that. */ public boolean isSearchWindowAware() { - return isSearchWindowAware; + return searchWindowAware; } public Leg firstLeg() { @@ -247,7 +247,7 @@ public Itinerary withTimeShiftToStartAt(ZonedDateTime afterTime) { .stream() .map(leg -> leg.withTimeShift(duration)) .collect(Collectors.toList()); - var newItin = new Itinerary(timeShiftedLegs, isSearchWindowAware); + var newItin = new Itinerary(timeShiftedLegs, searchWindowAware); newItin.setGeneralizedCost(getGeneralizedCost()); return newItin; } 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 e49c6f422f8..47ca0e730e7 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 @@ -31,7 +31,7 @@ public String name() { @Override public Predicate shouldBeFlaggedForRemoval() { return it -> { - if (it.isFlexAndWalkOnly()) { + if (it.isDirectFlex()) { var time = it.startTime().toInstant(); return time.isBefore(earliestDepartureTime); } else { diff --git a/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java b/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java index 21531fbf591..e05273b8a68 100644 --- a/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java +++ b/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java @@ -260,8 +260,8 @@ void bothPenalties() { @Test void flexAndWalk() { - assertFalse(itinerary().isFlexAndWalkOnly()); - assertTrue(newItinerary(A).flex(T11_10, T11_20, B).build().isFlexAndWalkOnly()); + assertFalse(itinerary().isDirectFlex()); + assertTrue(newItinerary(A).flex(T11_10, T11_20, B).build().isDirectFlex()); } private static Itinerary itinerary() { From 15faf8015a105a30a2bd9949749b5782d7446ce8 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 24 Sep 2024 17:59:42 +0200 Subject: [PATCH 15/35] Update Javadoc --- .../org/opentripplanner/ext/emissions/EmissionsTest.java | 6 +++--- .../opentripplanner/routing/api/request/RouteRequest.java | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java index 602bd0b87a8..51bf5f344f6 100644 --- a/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/emissions/EmissionsTest.java @@ -87,7 +87,7 @@ void testNoEmissionsForFeedWithoutEmissionsConfigured() { @Test void testZeroEmissionsForItineraryWithZeroEmissions() { - Itinerary i = Itinerary.createScheduledTransitItinerary( + Itinerary i = createScheduledTransitItinerary( List.of(createTransitLeg(ROUTE_WITH_ZERO_EMISSIONS)) ); decorateWithEmission.decorate(i); @@ -96,7 +96,7 @@ void testZeroEmissionsForItineraryWithZeroEmissions() { @Test void testGetEmissionsForCombinedRoute() { - Itinerary i = Itinerary.createScheduledTransitItinerary( + Itinerary i = createScheduledTransitItinerary( List.of(createTransitLeg(ROUTE_WITH_EMISSIONS), STREET_LEG) ); decorateWithEmission.decorate(i); @@ -105,7 +105,7 @@ void testGetEmissionsForCombinedRoute() { @Test void testNoEmissionsForCombinedRouteWithoutTransitEmissions() { - Itinerary i = Itinerary.createScheduledTransitItinerary( + Itinerary i = createScheduledTransitItinerary( List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED), STREET_LEG) ); decorateWithEmission.decorate(i); diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 6e9a39bb9b7..ccd6d7329b7 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -178,9 +178,8 @@ public SortOrder itinerariesSortOrder() { * The direct mode is also unset when there is a page cursor because for anything other than the * initial page we don't want to see direct results. *

    - * However, because we still want to have a walking result to compare the transit with, we - * temporarily set the direct mode in {@link org.opentripplanner.routing.algorithm.raptoradapter.router.FilterTransitWhenDirectModeIsEmpty} - * and then filter out this walking itinerary in {@link org.opentripplanner.routing.algorithm.filterchain.filters.street.RemoveWalkOnlyFilter}. + * See also {@link org.opentripplanner.routing.algorithm.raptoradapter.router.FilterTransitWhenDirectModeIsEmpty}, + * it uses a direct search to prune transit. */ public void applyPageCursor() { if (pageCursor != null) { From f9c3adde37d0d92c7cf320073d5312cf0852bda9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 25 Sep 2024 09:50:15 +0200 Subject: [PATCH 16/35] Rename method --- .../routing/stoptimes/AlternativeLegsTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java index a8a66b39fb7..a4699ece2ed 100644 --- a/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java +++ b/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java @@ -55,7 +55,7 @@ void testPreviousLegs() { AlternativeLegsFilter.NO_FILTER ); - var legs = getStr(alternativeLegs); + var legs = toString(alternativeLegs); var expected = "B ~ BUS 2 0:20 0:30 ~ C [C₁-1], " + @@ -89,7 +89,7 @@ void testNextLegs() { AlternativeLegsFilter.NO_FILTER ); - var legs = getStr(alternativeLegs); + var legs = toString(alternativeLegs); var expected = "B ~ BUS 3 1:00 1:10 ~ C [C₁-1], " + @@ -123,7 +123,7 @@ void testCircularRoutes() { AlternativeLegsFilter.NO_FILTER ); - var legs = getStr(alternativeLegs); + var legs = toString(alternativeLegs); assertEquals("X ~ BUS 19 10:30 10:40 ~ Y [C₁-1], X ~ BUS 19 10:00 10:10 ~ Y [C₁-1]", legs); } @@ -150,13 +150,13 @@ void testComplexCircularRoutes() { false, AlternativeLegsFilter.NO_FILTER ); - var legs = getStr(alternativeLegs); + var legs = toString(alternativeLegs); var expected = String.join(", ", List.of("X ~ BUS 19 10:30 11:00 ~ B [C₁-1]")); assertEquals(expected, legs); } - private static String getStr(List alternativeLegs) { + private static String toString(List alternativeLegs) { return Itinerary.toStr( alternativeLegs .stream() From 07d1c5c7e0555546387160b3bce5c1c9a7dd79e7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 25 Sep 2024 10:22:41 +0200 Subject: [PATCH 17/35] Add setting for turning direct flex filter off --- doc/user/RouteRequest.md | 12 +++++++ .../ItineraryListFilterChainBuilder.java | 9 +++++ .../RouteRequestToFilterChainMapper.java | 1 + .../ItineraryFilterPreferences.java | 33 ++++++++++--------- .../config/framework/json/OtpVersion.java | 3 +- .../routerequest/ItineraryFiltersConfig.java | 14 ++++++++ .../ItineraryFilterPreferencesTest.java | 8 +++-- 7 files changed, 62 insertions(+), 18 deletions(-) diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index c00502b2726..2aa9325e603 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -111,6 +111,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | |    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | |    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | +|    [filterDirectFlexByEarliestDeparture](#rd_if_filterDirectFlexByEarliestDeparture) | `boolean` | Filter direct flex results by the earliestDepartureTime of the search window. | *Optional* | `true` | 2.7 | |    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | |    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | |    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | @@ -731,6 +732,17 @@ convenient when tuning the itinerary-filter-chain. moving to the next page. +

    filterDirectFlexByEarliestDeparture

    + +**Since version:** `2.7` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `true` +**Path:** /routingDefaults/itineraryFilters + +Filter direct flex results by the earliestDepartureTime of the search window. + + When direct flex is mixed with a transit search in the same request, then the direct flex results can be filtered by the + earliest departure time of the transit search window. + +

    filterItinerariesWithSameFirstOrLastTrip

    **Since version:** `2.2` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `false` 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 7cffe128a99..764ad69b59e 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -90,6 +90,7 @@ public class ItineraryListFilterChainBuilder { private boolean removeTransitIfWalkingIsBetter = true; private ItinerarySortKey itineraryPageCut; private boolean transitGroupPriorityUsed = false; + private boolean filterDirectFlexByEarliestDeparture = true; /** * Sandbox filters which decorate the itineraries with extra information. @@ -469,6 +470,9 @@ public ItineraryListFilterChain build() { filters, new OutsideSearchWindowFilter(earliestDepartureTime, searchWindow) ); + } + + if (earliestDepartureTime != null && filterDirectFlexByEarliestDeparture) { addRemoveFilter(filters, new FlexSearchWindowFilter(earliestDepartureTime)); } @@ -535,6 +539,11 @@ public ItineraryListFilterChain build() { return new ItineraryListFilterChain(filters, debugHandler); } + public ItineraryListFilterChainBuilder withFilterDirectFlexByEarliestDeparture(boolean b) { + this.filterDirectFlexByEarliestDeparture = b; + return this; + } + /** * If enabled, this adds the filter to remove itineraries which have the same stops and routes. * These are sometimes called "time-shifted duplicates" but since those terms have so many diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index c1fab68f999..9af40f630db 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -92,6 +92,7 @@ public static ItineraryListFilterChain createFilterChain( .withPageCursorInputSubscriber(pageCursorInputSubscriber) .withRemoveWalkAllTheWayResults(removeWalkAllTheWayResults) .withRemoveTransitIfWalkingIsBetter(true) + .withFilterDirectFlexByEarliestDeparture(params.filterDirectFlexByEarliestDeparture()) .withDebugEnabled(params.debug()); if (!request.preferences().transit().relaxTransitGroupPriority().isNormal()) { diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index 91f3071d4ed..043ccf89b10 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -31,7 +31,7 @@ public final class ItineraryFilterPreferences { private final boolean removeItinerariesWithSameRoutesAndStops; private final TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; private final CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly; - private final boolean removeTransitIfWalkingIsBetter; + private final boolean filterDirectFlexByEarliestDeparture; private ItineraryFilterPreferences() { this.accessibilityScore = false; @@ -52,7 +52,7 @@ private ItineraryFilterPreferences() { ); this.removeTransitWithHigherCostThanBestOnStreetOnly = CostLinearFunction.of(Duration.ofMinutes(1), 1.3); - this.removeTransitIfWalkingIsBetter = false; + this.filterDirectFlexByEarliestDeparture = true; } private ItineraryFilterPreferences(Builder builder) { @@ -73,7 +73,7 @@ private ItineraryFilterPreferences(Builder builder) { this.transitGeneralizedCostLimit = Objects.requireNonNull(builder.transitGeneralizedCostLimit); this.removeTransitWithHigherCostThanBestOnStreetOnly = Objects.requireNonNull(builder.removeTransitWithHigherCostThanBestOnStreetOnly); - this.removeTransitIfWalkingIsBetter = builder.removeTransitIfWalkingIsBetter; + this.filterDirectFlexByEarliestDeparture = builder.filterDirectFlexByEarliestDeparture; } public static Builder of() { @@ -136,8 +136,8 @@ public CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly() { return removeTransitWithHigherCostThanBestOnStreetOnly; } - public boolean removeTransitIfWalkingIsBetter() { - return removeTransitIfWalkingIsBetter; + public boolean filterDirectFlexByEarliestDeparture() { + return filterDirectFlexByEarliestDeparture; } @Override @@ -187,7 +187,7 @@ public String toString() { "removeItinerariesWithSameRoutesAndStops", removeItinerariesWithSameRoutesAndStops ) - .addBoolIfTrue("removeTransitIfWalkingIsBetter", removeTransitIfWalkingIsBetter) + .addBoolIfTrue("filterDirectFlexByEarliestDeparture", filterDirectFlexByEarliestDeparture) .toString(); } @@ -200,7 +200,6 @@ public boolean equals(Object o) { accessibilityScore == that.accessibilityScore && Double.compare(that.bikeRentalDistanceRatio, bikeRentalDistanceRatio) == 0 && debug == that.debug && - removeTransitIfWalkingIsBetter == that.removeTransitIfWalkingIsBetter && filterItinerariesWithSameFirstOrLastTrip == that.filterItinerariesWithSameFirstOrLastTrip && Double.compare( that.groupedOtherThanSameLegsMaxCostMultiplier, @@ -217,7 +216,8 @@ public boolean equals(Object o) { removeTransitWithHigherCostThanBestOnStreetOnly, that.removeTransitWithHigherCostThanBestOnStreetOnly ) && - Objects.equals(transitGeneralizedCostLimit, that.transitGeneralizedCostLimit) + Objects.equals(transitGeneralizedCostLimit, that.transitGeneralizedCostLimit) && + filterDirectFlexByEarliestDeparture == that.filterDirectFlexByEarliestDeparture ); } @@ -237,7 +237,7 @@ public int hashCode() { removeItinerariesWithSameRoutesAndStops, transitGeneralizedCostLimit, removeTransitWithHigherCostThanBestOnStreetOnly, - removeTransitIfWalkingIsBetter + filterDirectFlexByEarliestDeparture ); } @@ -258,6 +258,7 @@ public static class Builder { private TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; private CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly; private boolean removeTransitIfWalkingIsBetter; + private boolean filterDirectFlexByEarliestDeparture; public ItineraryFilterPreferences original() { return original; @@ -341,11 +342,6 @@ public Builder withRemoveTransitWithHigherCostThanBestOnStreetOnly( return this; } - public Builder withRemoveTransitIfWalkingIsBetter(boolean removeTransitIfWalkingIsBetter) { - this.removeTransitIfWalkingIsBetter = removeTransitIfWalkingIsBetter; - return this; - } - public Builder(ItineraryFilterPreferences original) { this.original = original; this.accessibilityScore = original.accessibilityScore; @@ -365,7 +361,7 @@ public Builder(ItineraryFilterPreferences original) { this.transitGeneralizedCostLimit = original.transitGeneralizedCostLimit; this.removeTransitWithHigherCostThanBestOnStreetOnly = original.removeTransitWithHigherCostThanBestOnStreetOnly; - this.removeTransitIfWalkingIsBetter = original.removeTransitIfWalkingIsBetter; + this.filterDirectFlexByEarliestDeparture = original.filterDirectFlexByEarliestDeparture; } public Builder apply(Consumer body) { @@ -377,5 +373,12 @@ public ItineraryFilterPreferences build() { var value = new ItineraryFilterPreferences(this); return original.equals(value) ? original : value; } + + public Builder withFilterDirectFlexByEarliestDeparture( + boolean filterDirectFlexByEarliestDeparture + ) { + this.filterDirectFlexByEarliestDeparture = filterDirectFlexByEarliestDeparture; + return this; + } } } diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java index 70b8e261ee4..ea9dbb4d6ba 100644 --- a/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java +++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/OtpVersion.java @@ -11,7 +11,8 @@ public enum OtpVersion { V2_3("2.3"), V2_4("2.4"), V2_5("2.5"), - V2_6("2.6"); + V2_6("2.6"), + V2_7("2.7"); private final String text; diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java index 3a58bfe9bcd..7b497c059f9 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java @@ -6,6 +6,7 @@ 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_4; +import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7; import org.opentripplanner.routing.algorithm.filterchain.api.TransitGeneralizedCostFilterParams; import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; @@ -261,6 +262,19 @@ public static void mapItineraryFilterParams( ) .asDouble(dft.minBikeParkingDistance()) ) + .withFilterDirectFlexByEarliestDeparture( + c + .of("filterDirectFlexByEarliestDeparture") + .since(V2_7) + .summary("Filter direct flex results by the earliestDepartureTime of the search window.") + .description( + """ + When direct flex is mixed with a transit search in the same request, then the direct flex results can be filtered by the + earliest departure time of the transit search window. + """ + ) + .asBoolean(true) + ) .build(); } diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java index f59586b0af9..fa19296777b 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java @@ -133,7 +133,10 @@ void testCopyOfEqualsAndHashCode() { @Test void testToString() { - assertEquals("ItineraryFilterPreferences{}", ItineraryFilterPreferences.DEFAULT.toString()); + assertEquals( + "ItineraryFilterPreferences{filterDirectFlexByEarliestDeparture}", + ItineraryFilterPreferences.DEFAULT.toString() + ); assertEquals( "ItineraryFilterPreferences{" + "accessibilityScore, " + @@ -147,7 +150,8 @@ void testToString() { "nonTransitGeneralizedCostLimit: 4s + 5.0 t, " + "parkAndRideDurationRatio: 0.44, " + "transitGeneralizedCostLimit: TransitGeneralizedCostFilterParams[costLimitFunction=4s + 5.0 t, intervalRelaxFactor=3.0], " + - "removeTransitWithHigherCostThanBestOnStreetOnly: 30s + 1.30 t" + + "removeTransitWithHigherCostThanBestOnStreetOnly: 30s + 1.30 t, " + + "filterDirectFlexByEarliestDeparture" + "}", subject.toString() ); From d1961b468b526c7a7e14bbf1245015a4b3e394a3 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 25 Sep 2024 11:08:34 +0200 Subject: [PATCH 18/35] Add test for switching filter off --- .../ItineraryListFilterChainTest.java | 71 ++++++++++++++----- .../ItineraryFilterPreferencesTest.java | 6 +- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java index d897c948e6d..29b65302a23 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java @@ -42,7 +42,7 @@ * This class test the whole filter chain with a few test cases. Each filter should be tested with a * unit test. This is just a some test on top of the other filter unit-tests. */ -public class ItineraryListFilterChainTest implements PlanTestConstants { +class ItineraryListFilterChainTest implements PlanTestConstants { private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of(); private static final Place A = Place.forStop(TEST_MODEL.stop("A").build()); @@ -59,7 +59,7 @@ public class ItineraryListFilterChainTest implements PlanTestConstants { private Itinerary i3; @BeforeEach - public void setUpItineraries() { + void setUpItineraries() { // Add some itineraries, with some none optimal options // Short walk - 2 minutes - to destination: i1 = newItinerary(A, T11_06).walk(D2m, E).build(); @@ -72,7 +72,7 @@ public void setUpItineraries() { } @Test - public void testDefaultFilterChain() { + void testDefaultFilterChain() { // Given a default chain ItineraryListFilterChain chain = createBuilder(false, false, 10).build(); @@ -80,7 +80,7 @@ public void testDefaultFilterChain() { } @Test - public void testFilterChainWithSearchWindowFilterSet() { + void testFilterChainWithSearchWindowFilterSet() { ItineraryListFilterChain chain = createBuilder(false, false, 10) .withSearchWindow(TestItineraryBuilder.newTime(T11_00).toInstant(), SW_D10m) .build(); @@ -89,7 +89,7 @@ public void testFilterChainWithSearchWindowFilterSet() { } @Test - public void withMinBikeParkingDistance() { + void withMinBikeParkingDistance() { // Given a "default" chain ItineraryListFilterChain chain = createBuilder(false, false, 10) .withMinBikeParkingDistance(500) @@ -105,7 +105,7 @@ public void withMinBikeParkingDistance() { } @Test - public void testDebugFilterChain() { + void testDebugFilterChain() { // Given a filter-chain with debugging enabled ItineraryListFilterChain chain = createBuilder(false, true, 3) .withSearchWindow(newTime(T11_00).toInstant(), SW_D10m) @@ -122,7 +122,7 @@ public void testDebugFilterChain() { } @Test - public void removeAllWalkingOnly() { + void removeAllWalkingOnly() { ItineraryListFilterChain chain = createBuilder(false, false, 20) .withRemoveWalkAllTheWayResults(true) .build(); @@ -134,7 +134,7 @@ public void removeAllWalkingOnly() { } @Test - public void groupByTheLongestItineraryAndTwoGroups() { + void groupByTheLongestItineraryAndTwoGroups() { ItineraryListFilterChain chain = createBuilder(false, false, 20) .addGroupBySimilarity(GroupBySimilarity.createWithOneItineraryPerGroup(.5)) .build(); @@ -157,7 +157,7 @@ public void groupByTheLongestItineraryAndTwoGroups() { } @Test - public void testSameFirstOrLastTripFilter() { + void testSameFirstOrLastTripFilter() { ItineraryListFilterChain chain = createBuilder(false, false, 20) .withSameFirstOrLastTripFilter(true) .build(); @@ -238,7 +238,7 @@ void transitAlertsTest() { } @Test - public void removeItinerariesWithSameRoutesAndStops() { + void removeItinerariesWithSameRoutesAndStops() { var i1 = newItinerary(A).bus(21, T11_06, T11_28, E).bus(41, T11_30, T11_32, D).build(); var i2 = newItinerary(A).bus(22, T11_09, T11_30, E).bus(42, T11_32, T11_33, D).build(); var i3 = newItinerary(A).bus(23, T11_10, T11_32, E).bus(43, T11_33, T11_50, D).build(); @@ -274,34 +274,67 @@ private ItineraryListFilterChainBuilder createBuilder( class MaxItinerariesBuilderTest { @BeforeEach - public void setUpItineraries() { + void setUpItineraries() { i1 = newItinerary(A).bus(21, T11_05, T11_10, E).build(); i2 = newItinerary(A).bus(31, T11_07, T11_12, E).build(); i3 = newItinerary(A).bus(41, T11_09, T11_14, E).build(); } @Test - public void testPostProcessorWithMaxItinerariesFilterSetToTwo() { + void testPostProcessorWithMaxItinerariesFilterSetToTwo() { // Given a default postProcessor with 'numOfItineraries=2' ItineraryListFilterChain chain = createBuilder(false, false, 2).build(); assertEquals(List.of(i1, i2), chain.filter(List.of(i1, i2, i3))); } @Test - public void testPostProcessorWithMaxItinerariesFilterSetToOneDepartAt() { + void testPostProcessorWithMaxItinerariesFilterSetToOneDepartAt() { // Given a default postProcessor with 'numOfItineraries=1' ItineraryListFilterChain chain = createBuilder(false, false, 1).build(); assertEquals(List.of(i1), chain.filter(List.of(i1, i2, i3))); } @Test - public void testPostProcessorWithMaxItinerariesFilterSetToOneArriveBy() { + void testPostProcessorWithMaxItinerariesFilterSetToOneArriveBy() { // Given a postProcessor with 'numOfItineraries=1' and 'arriveBy=true' ItineraryListFilterChain chain = createBuilder(true, false, 1).build(); assertEquals(List.of(i3), chain.filter(List.of(i1, i2, i3))); } } + @Nested + class FlexSearchWindow { + + private static final Itinerary FLEX = newItinerary(A, T11_00) + .flex(T11_00, T11_30, B) + .withIsSearchWindowAware(false) + .build(); + private static final Instant EARLIEST_DEPARTURE = FLEX.startTime().plusMinutes(10).toInstant(); + private static final Duration SEARCH_WINDOW = Duration.ofHours(7); + + /** + * When the filtering of direct flex by the transit search window is deactivated, the direct + * flex result should _not_ be filtered even though it starts before the earliest departure time. + */ + @Test + void keepDirectFlexWhenFilteringByEarliestDepartureIsDisabled() { + ItineraryListFilterChain chain = createBuilder(true, false, 10) + .withFilterDirectFlexByEarliestDeparture(false) + .withSearchWindow(EARLIEST_DEPARTURE, SEARCH_WINDOW) + .build(); + assertEquals(toStr(List.of(FLEX)), toStr(chain.filter(List.of(FLEX)))); + } + + @Test + void removeDirectFlexWhenFilteringByEarliestDepartureIsEnabled() { + ItineraryListFilterChain chain = createBuilder(true, false, 10) + .withFilterDirectFlexByEarliestDeparture(true) + .withSearchWindow(EARLIEST_DEPARTURE, SEARCH_WINDOW) + .build(); + assertEquals(toStr(List.of()), toStr(chain.filter(List.of(FLEX)))); + } + } + @Nested class RemoveTransitWithHigherCostThanBestOnStreetOnlyTest { @@ -310,7 +343,7 @@ class RemoveTransitWithHigherCostThanBestOnStreetOnlyTest { ItineraryListFilterChainBuilder builder = createBuilder(true, false, 20); @BeforeEach - public void setUpItineraries() { + void setUpItineraries() { // given // Walk for 12 minute walk = newItinerary(A, T11_06).walk(D12m, E).build(); @@ -319,7 +352,7 @@ public void setUpItineraries() { } @Test - public void removeTransitWithHigherCostThanBestOnStreetOnlyDisabled() { + void removeTransitWithHigherCostThanBestOnStreetOnlyDisabled() { // Allow non-optimal bus itinerary pass through ItineraryListFilterChain chain = builder .withRemoveTransitWithHigherCostThanBestOnStreetOnly(null) @@ -329,7 +362,7 @@ public void removeTransitWithHigherCostThanBestOnStreetOnlyDisabled() { } @Test - public void removeTransitWithHigherCostThanBestOnStreetOnlyEnabled() { + void removeTransitWithHigherCostThanBestOnStreetOnlyEnabled() { // Enable filter and remove bus itinerary ItineraryListFilterChain chain = builder .withRemoveTransitWithHigherCostThanBestOnStreetOnly( @@ -355,7 +388,7 @@ class AddEmissionsToItineraryTest { EmissionsService eService; @BeforeEach - public void setUpItineraries() { + void setUpItineraries() { bus = newItinerary(A).bus(21, T11_06, T11_09, B).build(); car = newItinerary(A).drive(T11_30, T11_50, B).build(); Map emissions = new HashMap<>(); @@ -364,7 +397,7 @@ public void setUpItineraries() { } @Test - public void emissionsTest() { + void emissionsTest() { ItineraryListFilterChain chain = builder .withEmissions(new DecorateWithEmission(eService)) .build(); diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java index fa19296777b..0b73a1062e6 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java @@ -127,7 +127,11 @@ void testCopyOfEqualsAndHashCode() { // Create a copy, make a change and set it back again to force creating a new object var other = subject.copyOf().withGroupSimilarityKeepOne(0.95).build(); - var same = other.copyOf().withGroupSimilarityKeepOne(GROUP_SIMILARITY_KEEP_ONE).build(); + var same = other + .copyOf() + .withGroupSimilarityKeepOne(GROUP_SIMILARITY_KEEP_ONE) + .withFilterDirectFlexByEarliestDeparture(true) + .build(); assertEqualsAndHashCode(subject, other, same); } From 744017f19d04fa0d7beb0a7d577268561b0b018d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 25 Sep 2024 12:53:44 +0200 Subject: [PATCH 19/35] Improve documentation --- doc/user/RouteRequest.md | 6 ++++-- .../api/request/preference/ItineraryFilterPreferences.java | 1 - .../config/routerequest/ItineraryFiltersConfig.java | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 2aa9325e603..94fc825007e 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -739,8 +739,10 @@ convenient when tuning the itinerary-filter-chain. Filter direct flex results by the earliestDepartureTime of the search window. - When direct flex is mixed with a transit search in the same request, then the direct flex results can be filtered by the - earliest departure time of the transit search window. +When direct flex is mixed with a transit search in the same request, then the direct flex results are filtered by the +earliest departure time of the transit search window. + +Use this configuration to turn this feature off.

    filterItinerariesWithSameFirstOrLastTrip

    diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index 043ccf89b10..da99634c428 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -257,7 +257,6 @@ public static class Builder { private boolean removeItinerariesWithSameRoutesAndStops; private TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; private CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly; - private boolean removeTransitIfWalkingIsBetter; private boolean filterDirectFlexByEarliestDeparture; public ItineraryFilterPreferences original() { diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java index 7b497c059f9..0e449e9820c 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java @@ -269,8 +269,10 @@ public static void mapItineraryFilterParams( .summary("Filter direct flex results by the earliestDepartureTime of the search window.") .description( """ - When direct flex is mixed with a transit search in the same request, then the direct flex results can be filtered by the - earliest departure time of the transit search window. + When direct flex is mixed with a transit search in the same request, then the direct flex results are filtered by the + earliest departure time of the transit search window. + + Use this configuration to turn this feature off. """ ) .asBoolean(true) From b64c5da0694bb8dfd4394cb7b91132a3dc997399 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 27 Sep 2024 16:17:44 +0200 Subject: [PATCH 20/35] Update src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java Co-authored-by: Joel Lappalainen --- .../filterchain/filters/system/OutsideSearchWindowFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java index 5f375c9f4d9..5ab6834437b 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/OutsideSearchWindowFilter.java @@ -8,7 +8,7 @@ /** * This filter will remove all itineraries that are both search-window aware and outside the - * search-window. Only those that use transit are search-window, street and flex itineraries are not. + * search-window. Only those that use transit are search-window aware, street and flex itineraries are not. *

    * In some cases the access is time-shifted after the end of the search-window. These results * should appear again when paging to the next page. Hence, this filter will remove From aca7ccdc107d6528d0472bc94d25512331937303 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 27 Sep 2024 16:19:36 +0200 Subject: [PATCH 21/35] Update src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java Co-authored-by: Joel Lappalainen --- .../router/FilterTransitWhenDirectModeIsEmpty.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java index fc75337b7d0..a6e1ed462ea 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java @@ -10,7 +10,7 @@ * ´directMode´, OTP does not do the streetSearch. And, the removal of non-optimal results is not * done, there is not street results to use to prune bad transit results with. In other words OTP is * forced to return at least one itinerary with at least one transit leg. So, instead of walking - * maybe 100 meters, the OTP suggest you need to walk to the closest bus stop, take the bus one + * maybe 100 meters, OTP suggests you need to walk to the closest bus stop, take the bus for one * stop and walk back, often with more walking than just those 100 meters. *

    * Let say OTP produces these internal results: From 442840b35ac23999070bb1c71e3a26a0d4c25fc1 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 27 Sep 2024 16:22:35 +0200 Subject: [PATCH 22/35] Apply suggestions from code review Co-authored-by: Joel Lappalainen --- .../router/FilterTransitWhenDirectModeIsEmpty.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java index a6e1ed462ea..bf14cee0bb9 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/FilterTransitWhenDirectModeIsEmpty.java @@ -5,10 +5,10 @@ /** *

    - * In OTP the street search and transit search is done as to separate searches. The results is then + * In OTP, the street search and transit search are done separately. The results are then * merged and filtered to remove non-optimal itineraries. But, when the client does NOT provide a * ´directMode´, OTP does not do the streetSearch. And, the removal of non-optimal results is not - * done, there is not street results to use to prune bad transit results with. In other words OTP is + * done, there are no street results to use to prune bad transit results with. In other words, OTP is * forced to return at least one itinerary with at least one transit leg. So, instead of walking * maybe 100 meters, OTP suggests you need to walk to the closest bus stop, take the bus for one * stop and walk back, often with more walking than just those 100 meters. From aa153b773955ada20efea14bff747d209ef453a2 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 30 Sep 2024 08:53:19 +0200 Subject: [PATCH 23/35] Check if itinerary contains flex --- .../java/org/opentripplanner/model/plan/Itinerary.java | 4 +++- .../java/org/opentripplanner/model/plan/ItineraryTest.java | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/src/main/java/org/opentripplanner/model/plan/Itinerary.java index a928e0c5fff..c3d8513c2f1 100644 --- a/src/main/java/org/opentripplanner/model/plan/Itinerary.java +++ b/src/main/java/org/opentripplanner/model/plan/Itinerary.java @@ -183,7 +183,9 @@ public boolean isOnStreetAllTheWay() { * Returns true if this itinerary has only flex and walking legs. */ public boolean isDirectFlex() { - return legs.stream().allMatch(l -> l.isFlexibleTrip() || l.isWalkingLeg()); + var containsFlex = legs.stream().anyMatch(Leg::isFlexibleTrip); + var flexOrWalkOnly = legs.stream().allMatch(l -> l.isFlexibleTrip() || l.isWalkingLeg()); + return containsFlex && flexOrWalkOnly; } /** TRUE if at least one leg is a transit leg. */ diff --git a/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java b/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java index e05273b8a68..1372af0f405 100644 --- a/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java +++ b/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java @@ -259,11 +259,16 @@ void bothPenalties() { } @Test - void flexAndWalk() { + void directFlex() { assertFalse(itinerary().isDirectFlex()); assertTrue(newItinerary(A).flex(T11_10, T11_20, B).build().isDirectFlex()); } + @Test + void walkOnlyIsNotDirectFlex() { + assertFalse(TestItineraryBuilder.newItinerary(A, T11_00).walk(10, B).build().isDirectFlex()); + } + private static Itinerary itinerary() { return newItinerary(A).bus(1, T11_04, T11_14, B).build(); } From ac53bc05012ad45f26258f1d376aff977b91f139 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 30 Sep 2024 08:57:31 +0200 Subject: [PATCH 24/35] Fix time notation in test --- .../filters/system/FlexSearchWindowFilterTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 c3989ef8d0e..65f18405ea6 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 @@ -14,11 +14,11 @@ class FlexSearchWindowFilterTest implements PlanTestConstants { private static final Instant LATEST_DEPARTURE_TIME = TestItineraryBuilder - .newTime(time("9:20")) + .newTime(time("09:20")) .toInstant(); @ParameterizedTest - @ValueSource(strings = { "9:20", "9:21", "13:20" }) + @ValueSource(strings = { "09:20", "09:21", "13:20" }) void keepFlexItinerariesAfterLDT(String startTime) { var edt = "9:20"; var subject = new FlexSearchWindowFilter(TestItineraryBuilder.newTime(time(edt)).toInstant()); @@ -32,7 +32,7 @@ void keepFlexItinerariesAfterLDT(String startTime) { } @ParameterizedTest - @ValueSource(strings = { "0:0", "0:01", "9:19" }) + @ValueSource(strings = { "00:00", "00:01", "09:19" }) void removeFlexItinerariesBeforeLDT(String startTime) { var subject = new FlexSearchWindowFilter(LATEST_DEPARTURE_TIME); From 9ec11be9e8b85947829f71b371e04dd40b084b54 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 30 Sep 2024 11:23:08 +0200 Subject: [PATCH 25/35] Fine-tune documentation --- doc/user/RouteRequest.md | 4 ++-- .../config/routerequest/ItineraryFiltersConfig.java | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 94fc825007e..465fb7f4a11 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -111,7 +111,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | |    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | |    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | -|    [filterDirectFlexByEarliestDeparture](#rd_if_filterDirectFlexByEarliestDeparture) | `boolean` | Filter direct flex results by the earliestDepartureTime of the search window. | *Optional* | `true` | 2.7 | +|    [filterDirectFlexByEarliestDeparture](#rd_if_filterDirectFlexByEarliestDeparture) | `boolean` | Filter direct flex results by the earliest-departure-time of the search window. | *Optional* | `true` | 2.7 | |    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | |    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | |    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | @@ -737,7 +737,7 @@ convenient when tuning the itinerary-filter-chain. **Since version:** `2.7` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `true` **Path:** /routingDefaults/itineraryFilters -Filter direct flex results by the earliestDepartureTime of the search window. +Filter direct flex results by the earliest-departure-time of the search window. When direct flex is mixed with a transit search in the same request, then the direct flex results are filtered by the earliest departure time of the transit search window. diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java index 0e449e9820c..5e1c5b64789 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java @@ -266,7 +266,9 @@ public static void mapItineraryFilterParams( c .of("filterDirectFlexByEarliestDeparture") .since(V2_7) - .summary("Filter direct flex results by the earliestDepartureTime of the search window.") + .summary( + "Filter direct flex results by the earliest-departure-time of the search window." + ) .description( """ When direct flex is mixed with a transit search in the same request, then the direct flex results are filtered by the From fca7ba96ed19594356bf2f7096e2123f671176b9 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 10 Oct 2024 15:49:23 +0200 Subject: [PATCH 26/35] Filter flex resuls also by sort order --- .../opentripplanner/model/plan/SortOrder.java | 12 +++++++ .../ItineraryListFilterChainBuilder.java | 5 ++- .../system/FlexSearchWindowFilter.java | 19 ++++++++-- .../system/FlexSearchWindowFilterTest.java | 36 ++++++++++++++++--- 4 files changed, 64 insertions(+), 8 deletions(-) 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)); + } } From 9e293e844f6e2b5480f883fa509746bc54e23fe4 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 10 Oct 2024 15:56:47 +0200 Subject: [PATCH 27/35] Update src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilter.java Co-authored-by: Thomas Gran --- .../filterchain/filters/system/FlexSearchWindowFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 030bdfa7b78..9684a5d64d7 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 @@ -17,7 +17,7 @@ */ public class FlexSearchWindowFilter implements RemoveItineraryFlagger { - public static final String TAG = "outside-flex-window"; + public static final String TAG = "flex-outside-search-window"; private final Instant earliestDepartureTime; private final Instant latestArrivalTime; From 483a04f1ef1fd31c0d0f50500ee362ce858199c7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 10 Oct 2024 16:05:42 +0200 Subject: [PATCH 28/35] Add comment Add comment Add comment --- .../filterchain/filters/system/FlexSearchWindowFilter.java | 2 ++ 1 file changed, 2 insertions(+) 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 9684a5d64d7..336bf97b1a0 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 @@ -42,9 +42,11 @@ public String name() { public Predicate shouldBeFlaggedForRemoval() { return it -> { if (it.isDirectFlex() && sortOrder.isSortedByDescendingDepartureTime()) { + // arive by var time = it.startTime().toInstant(); return time.isBefore(earliestDepartureTime); } else if (it.isDirectFlex() && sortOrder.isSortedByAscendingArrivalTime()) { + // depart at var time = it.startTime().toInstant(); return time.isAfter(latestArrivalTime); } else { From aa7334e26b505156e3514727b213ecb522ed2f27 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 10 Oct 2024 16:50:03 +0200 Subject: [PATCH 29/35] Rename config parameter --- doc/user/RouteRequest.md | 4 +-- .../ItineraryListFilterChainBuilder.java | 8 +++--- .../RouteRequestToFilterChainMapper.java | 2 +- .../ItineraryFilterPreferences.java | 26 +++++++++---------- .../routerequest/ItineraryFiltersConfig.java | 13 +++++----- .../ItineraryListFilterChainTest.java | 4 +-- .../ItineraryFilterPreferencesTest.java | 6 ++--- 7 files changed, 31 insertions(+), 32 deletions(-) diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 465fb7f4a11..5d649d53f3a 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -111,7 +111,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | |    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | |    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | -|    [filterDirectFlexByEarliestDeparture](#rd_if_filterDirectFlexByEarliestDeparture) | `boolean` | Filter direct flex results by the earliest-departure-time of the search window. | *Optional* | `true` | 2.7 | +|    [filterDirectFlexBySearchWindow](#rd_if_filterDirectFlexBySearchWindow) | `boolean` | Filter direct flex results by the earliest-departure-time of the search window. | *Optional* | `true` | 2.7 | |    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | |    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | |    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | @@ -732,7 +732,7 @@ convenient when tuning the itinerary-filter-chain. moving to the next page. -

    filterDirectFlexByEarliestDeparture

    +

    filterDirectFlexBySearchWindow

    **Since version:** `2.7` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `true` **Path:** /routingDefaults/itineraryFilters 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 6d3d17d8299..3ff85db467a 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainBuilder.java @@ -90,7 +90,7 @@ public class ItineraryListFilterChainBuilder { private boolean removeTransitIfWalkingIsBetter = true; private ItinerarySortKey itineraryPageCut; private boolean transitGroupPriorityUsed = false; - private boolean filterDirectFlexByEarliestDeparture = true; + private boolean filterDirectFlexBySearchWindow = true; /** * Sandbox filters which decorate the itineraries with extra information. @@ -472,7 +472,7 @@ public ItineraryListFilterChain build() { ); } - if (earliestDepartureTime != null && filterDirectFlexByEarliestDeparture) { + if (earliestDepartureTime != null && filterDirectFlexBySearchWindow) { addRemoveFilter( filters, new FlexSearchWindowFilter(earliestDepartureTime, searchWindow, sortOrder) @@ -542,8 +542,8 @@ public ItineraryListFilterChain build() { return new ItineraryListFilterChain(filters, debugHandler); } - public ItineraryListFilterChainBuilder withFilterDirectFlexByEarliestDeparture(boolean b) { - this.filterDirectFlexByEarliestDeparture = b; + public ItineraryListFilterChainBuilder withFilterDirectFlexBySearchWindow(boolean b) { + this.filterDirectFlexBySearchWindow = b; return this; } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java index 9af40f630db..01cba0dd47f 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RouteRequestToFilterChainMapper.java @@ -92,7 +92,7 @@ public static ItineraryListFilterChain createFilterChain( .withPageCursorInputSubscriber(pageCursorInputSubscriber) .withRemoveWalkAllTheWayResults(removeWalkAllTheWayResults) .withRemoveTransitIfWalkingIsBetter(true) - .withFilterDirectFlexByEarliestDeparture(params.filterDirectFlexByEarliestDeparture()) + .withFilterDirectFlexBySearchWindow(params.filterDirectFlexBySearchWindow()) .withDebugEnabled(params.debug()); if (!request.preferences().transit().relaxTransitGroupPriority().isNormal()) { diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java index da99634c428..31fa7edd139 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java +++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferences.java @@ -31,7 +31,7 @@ public final class ItineraryFilterPreferences { private final boolean removeItinerariesWithSameRoutesAndStops; private final TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; private final CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly; - private final boolean filterDirectFlexByEarliestDeparture; + private final boolean filterDirectFlexBySearchWindow; private ItineraryFilterPreferences() { this.accessibilityScore = false; @@ -52,7 +52,7 @@ private ItineraryFilterPreferences() { ); this.removeTransitWithHigherCostThanBestOnStreetOnly = CostLinearFunction.of(Duration.ofMinutes(1), 1.3); - this.filterDirectFlexByEarliestDeparture = true; + this.filterDirectFlexBySearchWindow = true; } private ItineraryFilterPreferences(Builder builder) { @@ -73,7 +73,7 @@ private ItineraryFilterPreferences(Builder builder) { this.transitGeneralizedCostLimit = Objects.requireNonNull(builder.transitGeneralizedCostLimit); this.removeTransitWithHigherCostThanBestOnStreetOnly = Objects.requireNonNull(builder.removeTransitWithHigherCostThanBestOnStreetOnly); - this.filterDirectFlexByEarliestDeparture = builder.filterDirectFlexByEarliestDeparture; + this.filterDirectFlexBySearchWindow = builder.filterDirectFlexBySearchWindow; } public static Builder of() { @@ -136,8 +136,8 @@ public CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly() { return removeTransitWithHigherCostThanBestOnStreetOnly; } - public boolean filterDirectFlexByEarliestDeparture() { - return filterDirectFlexByEarliestDeparture; + public boolean filterDirectFlexBySearchWindow() { + return filterDirectFlexBySearchWindow; } @Override @@ -187,7 +187,7 @@ public String toString() { "removeItinerariesWithSameRoutesAndStops", removeItinerariesWithSameRoutesAndStops ) - .addBoolIfTrue("filterDirectFlexByEarliestDeparture", filterDirectFlexByEarliestDeparture) + .addBoolIfTrue("filterDirectFlexBySearchWindow", filterDirectFlexBySearchWindow) .toString(); } @@ -217,7 +217,7 @@ public boolean equals(Object o) { that.removeTransitWithHigherCostThanBestOnStreetOnly ) && Objects.equals(transitGeneralizedCostLimit, that.transitGeneralizedCostLimit) && - filterDirectFlexByEarliestDeparture == that.filterDirectFlexByEarliestDeparture + filterDirectFlexBySearchWindow == that.filterDirectFlexBySearchWindow ); } @@ -237,7 +237,7 @@ public int hashCode() { removeItinerariesWithSameRoutesAndStops, transitGeneralizedCostLimit, removeTransitWithHigherCostThanBestOnStreetOnly, - filterDirectFlexByEarliestDeparture + filterDirectFlexBySearchWindow ); } @@ -257,7 +257,7 @@ public static class Builder { private boolean removeItinerariesWithSameRoutesAndStops; private TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; private CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly; - private boolean filterDirectFlexByEarliestDeparture; + private boolean filterDirectFlexBySearchWindow; public ItineraryFilterPreferences original() { return original; @@ -360,7 +360,7 @@ public Builder(ItineraryFilterPreferences original) { this.transitGeneralizedCostLimit = original.transitGeneralizedCostLimit; this.removeTransitWithHigherCostThanBestOnStreetOnly = original.removeTransitWithHigherCostThanBestOnStreetOnly; - this.filterDirectFlexByEarliestDeparture = original.filterDirectFlexByEarliestDeparture; + this.filterDirectFlexBySearchWindow = original.filterDirectFlexBySearchWindow; } public Builder apply(Consumer body) { @@ -373,10 +373,8 @@ public ItineraryFilterPreferences build() { return original.equals(value) ? original : value; } - public Builder withFilterDirectFlexByEarliestDeparture( - boolean filterDirectFlexByEarliestDeparture - ) { - this.filterDirectFlexByEarliestDeparture = filterDirectFlexByEarliestDeparture; + public Builder withFilterDirectFlexBySearchWindow(boolean filterDirectFlexBySearchWindow) { + this.filterDirectFlexBySearchWindow = filterDirectFlexBySearchWindow; return this; } } diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java index 5e1c5b64789..80ca41c366b 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java @@ -262,17 +262,18 @@ public static void mapItineraryFilterParams( ) .asDouble(dft.minBikeParkingDistance()) ) - .withFilterDirectFlexByEarliestDeparture( + .withFilterDirectFlexBySearchWindow( c - .of("filterDirectFlexByEarliestDeparture") + .of("filterDirectFlexBySearchWindow") .since(V2_7) - .summary( - "Filter direct flex results by the earliest-departure-time of the search window." - ) + .summary("Filter direct flex results by the search window.") .description( """ When direct flex is mixed with a transit search in the same request, then the direct flex results are filtered by the - earliest departure time of the transit search window. + search window of the transit results. + + Depart-at searches are filtered by latest-arrival-time and arrive-by searches are filtered + by earliest-departure-time and . Use this configuration to turn this feature off. """ diff --git a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java index 29b65302a23..2bf4afee7fb 100644 --- a/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java @@ -319,7 +319,7 @@ class FlexSearchWindow { @Test void keepDirectFlexWhenFilteringByEarliestDepartureIsDisabled() { ItineraryListFilterChain chain = createBuilder(true, false, 10) - .withFilterDirectFlexByEarliestDeparture(false) + .withFilterDirectFlexBySearchWindow(false) .withSearchWindow(EARLIEST_DEPARTURE, SEARCH_WINDOW) .build(); assertEquals(toStr(List.of(FLEX)), toStr(chain.filter(List.of(FLEX)))); @@ -328,7 +328,7 @@ void keepDirectFlexWhenFilteringByEarliestDepartureIsDisabled() { @Test void removeDirectFlexWhenFilteringByEarliestDepartureIsEnabled() { ItineraryListFilterChain chain = createBuilder(true, false, 10) - .withFilterDirectFlexByEarliestDeparture(true) + .withFilterDirectFlexBySearchWindow(true) .withSearchWindow(EARLIEST_DEPARTURE, SEARCH_WINDOW) .build(); assertEquals(toStr(List.of()), toStr(chain.filter(List.of(FLEX)))); diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java index 0b73a1062e6..4c1403b0b0f 100644 --- a/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java +++ b/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java @@ -130,7 +130,7 @@ void testCopyOfEqualsAndHashCode() { var same = other .copyOf() .withGroupSimilarityKeepOne(GROUP_SIMILARITY_KEEP_ONE) - .withFilterDirectFlexByEarliestDeparture(true) + .withFilterDirectFlexBySearchWindow(true) .build(); assertEqualsAndHashCode(subject, other, same); } @@ -138,7 +138,7 @@ void testCopyOfEqualsAndHashCode() { @Test void testToString() { assertEquals( - "ItineraryFilterPreferences{filterDirectFlexByEarliestDeparture}", + "ItineraryFilterPreferences{filterDirectFlexBySearchWindow}", ItineraryFilterPreferences.DEFAULT.toString() ); assertEquals( @@ -155,7 +155,7 @@ void testToString() { "parkAndRideDurationRatio: 0.44, " + "transitGeneralizedCostLimit: TransitGeneralizedCostFilterParams[costLimitFunction=4s + 5.0 t, intervalRelaxFactor=3.0], " + "removeTransitWithHigherCostThanBestOnStreetOnly: 30s + 1.30 t, " + - "filterDirectFlexByEarliestDeparture" + + "filterDirectFlexBySearchWindow" + "}", subject.toString() ); From 45574985f05c8ace9c8ddd78f1184f8020689c15 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 10 Oct 2024 16:55:41 +0200 Subject: [PATCH 30/35] Change logic of arrive by filters --- doc/user/RouteRequest.md | 9 +++++--- .../opentripplanner/model/plan/SortOrder.java | 12 ----------- .../system/FlexSearchWindowFilter.java | 21 ++++++++++++------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 5d649d53f3a..3573d453495 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -111,7 +111,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe |    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | |    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | |    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | -|    [filterDirectFlexBySearchWindow](#rd_if_filterDirectFlexBySearchWindow) | `boolean` | Filter direct flex results by the earliest-departure-time of the search window. | *Optional* | `true` | 2.7 | +|    [filterDirectFlexBySearchWindow](#rd_if_filterDirectFlexBySearchWindow) | `boolean` | Filter direct flex results by the search window. | *Optional* | `true` | 2.7 | |    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | |    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | |    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | @@ -737,10 +737,13 @@ convenient when tuning the itinerary-filter-chain. **Since version:** `2.7` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `true` **Path:** /routingDefaults/itineraryFilters -Filter direct flex results by the earliest-departure-time of the search window. +Filter direct flex results by the search window. When direct flex is mixed with a transit search in the same request, then the direct flex results are filtered by the -earliest departure time of the transit search window. +search window of the transit results. + +Depart-at searches are filtered by latest-arrival-time and arrive-by searches are filtered +by earliest-departure-time and . Use this configuration to turn this feature off. diff --git a/src/main/java/org/opentripplanner/model/plan/SortOrder.java b/src/main/java/org/opentripplanner/model/plan/SortOrder.java index 543460ac72d..342f963ba00 100644 --- a/src/main/java/org/opentripplanner/model/plan/SortOrder.java +++ b/src/main/java/org/opentripplanner/model/plan/SortOrder.java @@ -42,16 +42,4 @@ 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/filters/system/FlexSearchWindowFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilter.java index 336bf97b1a0..6185daf8475 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 @@ -41,14 +41,19 @@ public String name() { @Override public Predicate shouldBeFlaggedForRemoval() { return it -> { - if (it.isDirectFlex() && sortOrder.isSortedByDescendingDepartureTime()) { - // arive by - var time = it.startTime().toInstant(); - return time.isBefore(earliestDepartureTime); - } else if (it.isDirectFlex() && sortOrder.isSortedByAscendingArrivalTime()) { - // depart at - var time = it.startTime().toInstant(); - return time.isAfter(latestArrivalTime); + if (it.isDirectFlex()) { + return switch(sortOrder) { + case STREET_AND_DEPARTURE_TIME -> { + // arive by + var time = it.startTime().toInstant(); + yield time.isBefore(earliestDepartureTime); + } + case STREET_AND_ARRIVAL_TIME -> { + // depart at + var time = it.startTime().toInstant(); + yield time.isAfter(latestArrivalTime); + } + }; } else { return false; } From 4a519e4fdc963391f81d525b61ebcb1027a35d13 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 10 Oct 2024 17:04:46 +0200 Subject: [PATCH 31/35] Fix formatting --- .../filterchain/filters/system/FlexSearchWindowFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 6185daf8475..6a96efb2d85 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 @@ -42,8 +42,8 @@ public String name() { public Predicate shouldBeFlaggedForRemoval() { return it -> { if (it.isDirectFlex()) { - return switch(sortOrder) { - case STREET_AND_DEPARTURE_TIME -> { + return switch (sortOrder) { + case STREET_AND_DEPARTURE_TIME -> { // arive by var time = it.startTime().toInstant(); yield time.isBefore(earliestDepartureTime); From 3169cef2568e4810bd0df9dafbf03c2eb4271fdf Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 11 Oct 2024 09:15:50 +0200 Subject: [PATCH 32/35] Fix documentation --- doc/user/RouteRequest.md | 2 +- .../standalone/config/routerequest/ItineraryFiltersConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index 3573d453495..c652d7e6d96 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -743,7 +743,7 @@ When direct flex is mixed with a transit search in the same request, then the di search window of the transit results. Depart-at searches are filtered by latest-arrival-time and arrive-by searches are filtered -by earliest-departure-time and . +by earliest-departure-time. Use this configuration to turn this feature off. diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java index 80ca41c366b..d66aa4196e0 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java @@ -273,7 +273,7 @@ public static void mapItineraryFilterParams( search window of the transit results. Depart-at searches are filtered by latest-arrival-time and arrive-by searches are filtered - by earliest-departure-time and . + by earliest-departure-time. Use this configuration to turn this feature off. """ From ce36b346055982207bd5f47eadb622791039d70e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 11 Oct 2024 13:05:42 +0200 Subject: [PATCH 33/35] Update src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilter.java Co-authored-by: Thomas Gran --- .../filterchain/filters/system/FlexSearchWindowFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6a96efb2d85..d5d6c6351a4 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 @@ -8,7 +8,7 @@ import org.opentripplanner.routing.algorithm.filterchain.framework.spi.RemoveItineraryFlagger; /** - * The flex router doesn't use the transit router's time window but nevertheless using it + * The flex router doesn't use the transit router's search-window, but nevertheless using it * for filtering is useful when combining flex with transit. *

    * The flex router also searches the previous day (arrive by) or the next one (depart after). From c35532c7807f8a154279b1d23bb0ef749dd02a59 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 11 Oct 2024 13:32:47 +0200 Subject: [PATCH 34/35] Apply suggestions from code review Co-authored-by: Thomas Gran --- .../filterchain/filters/system/FlexSearchWindowFilter.java | 2 -- 1 file changed, 2 deletions(-) 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 d5d6c6351a4..efbb9bf4d08 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 @@ -44,12 +44,10 @@ public Predicate shouldBeFlaggedForRemoval() { if (it.isDirectFlex()) { return switch (sortOrder) { case STREET_AND_DEPARTURE_TIME -> { - // arive by var time = it.startTime().toInstant(); yield time.isBefore(earliestDepartureTime); } case STREET_AND_ARRIVAL_TIME -> { - // depart at var time = it.startTime().toInstant(); yield time.isAfter(latestArrivalTime); } From 9cb2f4efda51e2c2df3b54e3fb3292a8cf389b0f Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 11 Oct 2024 13:44:36 +0200 Subject: [PATCH 35/35] Update documentation --- doc/user/RouteRequest.md | 343 +++++++++--------- .../routerequest/ItineraryFiltersConfig.java | 14 +- 2 files changed, 181 insertions(+), 176 deletions(-) diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index c652d7e6d96..ea3d0d12c74 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -13,172 +13,172 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe -| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | -|--------------------------------------------------------------------------------------------------------------|:----------------------:|------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|------------------|:-----:| -| [alightSlack](#rd_alightSlack) | `duration` | The time safety margin when alighting from a vehicle. | *Optional* | `"PT0S"` | 2.0 | -| arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 | -| [boardSlack](#rd_boardSlack) | `duration` | The time safety margin when boarding a vehicle. | *Optional* | `"PT0S"` | 2.0 | -| [drivingDirection](#rd_drivingDirection) | `enum` | The driving direction to use in the intersection traversal calculation | *Optional* | `"right"` | 2.2 | -| elevatorBoardCost | `integer` | What is the cost of boarding a elevator? | *Optional* | `90` | 2.0 | -| elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | -| elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | -| elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | -| geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | -| ignoreRealtimeUpdates | `boolean` | When true, real-time updates are ignored during this search. | *Optional* | `false` | 2.0 | -| [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | -| locale | `locale` | TODO | *Optional* | `"en_US"` | 2.0 | -| [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | -| [maxJourneyDuration](#rd_maxJourneyDuration) | `duration` | The expected maximum time a journey can last across all possible journeys for the current deployment. | *Optional* | `"PT24H"` | 2.1 | -| modes | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 | -| nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 | -| numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | -| [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 | -| [relaxTransitGroupPriority](#rd_relaxTransitGroupPriority) | `string` | The relax function for transit-group-priority | *Optional* | `"0s + 1.00 t"` | 2.5 | -| [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 | -| [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 | -| [streetRoutingTimeout](#rd_streetRoutingTimeout) | `duration` | The maximum time a street routing request is allowed to take before returning the results. | *Optional* | `"PT5S"` | 2.2 | -| [transferPenalty](#rd_transferPenalty) | `integer` | An additional penalty added to boardings after the first. | *Optional* | `0` | 2.0 | -| [transferSlack](#rd_transferSlack) | `duration` | The extra time needed to make a safe transfer. | *Optional* | `"PT2M"` | 2.0 | -| turnReluctance | `double` | Multiplicative factor on expected turning time. | *Optional* | `1.0` | 2.0 | -| [unpreferredCost](#rd_unpreferredCost) | `cost-linear-function` | A cost function used to calculate penalty for an unpreferred route. | *Optional* | `"0s + 1.00 t"` | 2.2 | -| waitReluctance | `double` | How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. | *Optional* | `1.0` | 2.0 | -| accessEgress | `object` | Parameters for access and egress routing. | *Optional* | | 2.4 | -|    [maxDuration](#rd_accessEgress_maxDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 | -|    [maxStopCount](#rd_accessEgress_maxStopCount) | `integer` | Maximal number of stops collected in access/egress routing | *Optional* | `500` | 2.4 | -|    [maxDurationForMode](#rd_accessEgress_maxDurationForMode) | `enum map of duration` | Limit access/egress per street mode. | *Optional* | | 2.1 | -|    [penalty](#rd_accessEgress_penalty) | `enum map of object` | Penalty for access/egress by street mode. | *Optional* | | 2.4 | -|       FLEXIBLE | `object` | NA | *Optional* | | 2.4 | -|          costFactor | `double` | A factor multiplied with the time-penalty to get the cost-penalty. | *Optional* | `0.0` | 2.4 | -|          timePenalty | `time-penalty` | Penalty added to the time of a path/leg. | *Optional* | `"0s + 0.00 t"` | 2.4 | -| [alightSlackForMode](#rd_alightSlackForMode) | `enum map of duration` | How much extra time should be given when alighting a vehicle for each given mode. | *Optional* | | 2.0 | -| bicycle | `object` | Bicycle preferences. | *Optional* | | 2.5 | -|    [boardCost](#rd_bicycle_boardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a transit vehicle. | *Optional* | `600` | 2.0 | -|    [optimization](#rd_bicycle_optimization) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe-streets"` | 2.0 | -|    reluctance | `double` | A multiplier for how bad cycling is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -|    speed | `double` | Max bicycle speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | -|    parking | `object` | Preferences for parking a vehicle. | *Optional* | | 2.5 | -|       cost | `integer` | Cost to park a vehicle. | *Optional* | `120` | 2.0 | -|       time | `duration` | Time to park a vehicle. | *Optional* | `"PT1M"` | 2.0 | -|       [unpreferredVehicleParkingTagCost](#rd_bicycle_parking_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | -|       [bannedVehicleParkingTags](#rd_bicycle_parking_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | -|       [preferredVehicleParkingTags](#rd_bicycle_parking_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | -|       [requiredVehicleParkingTags](#rd_bicycle_parking_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | -|    rental | `object` | Vehicle rental options | *Optional* | | 2.3 | -|       allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | -|       dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|       dropOffTime | `duration` | Time to drop-off a rented vehicle. | *Optional* | `"PT30S"` | 2.0 | -|       keepingAtDestinationCost | `integer` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0` | 2.2 | -|       pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | -|       pickupTime | `duration` | Time to rent a vehicle. | *Optional* | `"PT1M"` | 2.0 | -|       useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | -|       [allowedNetworks](#rd_bicycle_rental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | -|       [bannedNetworks](#rd_bicycle_rental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | -|    [triangle](#rd_bicycle_triangle) | `object` | Triangle optimization criteria. | *Optional* | | 2.5 | -|       flatness | `double` | Relative importance of flat terrain (range 0-1). | *Optional* | `0.0` | 2.0 | -|       [safety](#rd_bicycle_triangle_safety) | `double` | Relative importance of safety (range 0-1). | *Optional* | `0.0` | 2.0 | -|       time | `double` | Relative importance of duration of travel (range 0-1). | *Optional* | `0.0` | 2.0 | -|    walk | `object` | Preferences for walking a vehicle. | *Optional* | | 2.5 | -|       [mountDismountCost](#rd_bicycle_walk_mountDismountCost) | `integer` | The cost of hopping on or off a vehicle. | *Optional* | `0` | 2.0 | -|       [mountDismountTime](#rd_bicycle_walk_mountDismountTime) | `duration` | The time it takes the user to hop on or off a vehicle. | *Optional* | `"PT0S"` | 2.0 | -|       reluctance | `double` | A multiplier for how bad walking with a vehicle is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 | -|       speed | `double` | The user's vehicle walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 | -|       stairsReluctance | `double` | How bad is it to walk the vehicle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 | -| [boardSlackForMode](#rd_boardSlackForMode) | `enum map of duration` | How much extra time should be given when boarding a vehicle for each given mode. | *Optional* | | 2.0 | -| car | `object` | Car preferences. | *Optional* | | 2.5 | -|    accelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -|    decelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | -|    pickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 | -|    pickupTime | `duration` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `"PT1M"` | 2.1 | -|    reluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -|    parking | `object` | Preferences for parking a vehicle. | *Optional* | | 2.5 | -|       cost | `integer` | Cost to park a vehicle. | *Optional* | `120` | 2.0 | -|       time | `duration` | Time to park a vehicle. | *Optional* | `"PT1M"` | 2.0 | -|       [unpreferredVehicleParkingTagCost](#rd_car_parking_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | -|       [bannedVehicleParkingTags](#rd_car_parking_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | -|       [preferredVehicleParkingTags](#rd_car_parking_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | -|       [requiredVehicleParkingTags](#rd_car_parking_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | -|    rental | `object` | Vehicle rental options | *Optional* | | 2.3 | -|       allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | -|       dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|       dropOffTime | `duration` | Time to drop-off a rented vehicle. | *Optional* | `"PT30S"` | 2.0 | -|       keepingAtDestinationCost | `integer` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0` | 2.2 | -|       pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | -|       pickupTime | `duration` | Time to rent a vehicle. | *Optional* | `"PT1M"` | 2.0 | -|       useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | -|       [allowedNetworks](#rd_car_rental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | -|       [bannedNetworks](#rd_car_rental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | -| [itineraryFilters](#rd_itineraryFilters) | `object` | Configure itinerary filters that may modify itineraries, sort them, and filter away less preferable results. | *Optional* | | 2.0 | -|    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | -|    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | -|    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | -|    [filterDirectFlexBySearchWindow](#rd_if_filterDirectFlexBySearchWindow) | `boolean` | Filter direct flex results by the search window. | *Optional* | `true` | 2.7 | -|    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | -|    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | -|    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | -|    [groupedOtherThanSameLegsMaxCostMultiplier](#rd_if_groupedOtherThanSameLegsMaxCostMultiplier) | `double` | Filter grouped itineraries, where the non-grouped legs are more expensive than in the lowest cost one. | *Optional* | `2.0` | 2.1 | -|    [minBikeParkingDistance](#rd_if_minBikeParkingDistance) | `double` | Filter out bike park+ride results that have fewer meters of cycling than this value. | *Optional* | `0.0` | 2.3 | -|    [nonTransitGeneralizedCostLimit](#rd_if_nonTransitGeneralizedCostLimit) | `cost-linear-function` | The function define a max-limit for generalized-cost for non-transit itineraries. | *Optional* | `"1h + 2.0 t"` | 2.1 | -|    [parkAndRideDurationRatio](#rd_if_parkAndRideDurationRatio) | `double` | Filter P+R routes that consist of driving and walking by the minimum fraction of the driving using of _time_. | *Optional* | `0.0` | 2.1 | -|    [removeItinerariesWithSameRoutesAndStops](#rd_if_removeItinerariesWithSameRoutesAndStops) | `boolean` | Set to true if you want to list only the first itinerary which goes through the same stops and routes. | *Optional* | `false` | 2.2 | -|    [removeTransitWithHigherCostThanBestOnStreetOnly](#rd_if_removeTransitWithHigherCostThanBestOnStreetOnly) | `cost-linear-function` | Limit function for generalized-cost computed from street-only itineries applied to transit itineraries. | *Optional* | `"1m + 1.30 t"` | 2.4 | -|    [transitGeneralizedCostLimit](#rd_if_transitGeneralizedCostLimit) | `object` | A relative limit for the generalized-cost for transit itineraries. | *Optional* | | 2.1 | -|       [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `cost-linear-function` | The base function used by the filter. | *Optional* | `"15m + 1.50 t"` | 2.2 | -|       [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 | -| [maxDirectStreetDurationForMode](#rd_maxDirectStreetDurationForMode) | `enum map of duration` | Limit direct route duration per street mode. | *Optional* | | 2.2 | -| scooter | `object` | Scooter preferences. | *Optional* | | 2.5 | -|    [optimization](#rd_scooter_optimization) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe-streets"` | 2.0 | -|    reluctance | `double` | A multiplier for how bad scooter travel is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -|    speed | `double` | Max scooter speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | -|    rental | `object` | Vehicle rental options | *Optional* | | 2.3 | -|       allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | -|       dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | -|       dropOffTime | `duration` | Time to drop-off a rented vehicle. | *Optional* | `"PT30S"` | 2.0 | -|       keepingAtDestinationCost | `integer` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0` | 2.2 | -|       pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | -|       pickupTime | `duration` | Time to rent a vehicle. | *Optional* | `"PT1M"` | 2.0 | -|       useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | -|       [allowedNetworks](#rd_scooter_rental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | -|       [bannedNetworks](#rd_scooter_rental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | -|    [triangle](#rd_scooter_triangle) | `object` | Triangle optimization criteria. | *Optional* | | 2.5 | -|       flatness | `double` | Relative importance of flat terrain (range 0-1). | *Optional* | `0.0` | 2.0 | -|       [safety](#rd_scooter_triangle_safety) | `double` | Relative importance of safety (range 0-1). | *Optional* | `0.0` | 2.0 | -|       time | `double` | Relative importance of duration of travel (range 0-1). | *Optional* | `0.0` | 2.0 | -| [transferOptimization](#rd_transferOptimization) | `object` | Optimize where a transfer between to trip happens. | *Optional* | | 2.1 | -|    [backTravelWaitTimeFactor](#rd_to_backTravelWaitTimeFactor) | `double` | To reduce back-travel we favor waiting, this reduces the cost of waiting. | *Optional* | `1.0` | 2.1 | -|    [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 | -|    [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 | -|    [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 | -| [transitGroupPriority](#rd_transitGroupPriority) | `object` | Group transit patterns and give each group a mutual advantage in the Raptor search. | *Optional* | | 2.5 | -| [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 | -| [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 | -|    [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 | -|    [routes](#rd_unpreferred_routes) | `feed-scoped-id[]` | The ids of the routes that incur an extra cost when being used. Format: `FeedId:RouteId` | *Optional* | | 2.2 | -| walk | `object` | Walking preferences. | *Optional* | | 2.5 | -|    boardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 | -|    escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | -|    [reluctance](#rd_walk_reluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | -|    [safetyFactor](#rd_walk_safetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 | -|    speed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 | -|    stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | -|    [stairsTimeFactor](#rd_walk_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 | -| wheelchairAccessibility | `object` | See [Wheelchair Accessibility](Accessibility.md) | *Optional* | | 2.2 | -|    enabled | `boolean` | Enable wheelchair accessibility. | *Optional* | `false` | 2.0 | -|    inaccessibleStreetReluctance | `double` | The factor to multiply the cost of traversing a street edge that is not wheelchair-accessible. | *Optional* | `25.0` | 2.2 | -|    [maxSlope](#rd_wheelchairAccessibility_maxSlope) | `double` | The maximum slope as a fraction of 1. | *Optional* | `0.083` | 2.0 | -|    [slopeExceededReluctance](#rd_wheelchairAccessibility_slopeExceededReluctance) | `double` | How much streets with high slope should be avoided. | *Optional* | `1.0` | 2.2 | -|    [stairsReluctance](#rd_wheelchairAccessibility_stairsReluctance) | `double` | How much stairs should be avoided. | *Optional* | `100.0` | 2.2 | -|    elevator | `object` | Configuration for when to use inaccessible elevators. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `false` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `20` | 2.2 | -|    stop | `object` | Configuration for when to use inaccessible stops. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | -|    trip | `object` | Configuration for when to use inaccessible trips. | *Optional* | | 2.2 | -|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | -|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | -|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | +| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since | +|--------------------------------------------------------------------------------------------------------------|:----------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------|:----------:|------------------|:-----:| +| [alightSlack](#rd_alightSlack) | `duration` | The time safety margin when alighting from a vehicle. | *Optional* | `"PT0S"` | 2.0 | +| arriveBy | `boolean` | Whether the trip should depart or arrive at the specified date and time. | *Optional* | `false` | 2.0 | +| [boardSlack](#rd_boardSlack) | `duration` | The time safety margin when boarding a vehicle. | *Optional* | `"PT0S"` | 2.0 | +| [drivingDirection](#rd_drivingDirection) | `enum` | The driving direction to use in the intersection traversal calculation | *Optional* | `"right"` | 2.2 | +| elevatorBoardCost | `integer` | What is the cost of boarding a elevator? | *Optional* | `90` | 2.0 | +| elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 | +| elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 | +| elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 | +| geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 | +| ignoreRealtimeUpdates | `boolean` | When true, real-time updates are ignored during this search. | *Optional* | `false` | 2.0 | +| [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 | +| locale | `locale` | TODO | *Optional* | `"en_US"` | 2.0 | +| [maxDirectStreetDuration](#rd_maxDirectStreetDuration) | `duration` | This is the maximum duration for a direct street search for each mode. | *Optional* | `"PT4H"` | 2.1 | +| [maxJourneyDuration](#rd_maxJourneyDuration) | `duration` | The expected maximum time a journey can last across all possible journeys for the current deployment. | *Optional* | `"PT24H"` | 2.1 | +| modes | `string` | The set of access/egress/direct/transit modes to be used for the route search. | *Optional* | `"TRANSIT,WALK"` | 2.0 | +| nonpreferredTransferPenalty | `integer` | Penalty (in seconds) for using a non-preferred transfer. | *Optional* | `180` | 2.0 | +| numItineraries | `integer` | The maximum number of itineraries to return. | *Optional* | `50` | 2.0 | +| [otherThanPreferredRoutesPenalty](#rd_otherThanPreferredRoutesPenalty) | `integer` | Penalty added for using every route that is not preferred if user set any route as preferred. | *Optional* | `300` | 2.0 | +| [relaxTransitGroupPriority](#rd_relaxTransitGroupPriority) | `string` | The relax function for transit-group-priority | *Optional* | `"0s + 1.00 t"` | 2.5 | +| [relaxTransitSearchGeneralizedCostAtDestination](#rd_relaxTransitSearchGeneralizedCostAtDestination) | `double` | Whether non-optimal transit paths at the destination should be returned | *Optional* | | 2.3 | +| [searchWindow](#rd_searchWindow) | `duration` | The duration of the search-window. | *Optional* | | 2.0 | +| [streetRoutingTimeout](#rd_streetRoutingTimeout) | `duration` | The maximum time a street routing request is allowed to take before returning the results. | *Optional* | `"PT5S"` | 2.2 | +| [transferPenalty](#rd_transferPenalty) | `integer` | An additional penalty added to boardings after the first. | *Optional* | `0` | 2.0 | +| [transferSlack](#rd_transferSlack) | `duration` | The extra time needed to make a safe transfer. | *Optional* | `"PT2M"` | 2.0 | +| turnReluctance | `double` | Multiplicative factor on expected turning time. | *Optional* | `1.0` | 2.0 | +| [unpreferredCost](#rd_unpreferredCost) | `cost-linear-function` | A cost function used to calculate penalty for an unpreferred route. | *Optional* | `"0s + 1.00 t"` | 2.2 | +| waitReluctance | `double` | How much worse is waiting for a transit vehicle than being on a transit vehicle, as a multiplier. | *Optional* | `1.0` | 2.0 | +| accessEgress | `object` | Parameters for access and egress routing. | *Optional* | | 2.4 | +|    [maxDuration](#rd_accessEgress_maxDuration) | `duration` | This is the maximum duration for access/egress for street searches. | *Optional* | `"PT45M"` | 2.1 | +|    [maxStopCount](#rd_accessEgress_maxStopCount) | `integer` | Maximal number of stops collected in access/egress routing | *Optional* | `500` | 2.4 | +|    [maxDurationForMode](#rd_accessEgress_maxDurationForMode) | `enum map of duration` | Limit access/egress per street mode. | *Optional* | | 2.1 | +|    [penalty](#rd_accessEgress_penalty) | `enum map of object` | Penalty for access/egress by street mode. | *Optional* | | 2.4 | +|       FLEXIBLE | `object` | NA | *Optional* | | 2.4 | +|          costFactor | `double` | A factor multiplied with the time-penalty to get the cost-penalty. | *Optional* | `0.0` | 2.4 | +|          timePenalty | `time-penalty` | Penalty added to the time of a path/leg. | *Optional* | `"0s + 0.00 t"` | 2.4 | +| [alightSlackForMode](#rd_alightSlackForMode) | `enum map of duration` | How much extra time should be given when alighting a vehicle for each given mode. | *Optional* | | 2.0 | +| bicycle | `object` | Bicycle preferences. | *Optional* | | 2.5 | +|    [boardCost](#rd_bicycle_boardCost) | `integer` | Prevents unnecessary transfers by adding a cost for boarding a transit vehicle. | *Optional* | `600` | 2.0 | +|    [optimization](#rd_bicycle_optimization) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe-streets"` | 2.0 | +|    reluctance | `double` | A multiplier for how bad cycling is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +|    speed | `double` | Max bicycle speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | +|    parking | `object` | Preferences for parking a vehicle. | *Optional* | | 2.5 | +|       cost | `integer` | Cost to park a vehicle. | *Optional* | `120` | 2.0 | +|       time | `duration` | Time to park a vehicle. | *Optional* | `"PT1M"` | 2.0 | +|       [unpreferredVehicleParkingTagCost](#rd_bicycle_parking_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | +|       [bannedVehicleParkingTags](#rd_bicycle_parking_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | +|       [preferredVehicleParkingTags](#rd_bicycle_parking_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | +|       [requiredVehicleParkingTags](#rd_bicycle_parking_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | +|    rental | `object` | Vehicle rental options | *Optional* | | 2.3 | +|       allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | +|       dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | +|       dropOffTime | `duration` | Time to drop-off a rented vehicle. | *Optional* | `"PT30S"` | 2.0 | +|       keepingAtDestinationCost | `integer` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0` | 2.2 | +|       pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | +|       pickupTime | `duration` | Time to rent a vehicle. | *Optional* | `"PT1M"` | 2.0 | +|       useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | +|       [allowedNetworks](#rd_bicycle_rental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | +|       [bannedNetworks](#rd_bicycle_rental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | +|    [triangle](#rd_bicycle_triangle) | `object` | Triangle optimization criteria. | *Optional* | | 2.5 | +|       flatness | `double` | Relative importance of flat terrain (range 0-1). | *Optional* | `0.0` | 2.0 | +|       [safety](#rd_bicycle_triangle_safety) | `double` | Relative importance of safety (range 0-1). | *Optional* | `0.0` | 2.0 | +|       time | `double` | Relative importance of duration of travel (range 0-1). | *Optional* | `0.0` | 2.0 | +|    walk | `object` | Preferences for walking a vehicle. | *Optional* | | 2.5 | +|       [mountDismountCost](#rd_bicycle_walk_mountDismountCost) | `integer` | The cost of hopping on or off a vehicle. | *Optional* | `0` | 2.0 | +|       [mountDismountTime](#rd_bicycle_walk_mountDismountTime) | `duration` | The time it takes the user to hop on or off a vehicle. | *Optional* | `"PT0S"` | 2.0 | +|       reluctance | `double` | A multiplier for how bad walking with a vehicle is, compared to being in transit for equal lengths of time. | *Optional* | `5.0` | 2.1 | +|       speed | `double` | The user's vehicle walking speed in meters/second. Defaults to approximately 3 MPH. | *Optional* | `1.33` | 2.1 | +|       stairsReluctance | `double` | How bad is it to walk the vehicle up/down a flight of stairs compared to taking a detour. | *Optional* | `10.0` | 2.3 | +| [boardSlackForMode](#rd_boardSlackForMode) | `enum map of duration` | How much extra time should be given when boarding a vehicle for each given mode. | *Optional* | | 2.0 | +| car | `object` | Car preferences. | *Optional* | | 2.5 | +|    accelerationSpeed | `double` | The acceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | +|    decelerationSpeed | `double` | The deceleration speed of an automobile, in meters per second per second. | *Optional* | `2.9` | 2.0 | +|    pickupCost | `integer` | Add a cost for car pickup changes when a pickup or drop off takes place | *Optional* | `120` | 2.1 | +|    pickupTime | `duration` | Add a time for car pickup changes when a pickup or drop off takes place | *Optional* | `"PT1M"` | 2.1 | +|    reluctance | `double` | A multiplier for how bad driving is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +|    parking | `object` | Preferences for parking a vehicle. | *Optional* | | 2.5 | +|       cost | `integer` | Cost to park a vehicle. | *Optional* | `120` | 2.0 | +|       time | `duration` | Time to park a vehicle. | *Optional* | `"PT1M"` | 2.0 | +|       [unpreferredVehicleParkingTagCost](#rd_car_parking_unpreferredVehicleParkingTagCost) | `integer` | What cost to add if a parking facility doesn't contain a preferred tag. | *Optional* | `300` | 2.3 | +|       [bannedVehicleParkingTags](#rd_car_parking_bannedVehicleParkingTags) | `string[]` | Tags with which a vehicle parking will not be used. If empty, no tags are banned. | *Optional* | | 2.1 | +|       [preferredVehicleParkingTags](#rd_car_parking_preferredVehicleParkingTags) | `string[]` | Vehicle parking facilities that don't have one of these tags will receive an extra cost and will therefore be penalised. | *Optional* | | 2.3 | +|       [requiredVehicleParkingTags](#rd_car_parking_requiredVehicleParkingTags) | `string[]` | Tags without which a vehicle parking will not be used. If empty, no tags are required. | *Optional* | | 2.1 | +|    rental | `object` | Vehicle rental options | *Optional* | | 2.3 | +|       allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | +|       dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | +|       dropOffTime | `duration` | Time to drop-off a rented vehicle. | *Optional* | `"PT30S"` | 2.0 | +|       keepingAtDestinationCost | `integer` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0` | 2.2 | +|       pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | +|       pickupTime | `duration` | Time to rent a vehicle. | *Optional* | `"PT1M"` | 2.0 | +|       useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | +|       [allowedNetworks](#rd_car_rental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | +|       [bannedNetworks](#rd_car_rental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | +| [itineraryFilters](#rd_itineraryFilters) | `object` | Configure itinerary filters that may modify itineraries, sort them, and filter away less preferable results. | *Optional* | | 2.0 | +|    [accessibilityScore](#rd_if_accessibilityScore) | `boolean` | An experimental feature contributed by IBI which adds a sandbox accessibility *score* between 0 and 1 for each leg and itinerary. | *Optional* | `false` | 2.2 | +|    [bikeRentalDistanceRatio](#rd_if_bikeRentalDistanceRatio) | `double` | Filter routes that consist of bike-rental and walking by the minimum fraction of the bike-rental leg using _distance_. | *Optional* | `0.0` | 2.1 | +|    [debug](#rd_if_debug) | `enum` | Enable this to attach a system notice to itineraries instead of removing them. This is very convenient when tuning the itinerary-filter-chain. | *Optional* | `"off"` | 2.0 | +|    [filterDirectFlexBySearchWindow](#rd_if_filterDirectFlexBySearchWindow) | `boolean` | Filter direct flex results by the search window. The search-window is not used during flex routing, but we use one end to align it with transit results. | *Optional* | `true` | 2.7 | +|    [filterItinerariesWithSameFirstOrLastTrip](#rd_if_filterItinerariesWithSameFirstOrLastTrip) | `boolean` | If more than one itinerary begins or ends with same trip, filter out one of those itineraries so that only one remains. | *Optional* | `false` | 2.2 | +|    groupSimilarityKeepOne | `double` | Pick ONE itinerary from each group after putting itineraries that are 85% similar together. | *Optional* | `0.85` | 2.1 | +|    groupSimilarityKeepThree | `double` | Reduce the number of itineraries to three itineraries by reducing each group of itineraries grouped by 68% similarity. | *Optional* | `0.68` | 2.1 | +|    [groupedOtherThanSameLegsMaxCostMultiplier](#rd_if_groupedOtherThanSameLegsMaxCostMultiplier) | `double` | Filter grouped itineraries, where the non-grouped legs are more expensive than in the lowest cost one. | *Optional* | `2.0` | 2.1 | +|    [minBikeParkingDistance](#rd_if_minBikeParkingDistance) | `double` | Filter out bike park+ride results that have fewer meters of cycling than this value. | *Optional* | `0.0` | 2.3 | +|    [nonTransitGeneralizedCostLimit](#rd_if_nonTransitGeneralizedCostLimit) | `cost-linear-function` | The function define a max-limit for generalized-cost for non-transit itineraries. | *Optional* | `"1h + 2.0 t"` | 2.1 | +|    [parkAndRideDurationRatio](#rd_if_parkAndRideDurationRatio) | `double` | Filter P+R routes that consist of driving and walking by the minimum fraction of the driving using of _time_. | *Optional* | `0.0` | 2.1 | +|    [removeItinerariesWithSameRoutesAndStops](#rd_if_removeItinerariesWithSameRoutesAndStops) | `boolean` | Set to true if you want to list only the first itinerary which goes through the same stops and routes. | *Optional* | `false` | 2.2 | +|    [removeTransitWithHigherCostThanBestOnStreetOnly](#rd_if_removeTransitWithHigherCostThanBestOnStreetOnly) | `cost-linear-function` | Limit function for generalized-cost computed from street-only itineries applied to transit itineraries. | *Optional* | `"1m + 1.30 t"` | 2.4 | +|    [transitGeneralizedCostLimit](#rd_if_transitGeneralizedCostLimit) | `object` | A relative limit for the generalized-cost for transit itineraries. | *Optional* | | 2.1 | +|       [costLimitFunction](#rd_if_transitGeneralizedCostLimit_costLimitFunction) | `cost-linear-function` | The base function used by the filter. | *Optional* | `"15m + 1.50 t"` | 2.2 | +|       [intervalRelaxFactor](#rd_if_transitGeneralizedCostLimit_intervalRelaxFactor) | `double` | How much the filter should be relaxed for itineraries that do not overlap in time. | *Optional* | `0.4` | 2.2 | +| [maxDirectStreetDurationForMode](#rd_maxDirectStreetDurationForMode) | `enum map of duration` | Limit direct route duration per street mode. | *Optional* | | 2.2 | +| scooter | `object` | Scooter preferences. | *Optional* | | 2.5 | +|    [optimization](#rd_scooter_optimization) | `enum` | The set of characteristics that the user wants to optimize for. | *Optional* | `"safe-streets"` | 2.0 | +|    reluctance | `double` | A multiplier for how bad scooter travel is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +|    speed | `double` | Max scooter speed along streets, in meters per second | *Optional* | `5.0` | 2.0 | +|    rental | `object` | Vehicle rental options | *Optional* | | 2.3 | +|       allowKeepingAtDestination | `boolean` | If a vehicle should be allowed to be kept at the end of a station-based rental. | *Optional* | `false` | 2.2 | +|       dropOffCost | `integer` | Cost to drop-off a rented vehicle. | *Optional* | `30` | 2.0 | +|       dropOffTime | `duration` | Time to drop-off a rented vehicle. | *Optional* | `"PT30S"` | 2.0 | +|       keepingAtDestinationCost | `integer` | The cost of arriving at the destination with the rented vehicle, to discourage doing so. | *Optional* | `0` | 2.2 | +|       pickupCost | `integer` | Cost to rent a vehicle. | *Optional* | `120` | 2.0 | +|       pickupTime | `duration` | Time to rent a vehicle. | *Optional* | `"PT1M"` | 2.0 | +|       useAvailabilityInformation | `boolean` | Whether or not vehicle rental availability information will be used to plan vehicle rental trips. | *Optional* | `false` | 2.0 | +|       [allowedNetworks](#rd_scooter_rental_allowedNetworks) | `string[]` | The vehicle rental networks which may be used. If empty all networks may be used. | *Optional* | | 2.1 | +|       [bannedNetworks](#rd_scooter_rental_bannedNetworks) | `string[]` | The vehicle rental networks which may not be used. If empty, no networks are banned. | *Optional* | | 2.1 | +|    [triangle](#rd_scooter_triangle) | `object` | Triangle optimization criteria. | *Optional* | | 2.5 | +|       flatness | `double` | Relative importance of flat terrain (range 0-1). | *Optional* | `0.0` | 2.0 | +|       [safety](#rd_scooter_triangle_safety) | `double` | Relative importance of safety (range 0-1). | *Optional* | `0.0` | 2.0 | +|       time | `double` | Relative importance of duration of travel (range 0-1). | *Optional* | `0.0` | 2.0 | +| [transferOptimization](#rd_transferOptimization) | `object` | Optimize where a transfer between to trip happens. | *Optional* | | 2.1 | +|    [backTravelWaitTimeFactor](#rd_to_backTravelWaitTimeFactor) | `double` | To reduce back-travel we favor waiting, this reduces the cost of waiting. | *Optional* | `1.0` | 2.1 | +|    [extraStopBoardAlightCostsFactor](#rd_to_extraStopBoardAlightCostsFactor) | `double` | Add an extra board- and alight-cost for prioritized stops. | *Optional* | `0.0` | 2.1 | +|    [minSafeWaitTimeFactor](#rd_to_minSafeWaitTimeFactor) | `double` | Used to set a maximum wait-time cost, base on min-safe-transfer-time. | *Optional* | `5.0` | 2.1 | +|    [optimizeTransferWaitTime](#rd_to_optimizeTransferWaitTime) | `boolean` | This enables the transfer wait time optimization. | *Optional* | `true` | 2.1 | +| [transitGroupPriority](#rd_transitGroupPriority) | `object` | Group transit patterns and give each group a mutual advantage in the Raptor search. | *Optional* | | 2.5 | +| [transitReluctanceForMode](#rd_transitReluctanceForMode) | `enum map of double` | Transit reluctance for a given transport mode | *Optional* | | 2.1 | +| [unpreferred](#rd_unpreferred) | `object` | Parameters listing authorities or lines that preferably should not be used in trip patters. | *Optional* | | 2.2 | +|    [agencies](#rd_unpreferred_agencies) | `feed-scoped-id[]` | The ids of the agencies that incur an extra cost when being used. Format: `FeedId:AgencyId` | *Optional* | | 2.2 | +|    [routes](#rd_unpreferred_routes) | `feed-scoped-id[]` | The ids of the routes that incur an extra cost when being used. Format: `FeedId:RouteId` | *Optional* | | 2.2 | +| walk | `object` | Walking preferences. | *Optional* | | 2.5 | +|    boardCost | `integer` | Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that is used when boarding while walking. | *Optional* | `600` | 2.0 | +|    escalatorReluctance | `double` | A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time | *Optional* | `1.5` | 2.4 | +|    [reluctance](#rd_walk_reluctance) | `double` | A multiplier for how bad walking is, compared to being in transit for equal lengths of time. | *Optional* | `2.0` | 2.0 | +|    [safetyFactor](#rd_walk_safetyFactor) | `double` | Factor for how much the walk safety is considered in routing. | *Optional* | `1.0` | 2.2 | +|    speed | `double` | The user's walking speed in meters/second. | *Optional* | `1.33` | 2.0 | +|    stairsReluctance | `double` | Used instead of walkReluctance for stairs. | *Optional* | `2.0` | 2.0 | +|    [stairsTimeFactor](#rd_walk_stairsTimeFactor) | `double` | How much more time does it take to walk a flight of stairs compared to walking a similar horizontal length. | *Optional* | `3.0` | 2.1 | +| wheelchairAccessibility | `object` | See [Wheelchair Accessibility](Accessibility.md) | *Optional* | | 2.2 | +|    enabled | `boolean` | Enable wheelchair accessibility. | *Optional* | `false` | 2.0 | +|    inaccessibleStreetReluctance | `double` | The factor to multiply the cost of traversing a street edge that is not wheelchair-accessible. | *Optional* | `25.0` | 2.2 | +|    [maxSlope](#rd_wheelchairAccessibility_maxSlope) | `double` | The maximum slope as a fraction of 1. | *Optional* | `0.083` | 2.0 | +|    [slopeExceededReluctance](#rd_wheelchairAccessibility_slopeExceededReluctance) | `double` | How much streets with high slope should be avoided. | *Optional* | `1.0` | 2.2 | +|    [stairsReluctance](#rd_wheelchairAccessibility_stairsReluctance) | `double` | How much stairs should be avoided. | *Optional* | `100.0` | 2.2 | +|    elevator | `object` | Configuration for when to use inaccessible elevators. | *Optional* | | 2.2 | +|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | +|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `false` | 2.2 | +|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `20` | 2.2 | +|    stop | `object` | Configuration for when to use inaccessible stops. | *Optional* | | 2.2 | +|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | +|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | +|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | +|    trip | `object` | Configuration for when to use inaccessible trips. | *Optional* | | 2.2 | +|       inaccessibleCost | `integer` | The cost to add when traversing an entity which is know to be inaccessible. | *Optional* | `3600` | 2.2 | +|       onlyConsiderAccessible | `boolean` | Whether to only use this entity if it is explicitly marked as wheelchair accessible. | *Optional* | `true` | 2.2 | +|       unknownCost | `integer` | The cost to add when traversing an entity with unknown accessibility information. | *Optional* | `600` | 2.2 | @@ -737,13 +737,14 @@ convenient when tuning the itinerary-filter-chain. **Since version:** `2.7` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `true` **Path:** /routingDefaults/itineraryFilters -Filter direct flex results by the search window. +Filter direct flex results by the search window. The search-window is not used +during flex routing, but we use one end to align it with transit results. -When direct flex is mixed with a transit search in the same request, then the direct flex results are filtered by the -search window of the transit results. +When direct flex is mixed with a transit search in the same request, then the direct +flex results are filtered by the search window of the transit results. -Depart-at searches are filtered by latest-arrival-time and arrive-by searches are filtered -by earliest-departure-time. +Depart-at searches are filtered by latest-arrival-time and arrive-by searches are +filtered by earliest-departure-time. Use this configuration to turn this feature off. diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java index d66aa4196e0..c021de88a8e 100644 --- a/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java +++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/ItineraryFiltersConfig.java @@ -266,14 +266,18 @@ public static void mapItineraryFilterParams( c .of("filterDirectFlexBySearchWindow") .since(V2_7) - .summary("Filter direct flex results by the search window.") + .summary( + """ + Filter direct flex results by the search window. The search-window is not used + during flex routing, but we use one end to align it with transit results.""" + ) .description( """ - When direct flex is mixed with a transit search in the same request, then the direct flex results are filtered by the - search window of the transit results. + When direct flex is mixed with a transit search in the same request, then the direct + flex results are filtered by the search window of the transit results. - Depart-at searches are filtered by latest-arrival-time and arrive-by searches are filtered - by earliest-departure-time. + Depart-at searches are filtered by latest-arrival-time and arrive-by searches are + filtered by earliest-departure-time. Use this configuration to turn this feature off. """