Skip to content

Commit

Permalink
Add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
sdjacobs committed Jan 15, 2019
1 parent 84b098b commit 0ebf3d0
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 22 deletions.
70 changes: 70 additions & 0 deletions docs/Flex.md
@@ -0,0 +1,70 @@
# GTFS-Flex routing

Many agencies run flexible services to complement their fixed-route service. "Flexible" service does
not follow a strict timetable or route. It may include any of the following features: boardings
or alightings outside its scheduled timetable and route; booking and scheduling in advance; or
transit parameters which depend on customer requests ("demand-responsive transit" or DRT). These
services are typically used in rural areas or for mobility-impaired riders.

A GTFS extension called [GTFS-Flex](https://github.com/MobilityData/gtfs-flex/blob/master/spec/reference.md) defines
how to model some kinds of flexible transit. A subset of GTFS-Flex has been implemented in
OpenTripPlanner as part of US DOT's [Mobility-on-Demand Sandbox Grant](https://www.transit.dot.gov/research-innovation/fiscal-year-2016-mobility-demand-mod-sandbox-program-projects).

In particular, OTP now has support for these modes of GTFS-Flex:

- "flag stops", in which a passenger can flag down the a vehicle along its route to board, or
alight in between stops
- "deviated-route service", in which a vehicle can deviate from its route within an area or radius to
do a dropoff or pickup
- "call-and-ride", which is an entirely deviated, point-to-point segment.

These modes can co-exist with fixed-route transit, and with each other. For example, some agencies
have fixed-route services that start in urban areas, where passengers must board at designated
stops, but end in rural areas where passengers can board and alight wherever they please. A
fixed-route service may terminate in an defined area where it can drop off passengers anywhere --
or have such an area at the beginning or middle of its route. A vehicle may be able to deviate a
certain radius outside its scheduled route to pick up or drop off passengers. If both a pickup and
dropoff occur in between scheduled timepoints, from the passenger's perspective, the service may
look like a call-and-ride trip. Other call-and-ride services may operate more like taxis, in which
all rides are independently scheduled.

## Configuration

In order to use flexible routing, an OTP graph must be built with a GTFS-Flex dataset and
OpenStreetMap data. The GTFS data must include `shapes.txt`.

In addition, the parameter `useFlexService: true` must be added to `router-config.json`.

A number of routing parameters can be used to control aspects of flexible service. These parameters
typically change the relative cost of using various flexible services relative to fixed-route
transit. All flex-related parameters begin with the prefix "flex" and can be found in the Javadocs
for `RoutingRequest.java`.

The following example `router-config.json` enables flexible routing and sets some parameters:

{
"useFlexService": true,
"routingDefaults": {
"flexCallAndRideReluctance": 3,
"flexMaxCallAndRideSeconds": 7200,
"flexFlagStopExtraPenalty": 180
}
}

## Implementation

The general approach of the GTFS-Flex implementation is as follows: prior to the main graph search,
special searches are run around the origin and destination to discover possible flexible options.
One search is with the WALK mode, to find flag stops, and the other is in the CAR mode, to find
deviated-route and call-and-ride options. These searches result in the creation of temporary,
request-specific vertices and edges. Then, the graph search proceeds as normal. Temporary graph
structures are disposed at the end of the request's lifecycle.

For flag stops and deviated-route service, timepoints in between scheduled locations are determined
via linear interpolation. For example, say a particular trip departs stop A at 9:00am and arrives
at stop B at 9:30am. A passenger would be able to board 20% of the way in between stop A and stop B
at 9:06am, since 20% of 30 minutes is 6 minutes.

For deviated-route service and call-and-ride service, the most pessimistic assumptions of vehicle
travel time are used -- e.g. vehicle travel time is calculated via the `drt_max_travel_time`
formula in the GTFS-Flex (see the spec [here](https://github.com/MobilityData/gtfs-flex/blob/master/spec/reference.md#defining-service-parameters)).
1 change: 1 addition & 0 deletions mkdocs.yml
Expand Up @@ -32,6 +32,7 @@ pages:
- Scripting: 'Scripting.md' - Scripting: 'Scripting.md'
- Security: 'Security.md' - Security: 'Security.md'
- Troubleshooting: 'Troubleshooting-Routing.md' - Troubleshooting: 'Troubleshooting-Routing.md'
- GTFS-Flex Routing: 'Flex.md'
- Development: - Development:
- "Developers' Guide": 'Developers-Guide.md' - "Developers' Guide": 'Developers-Guide.md'
- Architecture: 'Architecture.md' - Architecture: 'Architecture.md'
Expand Down
@@ -1,9 +1,10 @@
package org.opentripplanner.api.model; package org.opentripplanner.api.model;


/** /**
* Represent type of board/alight at a stop. The majority of boarding and alightings will be of * Distinguish between special ways a passenger may board or alight at a stop. The majority of
* type "default" -- a regular boarding or alighting at a regular transit stop. Currently, the * boardings and alightings will be of type "default" -- a regular boarding or alighting at a
* only non-default types are related to GTFS-Flex, but this pattern can be extended as necessary. * regular transit stop. Currently, the only non-default types are related to GTFS-Flex, but this
* pattern can be extended as necessary.
*/ */
public enum BoardAlightType { public enum BoardAlightType {


Expand All @@ -13,7 +14,7 @@ public enum BoardAlightType {
DEFAULT, DEFAULT,


/** /**
* A flag-stop boarding or alighting, e.g. flagging the bus down or a passegner asking the bus * A flag-stop boarding or alighting, e.g. flagging the bus down or a passenger asking the bus
* driver for a drop-off between stops. This is specific to GTFS-Flex. * driver for a drop-off between stops. This is specific to GTFS-Flex.
*/ */
FLAG_STOP, FLAG_STOP,
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/org/opentripplanner/api/model/Place.java
Expand Up @@ -93,8 +93,10 @@ public class Place {
public String bikeShareId; public String bikeShareId;


/** /**
* Type of board or alight. This will be "default" in most cases. Currently the only non- * This is an optional field which can be used to distinguish among ways a passenger's
* default values are for GTFS-Flex. * boarding or alighting at a stop can differ among services operated by a transit agency.
* This will be "default" in most cases. Currently the only non-default values are for
* GTFS-Flex board or alight types.
*/ */
public BoardAlightType boardAlightType; public BoardAlightType boardAlightType;


Expand Down
18 changes: 13 additions & 5 deletions src/main/java/org/opentripplanner/routing/core/RoutingRequest.java
Expand Up @@ -497,9 +497,10 @@ public class RoutingRequest implements Cloneable, Serializable {
* *
* In GTFS-Flex, deviated-route and call-and-ride service can define a trip-level parameter * In GTFS-Flex, deviated-route and call-and-ride service can define a trip-level parameter
* `drt_advance_book_min`, which determines how far in advance the flexible segment must be * `drt_advance_book_min`, which determines how far in advance the flexible segment must be
* scheduled. OTP will ensure that recommended boardings on those services respect that * scheduled. If `flexIgnoreDrtAdvanceBookMin = false`, OTP will only provide itineraries which
* constraint, unless this parameter is set to true, which may be desired for some * are feasible based on that constraint. For example, if the current time is 1:00pm and a
* applications. * particular service must be scheduled one hour in advance, the earliest time the service
* is usable is 2:00pm.
*/ */
public boolean flexIgnoreDrtAdvanceBookMin = false; public boolean flexIgnoreDrtAdvanceBookMin = false;


Expand Down Expand Up @@ -600,8 +601,15 @@ public class RoutingRequest implements Cloneable, Serializable {
private StreetEdge splitEdge = null; private StreetEdge splitEdge = null;


/** /**
* Keep track of time of request. This is currently only used by the GTFS-Flex implementation. * Keep track of epoch time the request was created by OTP. This is currently only used by the
* See the javadoc for flexIgnoreDrtAdvanceBookMin for more details. * GTFS-Flex implementation.
*
* In GTFS-Flex, deviated-route and call-and-ride service can define a trip-level parameter
* `drt_advance_book_min`, which determines how far in advance the flexible segment must be
* scheduled. If `flexIgnoreDrtAdvanceBookMin = false`, OTP will only provide itineraries which
* are feasible based on that constraint. For example, if the current time is 1:00pm and a
* particular service must be scheduled one hour in advance, the earliest time the service
* is usable is 2:00pm.
*/ */
public long clockTimeSec; public long clockTimeSec;


Expand Down
87 changes: 76 additions & 11 deletions src/main/java/org/opentripplanner/routing/edgetype/Timetable.java
Expand Up @@ -121,10 +121,43 @@ public boolean temporallyViable(ServiceDay sd, long searchTime, int bestWait, bo
/** /**
* Get the next (previous) trip that departs (arrives) from the specified stop at or after * Get the next (previous) trip that departs (arrives) from the specified stop at or after
* (before) the specified time. * (before) the specified time.
*
* For GTFS-Flex service, this method will take into account the fact that the passenger
* may be boarding or alighting midway between stops (flag stops), and that the vehicle may
* be deviating off-route to pick up or drop off the passenger (deviated-route service.) In
* both cases, unscheduled timepoints on the route in between scheduled stops are calculated
* via linear interpolation.
*
* The parameters `flexOffsetScale`, `preBoardDirectTime`, and `postAlightDirectTime` are
* specific to GTFS-Flex service. If arrivals/departures are being evaluated for default
* fixed-route service, these parameters will have the value 0 (see
* {@link #getNextTrip(State, ServiceDay, int, boolean)}).
*
* @param s0 State to evaluate the method at; the method uses the state's time.
* @param serviceDay Only consider trips if their service_id is active on this day
* @param stopIndex Index of stop in the trip to evaluate departure or arrival at
* @param boarding If true, find next trip which departs specified stop; if false, find
* previous trip which arrives at specified stop
* @param flexOffsetScale For GTFS-Flex routing, a control parameter to determine the amount of
* seconds to offset the scheduled timepoint due to a board/alight in
* the middle of a
* {@link org.opentripplanner.routing.edgetype.flex.FlexPatternHop}.
* Use the percentage in [0, 1] ([-1, 0]) from the beginning (end) of
* the hop at which the board (alight) is taking place. Note that the
* value is expected to be negative when evaluating alight points. Use
* 0 if this is not a flag stop or deviated-route board (alight) point.
* @param flexPreBoardDirectTime For GTFS-Flex routing, the amount of time the vehicle travels
* before rejoining the route, or 0 if this is not a deviated-
* route board (alight) point.
* @param flexPostAlightDirectTime For GTFS-Flex routing, the amount of time the vehicle
* travels after leaving the route, or 0 if this is not a
* deviated-route board (alight) point.
*
* @return the TripTimes object representing the (possibly updated) best trip, or null if no * @return the TripTimes object representing the (possibly updated) best trip, or null if no
* trip matches both the time and other criteria. * trip matches both the time and other criteria.
*/ */
public TripTimes getNextTrip(State s0, ServiceDay serviceDay, int stopIndex, boolean boarding, double flexOffsetScale, int preBoardDirectTime, int postAlightDirectTime) { public TripTimes getNextTrip(State s0, ServiceDay serviceDay, int stopIndex, boolean boarding, double flexOffsetScale,
int flexPreBoardDirectTime, int flexPostAlightDirectTime) {
/* Search at the state's time, but relative to midnight on the given service day. */ /* Search at the state's time, but relative to midnight on the given service day. */
int time = serviceDay.secondsSinceMidnight(s0.getTimeSeconds()); int time = serviceDay.secondsSinceMidnight(s0.getTimeSeconds());
// NOTE the time is sometimes negative here. That is fine, we search for the first trip of the day. // NOTE the time is sometimes negative here. That is fine, we search for the first trip of the day.
Expand Down Expand Up @@ -153,12 +186,22 @@ public TripTimes getNextTrip(State s0, ServiceDay serviceDay, int stopIndex, boo
int adjustedTime = adjustTimeForTransfer(s0, currentStop, tt.trip, boarding, serviceDay, time); int adjustedTime = adjustTimeForTransfer(s0, currentStop, tt.trip, boarding, serviceDay, time);
if (adjustedTime == -1) continue; if (adjustedTime == -1) continue;
if (boarding) { if (boarding) {
int adjustment = 0; // For GTFS-Flex, if this is a flag-stop or deviated-route board/alight, we need to
if (stopIndex + 1 < tt.getNumStops() && flexOffsetScale != 0.0) { // add to the scheduled timepoint the amount of time the vehicle travels along the
adjustment = (int) Math.round(flexOffsetScale*tt.getRunningTime(stopIndex)); // hop before the board/alight, and subtract the amount of time the vehicle travels
// off-route before rejoining the route. Both these values are 0 for regular fixed-
// route board/alights.
int flexTimeAdjustment = 0;
if (flexOffsetScale != 0 || flexPreBoardDirectTime != 0) {
int timeIntoHop = 0;
if (stopIndex + 1 < tt.getNumStops() && flexOffsetScale != 0.0) {
timeIntoHop = (int) Math.round(flexOffsetScale * tt.getRunningTime(stopIndex));
}
int vehicleTime = (flexPreBoardDirectTime == 0) ? 0 : tt.getDemandResponseMaxTime(flexPreBoardDirectTime);
flexTimeAdjustment = timeIntoHop - vehicleTime;
} }
int vehicleTime = (preBoardDirectTime == 0) ? 0 : tt.getDemandResponseMaxTime(preBoardDirectTime);
int depTime = tt.getDepartureTime(stopIndex) + adjustment - vehicleTime; int depTime = tt.getDepartureTime(stopIndex) + flexTimeAdjustment;
if (depTime < 0) continue; // negative values were previously used for canceled trips/passed stops/skipped stops, but if (depTime < 0) continue; // negative values were previously used for canceled trips/passed stops/skipped stops, but
// now its not sure if this check should be still in place because there is a boolean field // now its not sure if this check should be still in place because there is a boolean field
// for canceled trips // for canceled trips
Expand All @@ -167,12 +210,21 @@ public TripTimes getNextTrip(State s0, ServiceDay serviceDay, int stopIndex, boo
bestTime = depTime; bestTime = depTime;
} }
} else { } else {
int adjustment = 0; // For GTFS-Flex, subtract from the scheduled timepoint the amount of time left in
if (stopIndex - 1 >= 0 && flexOffsetScale != 0.0) { // the hop after the vehicle drops off the passenger (note flexOffsetScale < 0
adjustment = (int) Math.round(flexOffsetScale*tt.getRunningTime(stopIndex - 1)); // in this case), and add the amount of time the vehicle travels off-route before
// the passenger alights.
int flexTimeAdjustment = 0;
if (flexOffsetScale != 0 || flexPostAlightDirectTime != 0) {
int timeIntoHop = 0;
if (stopIndex - 1 >= 0 && flexOffsetScale != 0.0) {
timeIntoHop = (int) Math.round(flexOffsetScale * tt.getRunningTime(stopIndex - 1));
}
int vehicleTime = (flexPostAlightDirectTime == 0) ? 0 : tt.getDemandResponseMaxTime(flexPostAlightDirectTime);
flexTimeAdjustment = timeIntoHop + vehicleTime;
} }
int vehicleTime = (postAlightDirectTime == 0) ? 0 : tt.getDemandResponseMaxTime(postAlightDirectTime);
int arvTime = tt.getArrivalTime(stopIndex) + adjustment + vehicleTime; int arvTime = tt.getArrivalTime(stopIndex) + flexTimeAdjustment;
if (arvTime < 0) continue; if (arvTime < 0) continue;
if (arvTime <= adjustedTime && arvTime > bestTime) { if (arvTime <= adjustedTime && arvTime > bestTime) {
bestTrip = tt; bestTrip = tt;
Expand Down Expand Up @@ -215,6 +267,19 @@ public TripTimes getNextTrip(State s0, ServiceDay serviceDay, int stopIndex, boo
return bestTrip; return bestTrip;
} }


/**
* Get the next (previous) trip that departs (arrives) from the specified stop at or after
* (before) the specified time.
*
* @param s0 State to evaluate the method at; the method uses the state's time.
* @param serviceDay Only consider trips if their service_id is active on this day
* @param stopIndex index of stop in the trip to evaluate departure or arrival at
* @param boarding if true, find next trip which departs specified stop; if false, find
* previous trip which arrives at specified stop
*
* @return the TripTimes object representing the (possibly updated) best trip, or null if no
* trip matches both the time and other criteria.
*/
public TripTimes getNextTrip(State s0, ServiceDay serviceDay, int stopIndex, boolean boarding) { public TripTimes getNextTrip(State s0, ServiceDay serviceDay, int stopIndex, boolean boarding) {
return getNextTrip(s0, serviceDay, stopIndex, boarding, 0, 0, 0); return getNextTrip(s0, serviceDay, stopIndex, boarding, 0, 0, 0);
} }
Expand Down

0 comments on commit 0ebf3d0

Please sign in to comment.