Skip to content

Conversation

@t2gran
Copy link
Member

@t2gran t2gran commented Nov 28, 2025

Summary

Build on top of #7095 and #7093.

Add support for via-location visits in access and egress paths in Raptor. This enables via points to be reached as part of access or egress. Currently we do not support flex, but the Raptor code do.

This PR extends Raptor's via-location search capability to handle via points that are visited in access or egress paths, not just during transit routing.

How the code works

See the Raptor desing doc

The implementation tracks the number of via locations visited in access/egress paths and uses a chain of Raptor workers+state. When a via-location is reached the state for that stop is coped over the the next segment. Driving this is a composit-worker and stop-arrival-events at the via-stops.

Key classes:

  • RangeRaptorWorkerComposite - chains Raptor searches together
  • RouterResultPathAggregator - aggregates results from multiple segments
  • ViaConnectionStopArrivalEventListener - copies stop arrivals between segments
  • ArrivalsEventListenerMapper - builds and orders arrival listeners
  • AccessEgressFunctions - filters access/egress by via count

Issue

Part of #4887

Unit tests

  • Added comprehensive module tests:
    • J04_ViaVisitWithAccessTest - tests via locations visited in access paths
    • J05_ViaVisitWithEgressTest - tests via locations visited in egress paths
  • Updated existing tests

Documentation

✅ Design doc
✅ JavaDoc

Changelog

✅ Should be part of support for via

@t2gran t2gran added this to the 2.9 (next release) milestone Nov 28, 2025
@t2gran t2gran requested a review from a team as a code owner November 28, 2025 17:23
@t2gran t2gran added the !New Feature A functional feature targeting the end user. label Nov 28, 2025
@t2gran t2gran force-pushed the via-access-egress-raptor-impl branch from b80158a to 9d53204 Compare November 28, 2025 17:26
@t2gran t2gran requested a review from optionsome November 28, 2025 17:28
@codecov
Copy link

codecov bot commented Nov 28, 2025

Codecov Report

❌ Patch coverage is 94.68085% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.13%. Comparing base (fc3f984) to head (3469c83).
⚠️ Report is 23 commits behind head on dev-2.x.

Files with missing lines Patch % Lines
...angeraptor/support/RouterResultPathAggregator.java 73.33% 4 Missing ⚠️
...r/raptor/util/paretoset/ParetoSetWithListener.java 75.00% 0 Missing and 3 partials ⚠️
...entripplanner/raptor/api/request/SearchParams.java 95.23% 1 Missing ⚠️
...raptor/rangeraptor/RangeRaptorWorkerComposite.java 96.00% 0 Missing and 1 partial ⚠️
...lanner/raptor/rangeraptor/transit/AccessPaths.java 92.85% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             dev-2.x    #7101      +/-   ##
=============================================
+ Coverage      72.09%   72.13%   +0.04%     
- Complexity     20706    20757      +51     
=============================================
  Files           2241     2242       +1     
  Lines          83725    83874     +149     
  Branches        8355     8380      +25     
=============================================
+ Hits           60362    60503     +141     
- Misses         20433    20437       +4     
- Partials        2930     2934       +4     

☔ 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.

.max()
.orElse(0);

if (maxAccessViaVisits + maxEgressViaVisits > numberOfViaVisits) {
Copy link
Member

Choose a reason for hiding this comment

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

This logic needs to be updates since the same via can be visited both in access and egress.

"Stop Arrival - w/egress",
StopArrivalParetoSet.of(comparator).withEgressListener(List.of(), null).build()
)
Arguments.of("Stop Arrival - regular", ParetoSet.of(comparator)),
Copy link
Contributor

Choose a reason for hiding this comment

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

they are both the same test case just under a different name

null
accessPaths.filterOnSegment(i),
viaConnection,
egressPaths.filterOnSegment(nConnections - i)
Copy link
Contributor

Choose a reason for hiding this comment

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

since you don't set the egress paths to null anymore, they shouldn't be nullable in the SearchContextViaSegments either

);
}

static TIntObjectMap<List<RaptorAccessEgress>> filter(
Copy link
Contributor

Choose a reason for hiding this comment

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

since this is used by both AccessPaths and EgressPaths I think this method might be better located in AccessEgressFunctions.

}

/**
* Sets the next leg state. This is used to connect the state created by this config with the
Copy link
Contributor

Choose a reason for hiding this comment

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

some java doc still refers to segments as legs


public static <T extends RaptorTripSchedule> TIntObjectMap<
ParetoSetEventListener<ArrivalView<T>>
> of(
Copy link
Contributor

Choose a reason for hiding this comment

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

It's nice to separate this logic out, but I am not sure I like the naming here. When I call xxx.of(...) I'd expect to get a new instance of type xxx

}

@Override
public boolean remove(Object o) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why could this be deleted?


if (firstVectorAfterMarker == null) {
set.forEach(it -> buf.add(it.toString()));
set.stream().forEach(it -> buf.add(it.toString()));
Copy link
Contributor

Choose a reason for hiding this comment

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

why did you add .stream() here and in the else block? Using a stream before forEach is just overhead, isn't it?

* while using this iterator.
*/
private Iterator<T> tailIterator(final int startInclusive) {
private final Iterator<T> tailIterator(final int startInclusive) {
Copy link
Contributor

Choose a reason for hiding this comment

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

private methods can't be overridden, so the final indicator is redundant

}

@Override
protected final void notifyElementAccepted(T newElement) {
Copy link
Contributor

Choose a reason for hiding this comment

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

the class is final, so the methods don't need to be


@Override
protected final void notifyElementDropped(T element, T droppedByElement) {
if (eventListener != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

the if clause is not necessary anymore. I'm not sure how much is actually won by putting the listener stuff into a child class. It's basically just dropping this if clause and having the very basic logic of calling the eventListener in another place. Not sure.

/**
* In a via-search (both pass-through and visit-via) the access/egress may contain one
* ore more via-locations. If so Raptor needs to know how many via-locations are included
* ore more via-locations. If so, Raptor needs to know how many via-locations are included
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
* ore more via-locations. If so, Raptor needs to know how many via-locations are included
* or more via-locations. If so, Raptor needs to know how many via-locations are included

Comment on lines +152 to +170
default void validateAccessEgressVisitVia(int numberOfViaLocations) {
int viaVisits = numberOfViaLocationsVisited();

if (viaVisits < 0) {
throw new IllegalArgumentException(
"Access/Egress cannot have negative via visits: " + viaVisits
);
}

if (viaVisits > numberOfViaLocations) {
throw new IllegalArgumentException(
"Access/Egress visits " +
viaVisits +
" via locations, but only " +
numberOfViaLocations +
" are defined"
);
}
}
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 this kind of validation should be when you set the number of via locations instead of when you try to use them?

* the "glue" make sure new destination arrivals are added to the destination arrivals.
*/
private TIntObjectMap<ParetoSetEventListener<ArrivalView<T>>> map() {
// debugListener, nextSearchListener, egressListener
Copy link
Member

Choose a reason for hiding this comment

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

?

public class RouterResultPathAggregator<T extends RaptorTripSchedule>
implements RaptorRouterResult<T> {

private final RaptorRouterResult<T> master;
Copy link
Member

Choose a reason for hiding this comment

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

What does master mean in this context?

set.clear();
set.addAll(list);
result.addAll(set);
result.addAll(set.stream().toList());
Copy link
Member

Choose a reason for hiding this comment

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

You are repeating this stream.ToList() on at least a couple of places. Should ParetoSet itself have asList method?

assertEquals(
"""
Walk 1m ~ A ~ BUS R1 0:05 0:15 ~ C ~ BUS R2 0:18 0:20 ~ D ~ Walk 1m [0:04 0:21 17m Tₙ1 C₁2_340]
Walk 1m ~ A ~ BUS R1 0:05 0:10 ~ B ~ Walk 15m Vₙ1 [0:04 0:25 21m Tₙ0 C₁2_820]""",
Copy link
Member

Choose a reason for hiding this comment

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

The formatting is a bit odd with """ at the end being on the same line. Is this something that prettier forces or something you can affect? There are other instances of this formatting in this class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

!New Feature A functional feature targeting the end user.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants