diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md index c00502b2726..ea3d0d12c74 100644 --- a/doc/user/RouteRequest.md +++ b/doc/user/RouteRequest.md @@ -13,171 +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 | -|    [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 | @@ -731,6 +732,23 @@ convenient when tuning the itinerary-filter-chain. moving to the next page. +

filterDirectFlexBySearchWindow

+ +**Since version:** `2.7` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `true` +**Path:** /routingDefaults/itineraryFilters + +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. + +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. + +

filterItinerariesWithSameFirstOrLastTrip

**Since version:** `2.2` ∙ **Type:** `boolean` ∙ **Cardinality:** `Optional` ∙ **Default value:** `false` 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..51bf5f344f6 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,42 +64,48 @@ static void SetUp() { @Test void testGetEmissionsForItinerary() { - Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_EMISSIONS))); + 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)); + 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))); + Itinerary i = createScheduledTransitItinerary( + List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED)) + ); decorateWithEmission.decorate(i); assertNull(i.getEmissionsPerPerson()); } @Test void testZeroEmissionsForItineraryWithZeroEmissions() { - Itinerary i = new Itinerary(List.of(createTransitLeg(ROUTE_WITH_ZERO_EMISSIONS))); + Itinerary i = 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)); + Itinerary i = 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( + Itinerary i = createScheduledTransitItinerary( List.of(createTransitLeg(ROUTE_WITHOUT_EMISSIONS_CONFIGURED), STREET_LEG) ); decorateWithEmission.decorate(i); 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..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); + 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 dee80addd91..c3d8513c2f1 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 searchWindowAware; private List legs; private ItineraryFares fare = ItineraryFares.empty(); - public Itinerary(List legs) { + private Itinerary(List legs, boolean searchWindowAware) { setLegs(legs); + this.searchWindowAware = searchWindowAware; // Set aggregated data ItinerariesCalculateLegTotals totals = new ItinerariesCalculateLegTotals(legs); @@ -87,6 +89,21 @@ public Itinerary(List legs) { 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. */ @@ -162,6 +179,15 @@ public boolean isOnStreetAllTheWay() { return isStreetOnly(); } + /** + * Returns true if this itinerary has only flex and walking legs. + */ + public boolean isDirectFlex() { + 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. */ public boolean hasTransit() { return legs @@ -169,6 +195,14 @@ 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 searchWindowAware; + } + public Leg firstLeg() { return getLegs().get(0); } @@ -215,7 +249,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, searchWindowAware); newItin.setGeneralizedCost(getGeneralizedCost()); return newItin; } 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). 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..3ff85db467a 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; @@ -89,6 +90,7 @@ public class ItineraryListFilterChainBuilder { private boolean removeTransitIfWalkingIsBetter = true; private ItinerarySortKey itineraryPageCut; private boolean transitGroupPriorityUsed = false; + private boolean filterDirectFlexBySearchWindow = true; /** * Sandbox filters which decorate the itineraries with extra information. @@ -470,6 +472,13 @@ public ItineraryListFilterChain build() { ); } + if (earliestDepartureTime != null && filterDirectFlexBySearchWindow) { + addRemoveFilter( + filters, + new FlexSearchWindowFilter(earliestDepartureTime, searchWindow, sortOrder) + ); + } + // Remove itineraries present in the page retrieved before this page/search. if (itineraryPageCut != null) { addRemoveFilter( @@ -533,6 +542,11 @@ public ItineraryListFilterChain build() { return new ItineraryListFilterChain(filters, debugHandler); } + public ItineraryListFilterChainBuilder withFilterDirectFlexBySearchWindow(boolean b) { + this.filterDirectFlexBySearchWindow = 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/filterchain/filters/system/FlexSearchWindowFilter.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilter.java new file mode 100644 index 00000000000..efbb9bf4d08 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilter.java @@ -0,0 +1,65 @@ +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; + +/** + * 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). + * 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 { + + public static final String TAG = "flex-outside-search-window"; + + private final 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 + public String name() { + return TAG; + } + + @Override + public Predicate shouldBeFlaggedForRemoval() { + return it -> { + if (it.isDirectFlex()) { + return switch (sortOrder) { + case STREET_AND_DEPARTURE_TIME -> { + var time = it.startTime().toInstant(); + yield time.isBefore(earliestDepartureTime); + } + case STREET_AND_ARRIVAL_TIME -> { + var time = it.startTime().toInstant(); + yield time.isAfter(latestArrivalTime); + } + }; + } 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 ba3ef4b04f3..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 @@ -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 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 * such itineraries. The same is true for when paging to the previous page for arriveBy=true. *

@@ -35,8 +37,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/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapper.java index 11302098f25..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); + 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 155394376ae..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); + 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()), 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..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,6 +92,7 @@ public static ItineraryListFilterChain createFilterChain( .withPageCursorInputSubscriber(pageCursorInputSubscriber) .withRemoveWalkAllTheWayResults(removeWalkAllTheWayResults) .withRemoveTransitIfWalkingIsBetter(true) + .withFilterDirectFlexBySearchWindow(params.filterDirectFlexBySearchWindow()) .withDebugEnabled(params.debug()); if (!request.preferences().transit().relaxTransitGroupPriority().isNormal()) { 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 09431f0be19..0ed9bdb0558 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,13 +5,13 @@ /** *

- * 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 - * done, there is not street results to use to prune bad transit results with. In other words OTP is + * 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 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, 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, 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: *

    @@ -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 609d6ec5355..7d73d705dfe 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) { 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 32f0ac06ef4..56ba39d8431 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -36,14 +36,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 { @@ -184,6 +176,12 @@ 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. + *

    + * See also {@link org.opentripplanner.routing.algorithm.raptoradapter.router.FilterTransitWhenDirectModeIsEmpty}, + * it uses a direct search to prune transit. */ public void applyPageCursor() { if (pageCursor != null) { 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..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 removeTransitIfWalkingIsBetter; + 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.removeTransitIfWalkingIsBetter = false; + 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.removeTransitIfWalkingIsBetter = builder.removeTransitIfWalkingIsBetter; + this.filterDirectFlexBySearchWindow = builder.filterDirectFlexBySearchWindow; } public static Builder of() { @@ -136,8 +136,8 @@ public CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly() { return removeTransitWithHigherCostThanBestOnStreetOnly; } - public boolean removeTransitIfWalkingIsBetter() { - return removeTransitIfWalkingIsBetter; + public boolean filterDirectFlexBySearchWindow() { + return filterDirectFlexBySearchWindow; } @Override @@ -187,7 +187,7 @@ public String toString() { "removeItinerariesWithSameRoutesAndStops", removeItinerariesWithSameRoutesAndStops ) - .addBoolIfTrue("removeTransitIfWalkingIsBetter", removeTransitIfWalkingIsBetter) + .addBoolIfTrue("filterDirectFlexBySearchWindow", filterDirectFlexBySearchWindow) .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) && + filterDirectFlexBySearchWindow == that.filterDirectFlexBySearchWindow ); } @@ -237,7 +237,7 @@ public int hashCode() { removeItinerariesWithSameRoutesAndStops, transitGeneralizedCostLimit, removeTransitWithHigherCostThanBestOnStreetOnly, - removeTransitIfWalkingIsBetter + filterDirectFlexBySearchWindow ); } @@ -257,7 +257,7 @@ public static class Builder { private boolean removeItinerariesWithSameRoutesAndStops; private TransitGeneralizedCostFilterParams transitGeneralizedCostLimit; private CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly; - private boolean removeTransitIfWalkingIsBetter; + private boolean filterDirectFlexBySearchWindow; public ItineraryFilterPreferences original() { return original; @@ -341,11 +341,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 +360,7 @@ public Builder(ItineraryFilterPreferences original) { this.transitGeneralizedCostLimit = original.transitGeneralizedCostLimit; this.removeTransitWithHigherCostThanBestOnStreetOnly = original.removeTransitWithHigherCostThanBestOnStreetOnly; - this.removeTransitIfWalkingIsBetter = original.removeTransitIfWalkingIsBetter; + this.filterDirectFlexBySearchWindow = original.filterDirectFlexBySearchWindow; } public Builder apply(Consumer body) { @@ -377,5 +372,10 @@ public ItineraryFilterPreferences build() { var value = new ItineraryFilterPreferences(this); return original.equals(value) ? original : value; } + + 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 3a58bfe9bcd..c021de88a8e 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,28 @@ public static void mapItineraryFilterParams( ) .asDouble(dft.minBikeParkingDistance()) ) + .withFilterDirectFlexBySearchWindow( + c + .of("filterDirectFlexBySearchWindow") + .since(V2_7) + .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. + + 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. + """ + ) + .asBoolean(true) + ) .build(); } diff --git a/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java b/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java index 142ced31504..1372af0f405 100644 --- a/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java +++ b/src/test/java/org/opentripplanner/model/plan/ItineraryTest.java @@ -258,6 +258,17 @@ void bothPenalties() { ); } + @Test + 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(); } diff --git a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java index ec04d9f6d96..7b963ae8ee6 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; @@ -199,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()); @@ -398,6 +403,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 +423,12 @@ public Itinerary build(int c1) { } public Itinerary build() { - Itinerary itinerary = new Itinerary(legs); + 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/algorithm/filterchain/ItineraryListFilterChainTest.java b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/ItineraryListFilterChainTest.java index d897c948e6d..2bf4afee7fb 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) + .withFilterDirectFlexBySearchWindow(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) + .withFilterDirectFlexBySearchWindow(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/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..30c8872b859 --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/filterchain/filters/system/FlexSearchWindowFilterTest.java @@ -0,0 +1,74 @@ +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 { + + private static final Instant LATEST_DEPARTURE_TIME = TestItineraryBuilder + .newTime(time("09:20")) + .toInstant(); + + @ParameterizedTest + @ValueSource(strings = { "09:20", "09:21", "13:20" }) + void keepArriveByFlexItinerariesAfterEDT(String startTime) { + var edt = "9:20"; + 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) + .withIsSearchWindowAware(false) + .build(); + + assertThat(subject.flagForRemoval(List.of(itin))).isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = { "00:00", "00:01", "09:19" }) + 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) + .withIsSearchWindowAware(false) + .build(); + + 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)); + } +} 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..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 @@ -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,31 @@ 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) + ) + // results from the street & flex routers are not aware of the search window + .map(b -> b.withIsSearchWindowAware(false).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(); + } } 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..2d5c1b1770b --- /dev/null +++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/GraphPathToItineraryMapperTest.java @@ -0,0 +1,40 @@ +package org.opentripplanner.routing.algorithm.mapping; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +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 { + + 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 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 67253f71438..d10504978a4 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; @@ -250,6 +251,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/api/request/preference/ItineraryFilterPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/ItineraryFilterPreferencesTest.java index f59586b0af9..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 @@ -127,13 +127,20 @@ 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) + .withFilterDirectFlexBySearchWindow(true) + .build(); assertEqualsAndHashCode(subject, other, same); } @Test void testToString() { - assertEquals("ItineraryFilterPreferences{}", ItineraryFilterPreferences.DEFAULT.toString()); + assertEquals( + "ItineraryFilterPreferences{filterDirectFlexBySearchWindow}", + ItineraryFilterPreferences.DEFAULT.toString() + ); assertEquals( "ItineraryFilterPreferences{" + "accessibilityScore, " + @@ -147,7 +154,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, " + + "filterDirectFlexBySearchWindow" + "}", subject.toString() ); diff --git a/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java b/src/test/java/org/opentripplanner/routing/stoptimes/AlternativeLegsTest.java index fde87776909..a4699ece2ed 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 = toString(alternativeLegs); var expected = "B ~ BUS 2 0:20 0:30 ~ C [C₁-1], " + @@ -91,9 +89,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 = toString(alternativeLegs); var expected = "B ~ BUS 3 1:00 1:10 ~ C [C₁-1], " + @@ -126,9 +122,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 = toString(alternativeLegs); assertEquals("X ~ BUS 19 10:30 10:40 ~ Y [C₁-1], X ~ BUS 19 10:00 10:10 ~ Y [C₁-1]", legs); } @@ -155,11 +150,20 @@ 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 = toString(alternativeLegs); var expected = String.join(", ", List.of("X ~ BUS 19 10:30 11:00 ~ B [C₁-1]")); assertEquals(expected, legs); } + + private static String toString(List alternativeLegs) { + return Itinerary.toStr( + alternativeLegs + .stream() + .map(Leg.class::cast) + .map(List::of) + .map(Itinerary::createScheduledTransitItinerary) + .toList() + ); + } }