Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generalizedCostMaxLimit field to the PageCursor to enable using RemoveTransitIfStreetOnlyIsBetter filter with paging #6474

Open
wants to merge 50 commits into
base: dev-2.x
Choose a base branch
from

Conversation

VillePihlava
Copy link
Contributor

@VillePihlava VillePihlava commented Feb 20, 2025

Summary

This PR adds the generalizedCostMaxLimit field to the PageCursor. This enables using the RemoveTransitIfStreetOnlyIsBetter filter with paging. Before this PR, this was not possible because paging only used the walk mode to create a direct itinerary which made the filter work incorrectly.

Issue

N/A

Unit tests

Changed existing unit tests for compatibility and created new ones. Also performed manual testing.

Documentation

Code comments, no other documentation

@VillePihlava VillePihlava requested a review from a team as a code owner February 20, 2025 12:44
Copy link

codecov bot commented Feb 20, 2025

Codecov Report

Attention: Patch coverage is 89.26174% with 16 lines in your changes missing coverage. Please review.

Project coverage is 71.06%. Comparing base (9a5c2aa) to head (d830c17).
Report is 371 commits behind head on dev-2.x.

Files with missing lines Patch % Lines
...thm/filterchain/paging/DefaultPageCursorInput.java 82.14% 5 Missing ⚠️
...ansit/RemoveTransitIfStreetOnlyIsBetterResult.java 25.00% 3 Missing ⚠️
...m/filterchain/ItineraryListFilterChainBuilder.java 81.81% 2 Missing ⚠️
...rithm/mapping/RouteRequestToFilterChainMapper.java 33.33% 1 Missing and 1 partial ⚠️
...er/model/plan/paging/cursor/PageCursorFactory.java 94.73% 1 Missing ⚠️
...entripplanner/routing/algorithm/RoutingWorker.java 50.00% 0 Missing and 1 partial ⚠️
...ain/filters/system/NumItinerariesFilterResult.java 83.33% 1 Missing ⚠️
.../opentripplanner/service/paging/PagingService.java 91.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             dev-2.x    #6474      +/-   ##
=============================================
+ Coverage      70.25%   71.06%   +0.81%     
+ Complexity     18385    18321      -64     
=============================================
  Files           2088     2008      -80     
  Lines          77417    75962    -1455     
  Branches        7840     7786      -54     
=============================================
- Hits           54386    53983     -403     
+ Misses         20262    19232    -1030     
+ Partials        2769     2747      -22     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@VillePihlava
Copy link
Contributor Author

I removed the direct mode search with paging and refactored bestStreetOnlyCost to streetOnlyCost. This should now be ready-for-review.

@VillePihlava VillePihlava changed the title Add bestStreetOnlyCost field to the PageCursor to enable using RemoveTransitIfStreetOnlyIsBetter filter with paging Add streetOnlyCost field to the PageCursor to enable using RemoveTransitIfStreetOnlyIsBetter filter with paging Feb 21, 2025
@VillePihlava VillePihlava changed the title Add streetOnlyCost field to the PageCursor to enable using RemoveTransitIfStreetOnlyIsBetter filter with paging Add generalizedCostMaxLimit field to the PageCursor to enable using RemoveTransitIfStreetOnlyIsBetter filter with paging Mar 6, 2025
@t2gran t2gran added this to the 2.8 (next release) milestone Mar 12, 2025
@VillePihlava
Copy link
Contributor Author

This PR depends on how the new logic in RemoveTransitIfStreetOnlyIsBetter should be structured. I did not fix all comments because of this. We had a short discussion with @optionsome and @t2gran on what to do, but a decision was not made. Once it is clear what is wanted, I'll make the changes

Copy link
Member

@t2gran t2gran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a fast architecture review - I did not read all code-lines. If you want we can go through it in a meeting.

Comment on lines 3 to 4
import org.opentripplanner.routing.algorithm.filterchain.filters.system.NumItinerariesFilterResult;
import org.opentripplanner.routing.algorithm.filterchain.filters.transit.RemoveTransitIfStreetOnlyIsBetterResult;
Copy link
Member

@t2gran t2gran Mar 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PageCursorInput define a role implemented by the itinerary filter chain - we did this to invert the dependencies and to decouple the paging from the itinerary filter chain. There is no point in having the implementation of an interface in the same package (unless its just the default of many) - it indicates that there is a design mistake.

There should NOT be any dependencies from paging to the itinerary-filter-chain.

So, this should be moved inside the filter-chain, witch should be responsible for produsing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the DefaultPageCursorInput into the org.opentripplanner.routing.algorithm.filterchain package

Comment on lines 290 to 293
DefaultPageCursorInput.of()
.withNumItinerariesFilterResult(numItinerariesFilterResult)
.withRemoveTransitIfStreetOnlyIsBetterResult(removeTransitIfStreetOnlyIsBetterResult)
.build(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the itinerary-filter-chain should be responsible for producing the PageCursorInput - this adds an extra layer of abstraction, but the coupling at field/information level is so high, that I do not se the point of doing it. The responsibility of the RoutingWorker is to wire things together, not mapping from one domain to another. The mapping logic should then be extracted, but it is simpler to just push it into he filter-chain.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, we need the result of filters to produce the event to send back to the paging. There are many ways to do it:

Alt 1. We can store the result in the filters and add the filters to a result aggregator. When the filter-chain is compleate, we notify the aggregator and it produces the paging event.

Alt 2. The filters can send a event to the result aggregator and the aggregator can send an event to the paging when it has rescieved an event from both of the filters. This is complex, and if one of the filters is disabled it might fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a PageCursorInputAggregator and use a callback to set the PageCursorInput

@VillePihlava VillePihlava requested a review from optionsome March 28, 2025 13:53
this.pageCursorInputSubscriber = builder.pageCursorInputSubscriber();
}

public void createPageCursorInput() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if create is the right word here since this is not a factory method. Maybe providePageCursorInput?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to providePageCursorInput

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test class should be restored.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I restored it and added new tests for when the pagecursor exists

}

public FilterTransitWhenDirectModeIsEmpty(StreetMode originalDirectMode) {
public FilterTransitWhenDirectModeIsEmpty(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should update this class' javadoc and add some to the public methods as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the class' javadoc and added documentation to two methods

@optionsome
Copy link
Member

I resolved the comments that I think have been fixed/are not relevant anymore. I think the ones that are open either are still relevant, need changes and/or should be taken a look at by @t2gran if everything is ok now.

Comment on lines +18 to +20
* The earliest-removed-departure defines the start of the search-window following the
* current window. To include this removed itinerary (and all other removed itineraries)
* in the next-page search the search windows must overlap.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct when searching to the other direction? The purpose of latestRemovedDeparture should probably be also explained.

* current window. To include this removed itinerary (and all other removed itineraries)
* in the next-page search the search windows must overlap.
* <p>
* In case the result has too many results: The {@code numberOfItineraries} request parameter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* In case the result has too many results: The {@code numberOfItineraries} request parameter
* In case the result has too many results, if the {@code numberOfItineraries} request parameter

Comment on lines +23 to +24
* is less than the number of itineraries found, then we keep the last itinerary kept and
* returned as part of the result. The sort vector will be included in the page-cursor and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "then we keep the last itinerary kept and returned as part of the result" part is difficult to understand.

Comment on lines +23 to +24
* @param costLimitFunction the cost limit function to use with the filter
* @param generalizedCostMaxLimit this limit is not null when paging is used
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the param can be null in some case, it should be marked as @Nullable and field can be marked as such also.

Comment on lines +35 to +38
* based on a direct mode used in the search. We achieve the filtering by setting the 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 is assigned back to the original state.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* based on a direct mode used in the search. We achieve the filtering by setting the 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 is assigned back to the original state.
* based on a direct search cost from the original search. We achieve the filtering by setting the
* 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 is assigned back to the original state.

Comment on lines +72 to +75
/**
* If the direct mode was not set and if the page cursor does not exist,
* the WALK mode is returned for filtering itineraries.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class feels really weird in many ways. The name makes it sound like this is a filter but this actually isn't one. This class has too many responsibilities in my opinion and this pr makes it even harder to follow the logic. It feels a bit hacky how we need to set the direct mode back-and-forth in many places. However, we have plans in the future to perhaps refactor the "direct filter" logic a bit so maybe this isn't the right time to do refactoring here.

* The earliest-removed-departure defines the start of the search-window following the
* current window. To include this removed itinerary (and all other removed itineraries)
* in the next-page search the search windows must overlap.
* This contains the results from {@link org.opentripplanner.routing.algorithm.filterchain.filters.system.NumItinerariesFilter}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still haven't decided what to do about this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Improvement A functional improvement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants