Fix RemoveTransitIfStreetOnlyIsBetter filter not applied to flex#7498
Fix RemoveTransitIfStreetOnlyIsBetter filter not applied to flex#7498sigtot wants to merge 4 commits intoopentripplanner:dev-2.xfrom
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev-2.x #7498 +/- ##
=============================================
- Coverage 71.39% 71.01% -0.38%
+ Complexity 21112 20997 -115
=============================================
Files 2344 2348 +4
Lines 87167 87250 +83
Branches 8633 8639 +6
=============================================
- Hits 62231 61963 -268
- Misses 21936 22291 +355
+ Partials 3000 2996 -4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
After discussion in the core team meeting, we see a problem with applying this within the RemoveTransitIfStreetOnlyIsBetter filter because that's strictly for StreetOnly. It also wouldn't be much better to rename everything to be a more generic "-IfDirectIsBetter" (like I've one in commits 3-4 in this PR), because we wouldn't be able to configure flex separately from street only. (They are separate concerns and the possible future need for separate configuration is evidence for that). In particular, the costLimitFunction (e.g. "60 + 1.3x") would make sense to configure separately. Instead, we've considered some alternative options:
|
Summary
There is a filter that removes transit itineraries that are worse than a "street only" itinerary (walk, bike, or car). This PR also enables this for direct flex itineraries. The code is organized in 4 commits that can be reviewed (maybe even merged) independently:
router-config.jsonvariables to reflect the renamingBefore merging this I would suggest we test out the fix alone (as Entur test) without breaking config, so only commits 1-3 or maybe even just 1-2.
Issue
Closes #7108
Reproduction url
This query:
{ first: trip( from: {place: "NSR:StopPlace:44672"} to: {place: "NSR:StopPlace:58950"} modes: {accessMode: flexible, directMode: flexible, egressMode: flexible, transportModes: [{transportMode: bus}, {transportMode: rail}]} dateTime: "2026-04-09T09:00:00+02:00" numTripPatterns: 1 searchWindow: 240 itineraryFilters: {debug: limitToSearchWindow} ) { nextPageCursor tripPatterns { generalizedCost systemNotices { tag } legs { aimedStartTime mode line { id } fromPlace { name } toPlace { name } } } } second: trip( pageCursor: "MnxORVhUX1BBR0V8MjAyNi0wNC0wOVQwOTo1Mzo1OVp8fDFofFNUUkVFVF9BTkRfQVJSSVZBTF9USU1FfGZhbHNlfDIwMjYtMDQtMDlUMDc6MjQ6NDVafDIwMjYtMDQtMDlUMDc6NTg6MDNafDB8NDU1MHx8" from: {place: "NSR:StopPlace:44672"} to: {place: "NSR:StopPlace:58950"} modes: {accessMode: flexible, directMode: flexible, egressMode: flexible, transportModes: [{transportMode: bus}, {transportMode: rail}]} dateTime: "2026-04-09T09:00:00+02:00" numTripPatterns: 1 searchWindow: 240 itineraryFilters: {debug: limitToSearchWindow} ) { nextPageCursor tripPatterns { generalizedCost systemNotices { tag } legs { aimedStartTime mode line { id } fromPlace { name } toPlace { name } } } } }Returns the following. Notice how the second page includes two redundant rail legs.
{ "data": { "first": { "nextPageCursor": "MnxORVhUX1BBR0V8MjAyNi0wNC0wOVQwOTo1Mzo1OVp8fDFofFNUUkVFVF9BTkRfQVJSSVZBTF9USU1FfGZhbHNlfDIwMjYtMDQtMDlUMDc6MjQ6NDVafDIwMjYtMDQtMDlUMDc6NTg6MDNafDB8NDU1MHx8", "tripPatterns": [ { "generalizedCost": 4550, "systemNotices": [], "legs": [ { "aimedStartTime": "2026-04-09T09:24:45+02:00", "mode": "foot", "line": null, "fromPlace": { "name": "Frosta skole" }, "toPlace": { "name": "Frosta sentrum" } }, { "aimedStartTime": "2026-04-09T09:30:00+02:00", "mode": "bus", "line": { "id": "ATB:Line:2_635" }, "fromPlace": { "name": "Frosta sentrum" }, "toPlace": { "name": "Åsen E6" } }, { "aimedStartTime": "2026-04-09T09:55:00+02:00", "mode": "foot", "line": null, "fromPlace": { "name": "Åsen E6" }, "toPlace": { "name": "Åsen stasjon" } } ] }, { "generalizedCost": 3079, "systemNotices": [ { "tag": "number-of-itineraries-filter" } ], "legs": [ { "aimedStartTime": "2026-04-09T11:53:59+02:00", "mode": "foot", "line": null, "fromPlace": { "name": "Frosta skole" }, "toPlace": { "name": "krysset mellom Alstadvegen og gangvei" } }, { "aimedStartTime": "2026-04-09T11:59:00+02:00", "mode": "bus", "line": { "id": "ATB:FlexibleLine:13d3f9fe-31f3-578d-867f-2f53dcfa6aa9" }, "fromPlace": { "name": "krysset mellom Alstadvegen og gangvei (del av Frosta sentrum (busstopp))" }, "toPlace": { "name": "krysset mellom gangvei og stikkvei (del av Levanger )" } }, { "aimedStartTime": "2026-04-09T12:16:47+02:00", "mode": "foot", "line": null, "fromPlace": { "name": "krysset mellom gangvei og stikkvei" }, "toPlace": { "name": "Åsen stasjon" } } ] }, // ... other filtered itineraries ] }, "second": { "nextPageCursor": "MnxORVhUX1BBR0V8MjAyNi0wNC0wOVQwOTo1NDo1OVp8fDFofFNUUkVFVF9BTkRfQVJSSVZBTF9USU1FfGZhbHNlfDIwMjYtMDQtMDlUMDk6NTQ6NTlafDIwMjYtMDQtMDlUMTE6MjI6MDBafDJ8MTE1Nzh8fA==", "tripPatterns": [ { "generalizedCost": 11578, "systemNotices": [], "legs": [ { "aimedStartTime": "2026-04-09T11:54:59+02:00", "mode": "foot", "line": null, "fromPlace": { "name": "Frosta skole" }, "toPlace": { "name": "krysset mellom Alstadvegen og gangvei" } }, { "aimedStartTime": "2026-04-09T12:00:00+02:00", "mode": "bus", "line": { "id": "ATB:FlexibleLine:13d3f9fe-31f3-578d-867f-2f53dcfa6aa9" }, "fromPlace": { "name": "krysset mellom Alstadvegen og gangvei (del av Frosta sentrum (busstopp))" }, "toPlace": { "name": "krysset mellom gangvei og stikkvei (del av Levanger )" } }, { "aimedStartTime": "2026-04-09T12:18:00+02:00", "mode": "foot", "line": null, "fromPlace": { "name": "krysset mellom gangvei og stikkvei" }, "toPlace": { "name": "Åsen stasjon" } }, { "aimedStartTime": "2026-04-09T12:37:00+02:00", "mode": "rail", "line": { "id": "SJN:Line:26" }, "fromPlace": { "name": "Åsen stasjon" }, "toPlace": { "name": "Skatval stasjon" } }, { "aimedStartTime": "2026-04-09T13:06:00+02:00", "mode": "rail", "line": { "id": "SJN:Line:26" }, "fromPlace": { "name": "Skatval stasjon" }, "toPlace": { "name": "Åsen stasjon" } } ] } ] } } }Notice that the decoded cursor has cost 4550, from the first transit itinerary that should actually have been filtered out.
MnxORVhUX1BBR0V8MjAyNi0wNC0wOVQwOTo1Mzo1OVp8fDFofFNUUkVFVF9BTkRfQVJSSVZBTF9USU1FfGZhbHNlfDIwMjYtMDQtMDlUMDc6MjQ6NDVafDIwMjYtMDQtMDlUMDc6NTg6MDNafDB8NDU1MHx82|NEXT_PAGE|2026-04-09T09:53:59Z||1h|STREET_AND_ARRIVAL_TIME|false|2026-04-09T07:24:45Z|2026-04-09T07:58:03Z|0|4550||With the fix applied:
generalizedCostMaxLimitso that the itinerary with two extra rail legs gets filtered out! Thus fixing the bug.Unit tests
New test case is added to check the transit itinerary gets removed in the presence of a better direct flex itinerary.
Documentation
Javadocs updated in commit 3 after changing the concept to allow direct flex.