Skip to content

Conversation

@tobsesHub
Copy link
Contributor

Summary

consider vehicles[].available_until field vehicle_status.json in direct routing

Issue

#6547

This is the implementation for two steps explained in this commend. #6547 (comment)

  • Support availability in direct routing by only boarding vehicles witch are available in a fixed time-window by using the departure time(when the rental of the vehicle starts)
  • Add time-window(as Duration) for availability to the GTFS-GraphQL API and configuration(routing defaults). I named the field rentalDuration. rentalDuraton is zero by default. If it is negative or zero, it will not be considered. Therefore it should not change the previous behavior, if the new parameter was not used.

Unit tests

  • added tests for routerequestMapper for the new rentalDuration field(gtfs graphql api)
  • added tests for the availability in VehicleRentalEdgeTest. For that use case updated TestFreeFloatingRentalVehicleBuilder and StreetModelForTest
  • updated timestamp in rental-vehicle.json for the gtfs-graphql api integreation test

Documentation

  • documentation update for the new rentalDuration field in the configuration (generated RouteRequest.md)

@tobsesHub tobsesHub requested a review from a team as a code owner June 12, 2025 18:56
@tobsesHub tobsesHub closed this Jun 13, 2025
@tobsesHub tobsesHub reopened this Jun 13, 2025
@optionsome
Copy link
Member

Perhaps only use this for car rental as it's probably the only thing you are interested in? Could you join a developer meeting at some point to discuss this again?

@tobsesHub
Copy link
Contributor Author

Perhaps only use this for car rental as it's probably the only thing you are interested in? Could you join a developer meeting at some point to discuss this again?

Yes I actually just interested in car rental, so I could do it just for the car rental.
Yes, I will be in the developer meeting on Thursday the 26. Than we can discuss about this pr. Sorry I could not attend the meetings before.

@tobsesHub tobsesHub marked this pull request as draft July 3, 2025 11:15
@tobsesHub
Copy link
Contributor Author

First of all draft. I continue with this pr later.

@codecov
Copy link

codecov bot commented Jul 9, 2025

Codecov Report

❌ Patch coverage is 88.57143% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.24%. Comparing base (e9e4dff) to head (0fec972).
⚠️ Report is 82 commits behind head on dev-2.x.

Files with missing lines Patch % Lines
...ervice/vehiclerental/street/VehicleRentalEdge.java 75.00% 1 Missing and 2 partials ⚠️
...ntripplanner/apis/gtfs/generated/GraphQLTypes.java 50.00% 2 Missing ⚠️
...fs/mapping/routerequest/ModePreferencesMapper.java 85.71% 0 Missing and 2 partials ⚠️
...ner/routing/api/request/request/StreetRequest.java 91.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             dev-2.x    #6697      +/-   ##
=============================================
+ Coverage      72.18%   72.24%   +0.05%     
- Complexity     19840    19933      +93     
=============================================
  Files           2155     2163       +8     
  Lines          80051    80284     +233     
  Branches        8082     8113      +31     
=============================================
+ Hits           57786    57998     +212     
- Misses         19419    19422       +3     
- Partials        2846     2864      +18     

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

@tobsesHub tobsesHub marked this pull request as ready for review July 10, 2025 09:40
@tobsesHub tobsesHub force-pushed the feat/rental-routing-avaibility-window branch from bbd6ddb to 3af25f0 Compare July 17, 2025 14:18
@optionsome optionsome requested a review from t2gran August 5, 2025 08:52
@t2gran t2gran added this to the 2.8 (next release) milestone Aug 11, 2025
@tobsesHub tobsesHub force-pushed the feat/rental-routing-avaibility-window branch from 3af25f0 to f1b62aa Compare August 14, 2025 09:30
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 think access/egress time-shifting support (as discussed in dev meeting) should be added in a follow up PR. Until then the API request parameter should be documented as experimental.

Implementing the Access/Egress opening hours can be done extending the DefaultAccessEgress and implementing: earliestDepartureTime(int requestedDepartureTime), latestArrivalTime(int requestedArrivalTime), numberOfRides()and hasOpeningHours()

Should the rentalDuration be relative to the requestTime or the pick-up-time, I am leaning towards requestTime using pick-up time may lead to very strange results (same problem as checking the time constraints during routing).

this.pickupCost = builder.pickupCost;
this.dropOffTime = Duration.ofSeconds(Units.duration(builder.dropOffTime));
this.dropOffCost = builder.dropOffCost;
this.rentalDuration = Duration.ofSeconds(Units.duration(builder.rentalDuration));
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 validate the duration here by calling DurationUtils#requireNonNegative.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added this validatio to ModePreferencesMapper

Comment on lines 129 to 133
Instant departureTime = s0.getTime().plus(preferences.pickupTime());
OffsetDateTime rentalEndTime = OffsetDateTime.ofInstant(
departureTime,
ZoneId.systemDefault()
).plus(preferences.rentalDuration());
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 rental available period should be relative to the request time, not relative to the pickup-time. For depart after [request.dateTime, +rentalDuration] and for arrive by [-rentalDuration, request.dateTime]. This should be calculated and set in the StreetSearchRequest and checked here.

Unsure about the time-zone, using ZoneId.systemDefault() seams wrong. Also, I think the time calculations should use the internal AStar model time format.

@leonardehrenfried & @optionsome What happens if we traverse the graph in reverse - do we ever do that?

Copy link
Member

Choose a reason for hiding this comment

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

What should we do if the journey can not be completed within the rentalDuration? I guess this should end up in a empty result. So I think there need to be a check that we have not exceeded the rentalDuration limit her as well.

Copy link
Contributor Author

@tobsesHub tobsesHub Aug 20, 2025

Choose a reason for hiding this comment

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

hmm I don't get what's the problem by using the pickup-time.
The pickup time seems to be more more precise to me as it really just considers the rental duration.
But if there is any experience with similar cases, I can change it.

Copy link
Member

@t2gran t2gran Aug 21, 2025

Choose a reason for hiding this comment

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

The problem using the pick-up time is that if you have two paths to walk to the rental A, and B. Then A takes 5 min and B 6 min. Then the rental is dropped for path A but accepted for path B. Then the returned result is not optimal. It is possible that path B i dropped in the "from" vertex state - if so this would be ok. Another problem is how to explain this to the end user. Let say there are 2 cars available from 12:00 to 15:00, you start walking 11:50. It takes 9 minutes to the first and 30 minutes to the second. rentalDuration is 1h. In this case the best option is to walk to the first car and WAIT 1 minute. Can you explain why we are not checking the "available from time"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do not really have the "available from time" right now. The GBFS standard support currently available vehicles and the availableUntil fiel. So we do not have the informatin what is the start time, on which a vehicle is available.

Copy link
Member

Choose a reason for hiding this comment

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

Thank you for the clarification. This was discussed shortly at todays developer meeting, and we decided:

  • to use departure-time + rentalDuration for depart-after-search. For arrival search the vehicle need to be available at when the search start. An the search should be stopped when the time is less than the arrival-time - rentalDuration.
  • Any rentalDuration value needs to be cleared before we do access/egress search since time-shifting is not supported. This can be implemented later - if someone needs it.
  • The following text should be added to the rentalDuration doc (domain model JavaDoc and API doc):

The rentalDuration only apply to direct search, support for transit search is not implemented.

Copy link
Contributor Author

@tobsesHub tobsesHub Sep 2, 2025

Choose a reason for hiding this comment

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

I changed the departure by using the start time of the StreetSearchRequest.
I also updated the behavior of arrival requests. I used the start time (arrival time) of the StreetSearchRequest to check, if the vehicle is available at this arrival time.

But I am not sure about "An the search should be stopped when the time is less than the arrival-time - rentalDuration" What exactly do you mean with "time". Maybe we can clarify that in the dev meeting.

I added the describing sentence to the GreaphQL parameter(schema) and as Java doc to the rentalDuration property in StreetRequest and StreetSearchRequest.

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 modelled the startTime and endTime of the rentalduration as RentalPeriod and get build in the StreetSearchRequestMapper.

@t2gran
Copy link
Member

t2gran commented Aug 21, 2025

I am unsure what we need to do to make this work with access/egress and regular transit. There are a few things that need investigation:

  • How to do time-shifting of access/egress. The business rules need to be described.
  • There need to be a walk only option for all stops, witch can be used when time-shifting the rental option is not available.

If this feature is not supported for access/egress then we need to make sure it is disabled in the access/egress search. Either of these two options needs to be implemented in this PR.

Comment on lines 93 to 95
* An assumed duration of the rental trip, to make sure the vehicle is available during this time
*/
public Duration rentalDuration() {
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
* An assumed duration of the rental trip, to make sure the vehicle is available during this time
*/
public Duration rentalDuration() {
* An assumed duration of the rental trip, to make sure the vehicle is available during this time
*/
@Nullable
public Duration rentalDuration() {

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 added nullable annotation to the rentalDuration property of the streetRequest and streetSearchRequest

Comment on lines 129 to 133
Instant departureTime = s0.getTime().plus(preferences.pickupTime());
OffsetDateTime rentalEndTime = OffsetDateTime.ofInstant(
departureTime,
ZoneId.systemDefault()
).plus(preferences.rentalDuration());
Copy link
Member

Choose a reason for hiding this comment

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

Thank you for the clarification. This was discussed shortly at todays developer meeting, and we decided:

  • to use departure-time + rentalDuration for depart-after-search. For arrival search the vehicle need to be available at when the search start. An the search should be stopped when the time is less than the arrival-time - rentalDuration.
  • Any rentalDuration value needs to be cleared before we do access/egress search since time-shifting is not supported. This can be implemented later - if someone needs it.
  • The following text should be added to the rentalDuration doc (domain model JavaDoc and API doc):

The rentalDuration only apply to direct search, support for transit search is not implemented.

@tobsesHub tobsesHub marked this pull request as draft August 28, 2025 11:32
@tobsesHub tobsesHub force-pushed the feat/rental-routing-avaibility-window branch 2 times, most recently from 2f5f8e1 to f217651 Compare September 2, 2025 15:29
@tobsesHub tobsesHub marked this pull request as ready for review September 2, 2025 15:49
@tobsesHub
Copy link
Contributor Author

Before merging this PR, I would like to squash the commits. There are now quite a few commits for just one feature.

@t2gran t2gran modified the milestones: 2.8, 2.9 (next release) Sep 10, 2025
@tobsesHub tobsesHub force-pushed the feat/rental-routing-avaibility-window branch from 95b841a to c0b9a69 Compare September 18, 2025 11:56
@t2gran
Copy link
Member

t2gran commented Oct 13, 2025

@tobsesHub Can you go over the pending review comments.

return ToStringBuilder.ofEmbeddedType().addEnum("mode", mode, DEFAULT.mode).toString();
return ToStringBuilder.ofEmbeddedType()
.addEnum("mode", mode, DEFAULT.mode)
.addObj("rentalDuration", rentalDuration, DEFAULT.rentalDuration)
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
.addObj("rentalDuration", rentalDuration, DEFAULT.rentalDuration)
.addDuration("rentalDuration", rentalDuration, DEFAULT.rentalDuration)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed it.

return mode;
}

public Duration rentalDuration() {
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
public Duration rentalDuration() {
@Nullable
public Duration rentalDuration() {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed it.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

@tobsesHub tobsesHub Oct 23, 2025

Choose a reason for hiding this comment

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

added nullable to rentalPeriod()

boolean wheelchair;
GenericLocation from;
GenericLocation to;
Duration rentalDuration;
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
Duration rentalDuration;
@Nullable
Duration rentalDuration;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed it

}
var streetModes = direct.stream().map(DirectModeMapper::map).toList();
journey.withDirect(new StreetRequest(getStreetModeForRouting(streetModes)));

Copy link
Member

Choose a reason for hiding this comment

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

There is a lot of repetition in the code below, consider:

if(a != null) {
  var b = a.b;
  if(b != null) {
    var c = b.c
    if(c != null) {
      ... 
    }
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually I do not like both, because this solution has lots of nesting instead, but I think it is less error prone, so it should be better. I outsourced it to a static method. I hope makes the code more readable.

}
StreetRequest that = (StreetRequest) o;
return mode == that.mode;
return (mode == that.mode && Objects.equals(rentalDuration, that.rentalDuration));
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
return (mode == that.mode && Objects.equals(rentalDuration, that.rentalDuration));
return (mode == that.mode) && Objects.equals(rentalDuration, that.rentalDuration);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed it.

Comment on lines +22 to +25
public StreetRequest(StreetMode mode, Duration rentalDuration) {
this.mode = mode;
this.rentalDuration = rentalDuration;
}
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 this is good enough, but the next time we add a new parameter to this class, we should make a builder.

@Nonnull
private final Instant rentalEndTime;

public RentalPeriod(RentalPeriodBuilder rentalPeriodBuilder) {
Copy link
Member

Choose a reason for hiding this comment

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

Consider: pass in rentalStartTime and rentalEndTime here, not the builder.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed it.

RentalPeriod rentalPeriod = new RentalPeriodBuilder()
.setRentalStartTime(rentalStart)
.setRentalEndTime(rentalEnd)
.build();
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 can be moved inside the RentalPeriodBuilder - then it become easy to unit-test. Instead
of the regular builder pattern it is better to create two builder methods:

var builder = RentalPeriod.of(time, rentalDuration);
var rentalPeriod = request.arriveBy()
    ? builder.buildFromLatestArrivalTime()
    : builder.buildFromEarliestDepartureTime();

Copy link
Contributor Author

@tobsesHub tobsesHub Oct 23, 2025

Choose a reason for hiding this comment

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

good advice, thanks, I changed it that way.

I also was thinking, to add the arriveBy to the builder. Than the whole logic would be at one place at the RentalPeriodBuilder. But I was not sure, if it is a good idea.

if (availableUntil != null && availableUntil.isBefore(rentalEndTime)) {
return State.empty();
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Why is there 2 identical code blocks here?

Copy link
Contributor Author

@tobsesHub tobsesHub Oct 23, 2025

Choose a reason for hiding this comment

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

Good point, there is no good reason. I added a isRentalVehicleAvailableDuringRentalPeriod method, to prevent the duplicate code blocks.

@tobsesHub tobsesHub marked this pull request as draft October 16, 2025 07:38
@tobsesHub tobsesHub force-pushed the feat/rental-routing-avaibility-window branch from c0b9a69 to 0fec972 Compare October 23, 2025 17:52
@tobsesHub tobsesHub marked this pull request as ready for review October 23, 2025 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants