
org.opentripplanner.routing.algorithm.transferoptimization.package.md Maven / Gradle / Ivy
# Transfers / Interchanges
OTP2 handles transfers differently than OTP1. In OTP1, transfers were optimized by applying a cost
for each transfer edge during the search. In OTP2, finding the best transfers is done partially
during routing and then improved in a post-processing step. It is easier to understand how you
should tune OTP2 if you understand this process. When optimizing transfers we first find the best
paths
(sequences of trips), then identify the best stops to transfer between subsequent trips in each of
those paths (the post-processing).
## Supported Input Data
- OTP supports [GTFS Transfers.txt](https://gtfs.org/reference/static#transferstxt) and the Google
extention
with [Trip to Trip transfers](https://developers.google.com/transit/gtfs/reference/gtfs-extensions#TripToTripTransfers)
.
- OTP
supports [NeTEx Interchanges](https://enturas.atlassian.net/wiki/spaces/PUBLIC/pages/728760393/timetable#Interchange.1)
as specified in the Nordic profile.
## Goals
1. Prefer paths with fewer transfers over many transfers.
2. Prefer some locations (stop pairs) over others.
3. Support `StaySeated` and `Guaranteed` transfers.
4. Choose the transfer with the highest priority: `Preferred` over `Recommended` over `Allowed`.
5. Support for transfer
specificity, [Specificity of a transfer](https://developers.google.com/transit/gtfs/reference/gtfs-extensions#specificity-of-a-transfer)
.
6. Prevent transfers between stops and/or trips/routes. GTFS Transfers `transfer_type = 3` and Netex
Transfer `priority = -1(not allowed)`.
7. Find the best place to transfer between two transit trips in a path. Normally we want to maximize
the "wait-time"/"extra-time" to allow the traveler to have as much time to do the transfer as
possible.
1. Maximize the wait-time
2. For a journey with more than one transfer we want to distribute the wait-time, if possible.
For a path with 2 transfers it is better to have 4 minutes extra for each transfer than
having 30 seconds for one and 7m30s for the other.
3. For a transfer between two trips we want to find the right balance, between
extra-transfer-time and the path generalized-cost(like the cost of walking and riding a bus).
## Not Supported (jet)
- Using GTFS transfers.txt it is possible to set the `min_transfer_time` with `transfer_type = 2`.
See issue [#3369](https://github.com/opentripplanner/OpenTripPlanner/issues/3369)
- The NeTEx Interchange MaximumWaitTime is ignored.
- Transfer not-allowed at location. See
issue [#3505](https://github.com/opentripplanner/OpenTripPlanner/issues/3505).
- Support for Trip matching when only Route is specified in transfers.txt. See
issue [#3429](https://github.com/opentripplanner/OpenTripPlanner/issues/3429)
## Implementation
OTP finds the best transfers in 2 steps:
1. As part of the routing. The routing engine (Raptor) applies generalized-cost and
number-of-transfers as criteria during the routing. In addition, Raptor supports overriding
regular transfers with constrained transfers.
1. Goal 1 is achieved by having `number-of-transfers` as a Raptor criterion.
2. Goal 2 is achieved by giving some stops a lower _visiting-cost_ according to the
`StopTransferPriority`.
3. Goal 3 and 6 is achieved by allowing constrained transfers to override regular transfers. An
optional `RaptorTransferConstraintsProvider` is injected into Raptor which Raptor calls to
get guaranteed transfers. This service is also responsible for rejecting a transfer(TODO).
2. Optimize transfers: Goal 4, 5, and 7 are achieved by post-processing paths. Paths from the
routing search are revised, optimizing the stop at which transfers happen between each pair of
transit rides (trips). This step does NOT compare different paths returned by the router
(Raptor), but instead finds all possible transfer locations for a given path, and the different
alternatives to transfer between each pair of trips within the path. This is an outline of the
process:
1. For each path find all possible permutations of transfers.
2. Filter paths based on priority including `StaySeated=100`, `Guaranteed=10`, `Preferred=2`,
`Recommended=1`, `Allowed=0` and `NotAllowed=-1000`. Each path is given a combined score, and
the set of the paths with the lowest score is returned (more than one path might have the
same score).
3. Another filter which breaks ties based on an "optimize-transfer-cost" function.
4. Because the number of permutations grow exponentially with the number of transfers, each
filter above is applied for each sub-path starting from the end of the path. This keep the
number of options down, close to O(N), where N is the number of transfers.
## Design - Optimize transfers
The `OptimizeTranferService` is the entry point and delegates to the "domain" services in
the `services` package. The result of the optimization process is an `OptimizedPath` found in the
`api` package. The optimization process first uses the `TransferGenerator` to find all possible
transfer points between each trip in a path. Then the `OptimizePathService` constructs all possible
paths starting at the "tail" with the egress-leg, and adds legs until the path is constructed. To
avoid exploring too many paths, the optimization filters the result of each step, reducing the set
of optimal tails. The `MinCostFilterChain` is used to filter each equivalent set of paths and
the `TransitPathLegSelector` finds the best tail for each transfer used to generate a new path-tail.
Here is an outline of the calls:

### Packages
Package `org.opentripplanner.routing.algorithm.transferoptimization` has the following subpackages:
- `api` classes used outside the transfer-optimization.
- `configure` creates and wires up the the module.
- `model` simple internal model classes used by the services.
- `services` internal transfer-optimization domain services, which collaborate to do the job.
### Important classes
The `TransferOptimizationServiceConfigurator` is responsible for creating and wiring up the module.
The configuration `TransferOptimizationParameters` is used to configure classes and inject the right
versions of each class. For example, the min-cost-filter-chain is created with the appropriate
filters to perform the filtering according to the configuration.
The `OptimizeTransferService` is the entry point of the service and responsible for delegating the
sub-tasks to the internal domain services. It is also responsible for the life-cycle, mainly
injecting 'setMinSafeTransferTime' before the calculation starts.
The `OptimizePathService` finds the best optimized path using the other domain services.
The `TransferGenerator` is responsible for finding all possible transfer options and decorating each
transfer with information that the other services will use later to do their job. The additional
information added to each transfer is:
- Constrained transfer information including priority, guaranteed, stay-seated and so on
- The various cost described in `TransferOptimized`:
- `transfer-priority-cost`
- `wait-time-optimized-cost` or `generalized-cost`
- `break-tie-cost`
The `wait-time-optimized-cost` is described in [detail below](#the-optimize-transfer-cost-function).
The `TransitPathLegSelector` uses the filter-chain to prune the results already found before
combining them with new transfer-points and a new transit-path-leg.
The domain `OptimizedPathTail` is used to store the intermediate results. It decorates the `PathLeg`
from Raptor with `TransferOptimized` costs.
### The Optimize-transfer-cost Function
The optimize-transfer-cost function is used select between alternative transfers for a
"path" with the same set of on-board trips. This address goal 7 above:
1. Maximize the wait-time
2. Distribute the wait-time between transfers
3. Balance the wait-time cost and the generalized-cost
To compare two paths we need a cost function that account for the differences in the existing cost,
except the wait-time cost, and add a new inverse cost for the extra-time:
```
F(t) = path.generalized-cost - total-wait-time * waitReluctance + ∑ f(t)
```
The `∑ f(t)` is the sum over the new wait-time-cost-function `f(t)` where `t` is the wait-time for
each transfer in the path.
We have created a function with two parts:
- a linear decreasing component with a factor `a`. This should be adjusted to balance with the
components of the path `generalized-cost`.
- an inverse logarithmic function which have a high cost for short wait-times. This function is
relative to the calculated `min-safe-transfer-time`.
The `f(t)` is constructed so only 2 parameters are needed to tune it. Let:
- `t0` be the `min-safe-transfer-time`. This is defined as 6.67% of the total transit-time including
board- and alight-slack across all paths found in the search. We want the `f(t)` to be relative to
the travel-time, and fairly stable over time, independent of day of week. So, by excluding wait
time we get an ok estimate for the journey travel duration.
Then the parameters used and available for configuration is:
- `a` the `inverse-wait-reluctance`. This factor is an inverse `waitReluctance`. For example, if it
is set to 1, a wait-time of 10 seconds give a _reduction_ in cost of 10 cost points - the same as
riding a bus for 10 seconds.
- `n` the `min-safe-wait-time-factor`. This defines the maximum cost for the logarithmic function
relative to the `min-safe-transfer-time` when wait time goes towards zero(0).
The `f(t)` is created so:
- `f(0) = (n+1) * t0`
- `f(t0) = t0 - a * t0`, so if `a=1` then `f(t0)=0`.
Note that since we are comparing paths where the only difference is the transfers, the effect of the
function `f(t)` lies in the delta between the cost of transfer A for alternative 1 and the cost of
transfer B for alternative 2.
The function is:
```
(n+1) * t0
f(t) = ----------------------- - a * t
1 + n * ln(1 + C * t)
```
where the constant `C = (e - 1.0)/t0`.
Here is a plot with three examples with different values for `n` and `a` which illustrate how the
function look like when the linear, and the logarithmic part is present or not. The blue line have
only the logarithmic part(n=4, a=0), the purple line is a combination (n=2, a=0.5), and the yellow
line have the linear path only(n=0, a=1). The minimum-safe-transfer-time is set to 600s or 10
minutes in this case. Notice that the blue line gots from `f(0)=(n+1) * t0` to `f(t0)=t0` with
delta `n * t0`. The purple line goes from `f(0)=t0` to `f(t0)=0` with delta `-t0`.

© 2015 - 2025 Weber Informatics LLC | Privacy Policy