jamespfennell / transiter

Web service for transit data
https://demo.transiter.dev
MIT License
62 stars 7 forks source link

Support unidirectional service maps #98

Open jamespfennell opened 1 year ago

jamespfennell commented 1 year ago

Service maps are currently built using all trips in a route. If the direction ID for a trip is false, we reverse the trip first. This ensures that all trips "point" in the same direction. We then construct the graph using all of the trips, and calculate the service maps.

This approach makes sense for the NYC subway. Northbound A trains call at the same stations as southbound A trains, just in the opposite order. However for other transit systems, including the NYC buses, this approach does not really make sense, as discussed in this PR. The B57 bus, for example, goes southbound on Court St in Cobble Hill, but northbound on Smith St. Constructing a service map using trips in both directions results in a map that's not very useful - it's basically a list of the northbound stops, followed by a list of the southbound stops.

I think I nice solution would be to support constructing "unidirectional" service maps - that is, service maps that only look at one direction. For the B57 bus this would give us a northbound service map and a southbound service map. In fact, the MTA's bus time app follows exactly this pattern and presents two "maps" for each bus route.

To support this we could add a new field to the service maps config proto. The value of the field would be something like the following new enum:

// Direction specifies the direction of trips that are used to build the service map.
enum Direction {
  // Use trips whose direction ID is true or false.
  // Any trip whose direction ID is false is reversed before being inputted into the map.
  BOTH_UNIFIED = 0;
  // Only use trips whose direction ID is true.
  DIRECTION_ID_TRUE = 1;
  // Only use trips whose direction ID is false.
  DIRECTION_ID_FALSE = 2;
  // Create two service maps using this configuration.
  // One service map is for trips whose direction ID is true, and the other for trips whose direction ID is false.
  // The IDs of these service maps will have postfix "_direction_id_true" and "_direction_id_false" respectively.
  // To further customize the postfix create two configs with the `DIRECTION_ID_TRUE` and `DIRECTION_ID_FALSE`
  // settings.
  BOTH_SPLIT = 3;
}

The service maps code would then do the right thing.

jamespfennell commented 1 year ago

I came up with a simpler API for this.

Right now service maps are keyed on (config ID, route ID). We can instead key them on (config ID, route ID, nullable direction ID). If the direction ID is null, the service map is a bi-directional map that was built using all trips (reversing trips whose direction ID is null). I.e., it's the same as the current service maps. If the direction ID is true, the service map is a uni-directional map that was built using trips whose direction ID is true. And similarly with false. The advantage of this is that a single service map config can be used to generate up to 3 service maps for each route.

In the service map configuration, we will have an enum that determines the directionality of the service maps to build with the config.

// Directionality specifies the direction of trips that are used to build the service maps with this config.
enum Directionality {
  // Analyze the trip data and determine whether to built a bi-directional map or two uni-directional maps.
  INFER = 0;
  // For each route, built a single service map with all trips for the route.
  // Trips whose direction ID is false are reversed before being inputted into the service map.
  BI_DIRECTIONAL = 1;
  // For each route, built two service maps: one for trips whose direction ID is true,
  // and one for trips whose direction ID is false.
  UNI_DIRECTIONAL = 2;
  // Create both a bi-directional and two uni-directional service maps for each route.
  BOTH = 3;
}