11. Publishing GTFS-realtime Feeds

So far this book has been focused on how to consume GTFS-realtime feeds; in this chapter you will be shown how to create and publish your own GTFS-realtime feeds.

While this chapter is primarily intended for transit agencies (or third-party companies providing services to public transit companies), this information can be useful in other situations also.

Even if you do not represent a transit agency or have access to the GPS units of an entire bus fleet, there may still be situations where you want to produce a GTFS feed. For example, if you have a trip planning server that can only handle GTFS and GTFS-realtime data, you might build your own GTFS-realtime feeds in the following situations:

  • A transit company offers service alerts only via Twitter or an RSS feed.
  • You can access vehicle positions or estimated arrivals from a feed in a format such as SIRI, NextBus or BusTime.
  • You have interpolated your own vehicle positions based on GTFS-realtime trip updates.
  • You have interpolated your own trip updates based on vehicle positions.

Building Protocol Buffer Elements

When you generate source files using the protoc command, there is a builder class created for each element type. To create an element to include in a protocol buffer, you use its builder to construct the element.

For example, a service alert entity uses the Alert class. To construct your own service alert, you would use the Alert.Builder class. The Alert class contains a static method called newBuilder() to create an instance of Alert.Builder.

Alert.Builder alert = Alert.newBuilder();

You can now set the various elements that describe a service alert.

alert.setCause(Cause.ACCIDENT);

alert.setEffect(Effect.DETOUR);

Most elements will be more complex than this; you will need to build them in a similar manner before adding them to the alert. For example, the header text for a service alert uses the TranslatedString element type, which contains one or more translations of a single string.

Translation.Builder translation = Translation.newBuilder();

translation.setText("Car accident");

TranslatedString.Builder translatedString = TranslatedString.newBuilder();

translatedString.addTranslation(translation);

alert.setHeaderText(translatedString);

In actual fact, you can chain together these calls, since the builder methods return the builder. The first two lines of the above code can be shortened as follows:

Translation.Builder translation = Translation.newBuilder().setText("Car accident");

For repeating elements (such as the informed_entity field), use the addElementName() method. In the case of informed_entity, this would be addInformedEntity(). The following code adds an informed entity to the alert for a route with an ID of 102:

EntitySelector.Builder entity = EntitySelector.newBuilder().setRouteId("102");

alert.addInformedEntity(entity);

Creating a Complete Protocol Buffer

The previous section showed the basics of creating a service alert message, but a protocol buffer feed has more to it than just a single entity. It can have multiple entities, and you must also include the GTFS-realtime header. The header can be created as follows:

FeedHeader.Builder header = FeedHeader.newBuilder();

header.setGtfsRealtimeVersion("1.0");

A single service alert (or a trip update, or a vehicle position) is contained within a FeedEntity object. Each FeedEntity in a feed must have a unique ID. The following code creates the FeedEntity using the alert object created in the previous section.

FeedEntity.Builder entity = FeedEntity.newBuilder();

entity.setId("SOME UNIQUE ID");

entity.setAlert(alert);

Once you have the header and an entity you can create the feed as follows:

FeedMessage.Builder message = FeedMessage.newBuilder();

message.setHeader(header);

message.addEntity(entity);

Note: A feed with no entities is also valid; in the middle of the night there may be no vehicle positions or trip updates, and there may frequently be no service alerts.

Once you have created this object, you can turn it into a FeedMessage by calling build().

FeedMessage feed = message.build();

This will give you a FeedMessage object just like when you parse a third-party feed using FeedMessage.parseFrom().

Full Source Code

Piecing together all of the code covered so far in this chapter, you could create a service alert feed (using a fictional detour) using the following code.

This example makes use of a helper method to build translated strings, since it needs to be done a number of times. If you want to create the alert in multiple languages, you would need to change this method accordingly.

public class SampleServicesAlertsFeedCreator {
  // Helper method to simplify creation of translated strings
  
  private TranslatedString translatedString(String str) {
    Translation.Builder translation = Translation.newBuilder().setText(str);
    
    return TranslatedString.newBuilder().addTranslation(translation).build();  
  }
  
  public FeedMessage create() {
    Alert.Builder alert = Alert.newBuilder();
    
    alert.setCause(Cause.ACCIDENT);
    alert.setEffect(Effect.DETOUR);
    alert.setUrl(translatedString("http://www.example.com"));
    alert.setHeaderText(translatedString("Car accident on 14th Street"));
    
    alert.setDescriptionText(translatedString(
      "Please be aware that 14th Street is closed due to a car accident"
    ));
    
    // Loop over several route IDs to mark them as impacted
    
    String impactedRouteIds[] = { "102", "103" };
    
    for (int i = 0; i < impactedRouteIds.length; i++) {
      EntitySelector.Builder entity = EntitySelector.newBuilder();
    
      entity.setRouteId(impactedRouteIds[i]);
    
      alert.addInformedEntity(entity);
    }
    
    // Create the alert container entity
    
    FeedEntity.Builder entity = FeedEntity.newBuilder();
    
    entity.setId("1");
    entity.setAlert(alert);
    
    // Build the feed header
    
    FeedHeader.Builder header = FeedHeader.newBuilder();
    
    header.setGtfsRealtimeVersion("1.0");
    
    // Build the feed using the header and entity
    
    FeedMessage.Builder message = FeedMessage.newBuilder();
    
    message.setHeader(header);
    message.addEntity(entity);
    
    // Return the built FeedMessage
    return message.build();
  }
}

Modifying an Existing Protocol Buffer

In some circumstances you might want to modify an existing protocol buffer. For example, consider a case where you have access to a service alerts feed, but also want to add additional service alerts that you parsed from Twitter. The following diagram demonstrates this:

Modified Feed

In this case, you can turn a FeedMessage object into a FeedMessage.Builder object by calling to the `toBuilder()`` method. You can then add additional alerts as required and create a new feed.

// Parse some third-party feed

URL url = new URL("http://example.com/alerts.pb");

InputStream is = url.openStream();

FeedMessage message = GtfsRealtime.FeedMessage.parseFrom(is);

// Convert existing feed into a builder

FeedMessage.Builder builder = message.toBuilder();

Alert.Builder alert = Alert.newBuilder();

// Add the details of the alert here

// Create the alert entity

FeedEntity.Builder entity = FeedEntity.newBuilder();

entity.setId("SOME ID");
entity.setAlert(alert);

// Add the new entity to the builder
builder.addEntity(entity);

// Build the update the FeedMessage
message = builder.build();

Saving a Protocol Buffer File

Once you have created a protocol buffer, the next step is to output it so other systems that read GTFS-realtime feeds can consume it (such as for others who publish real-time data in their apps or web sites).

Typically, you would generate a new version of the feed every X seconds, then save (or upload) it each time to your web server (see the next section for discussion on frequency of updates).

The raw protocol buffer bytes can be output using the writeTo() method on the FeedMessage object. This method accepts an OutputStream object as its only argument.

For example, to output the service alerts feed created in this chapter to a file, you can use the FileOutputStream class.

Note: While there are no specific rules for naming a protocol buffer, often the .pb extension is used.

SampleServicesAlertsFeedCreator creator = new SampleServicesAlertsFeedCreator();

FeedMessage message = creator.create();

File file = new File("/path/to/output/alerts.pb");

OutputStream outputStream = new FileOutputStream(file);

message.writeTo(outputStream);

Serving a Protocol Buffer File

The recommended content type header value to use when serving a Protocol Buffer file is application/octet-stream. In Apache HTTP Server, you can set the following configuration parameter to serve .pb files with this content type:

AddType application/octet-stream .pb

If you are using nginx for your web server, you can add the following entry to the nginx mime.types file:

types {
  ...

  application/octet-stream pb;
}

Frequency of Updates

When publishing your own feed, the frequency in which you update the feed on your web server depends on how frequently the source data is updated.

It is important to take into account the capabilities of your servers when providing a GTFS-realtime feed, as the more frequently the data is updated, the more resources that are required. Each of the GTFS-realtime message types has slightly different needs:

  • Vehicle Positions. These will need updating very frequently, as presumably the vehicles on your transit network are always moving. A vehicle position feed could update as frequently as every 10-15 seconds.
  • Trip Updates. These will need updating very frequently, although perhaps not as frequently as vehicle positions. Estimates would constantly be refined by new vehicle positions, but a single movement (or lack of movement) for a vehicle is not likely to make a huge difference to estimates. A trip updates feed could update every 10-30 seconds.
  • Service Alerts. These will typically change far less frequently then vehicle positions or trip updates. A system that triggered an update to the service alerts feed only when a new alert was entered into the system would be far more efficient than automatically doing it every X seconds.

To summarize:

  • Vehicle Positions. Update every 10-15 seconds.
  • Trip Updates. Update every 10-30 seconds.
  • Service Alerts. Triggered on demand when new data is available.

If your transit agency does not run all night, an additional efficiency would be to not update the feed at all when the network has shut down for the night.

In this case, once the last trip has finished, an empty protocol buffer would be uploaded (that is, a valid buffer but with no entities), and the next version would not be uploaded until the next morning when the first trip starts.