Skip to content
bdferris edited this page Feb 28, 2012 · 5 revisions

The goal of the GTFS-realtime specification is to provide both transit agencies and developers with a consistent way to exchange real-time public transit data. Two specific features of GTFS-realtime is support for trip updates, which allow an agency to specify information about delays, and vehicle positions, which allow an agency to specify information about vehicle locations.

I want to make it easy to provide trip updates and vehicle position information using the GTFS-realtime format, so I've put together a quick demo project that shows how to produce GTFS-realtime trip update and vehicle position feeds.

In our demo project, we'll walk you through a simple Java-based program that converts a custom vehicle data-source from SEPTA into a GTFS-realtime format.

If you want to follow along at home, you can download a ZIP of the source-code for the demo project or import the code from the Git repository directly. The project is designed to be built with Apache Maven, so download that if you are interested in building and running the project.

Running the Demo Project

Before we dig into how the demo application the works, let's see it in action! After you've downloaded the code for the project, open up a terminal and change to the root directory of the project. From there, run Maven to compile and package the application:

mvn package

Now that the project has been built, you should be able to run the resulting application bundle:

java -jar target/onebusaway-gtfs-realtime-trip-updates-producer-demo-0.0.1-SNAPSHOT.jar \
  --tripUpdatesUrl=http://localhost:8080/trip-updates \
  --vehiclePositionsUrl=http://localhost:8080/vehicle-positions

Once the application has started, you should see output like:

2012-02-24 18:48:41,420 INFO  [GtfsRealtimeProviderImpl.java:103] : starting GTFS-realtime service
2012-02-24 18:48:41,421 INFO  [GtfsRealtimeProviderImpl.java:281] : refreshing vehicles
2012-02-24 18:48:41,870 INFO  [GtfsRealtimeProviderImpl.java:257] : vehicles extracted: 24

Vehicles will be refreshed every 30 seconds. You can access the resulting GTFS-realtime alert stream at http://localhost:8080/trip-updates and http://localhost:8080/vehicle-positions but it will be binary-encoded, which won't mean much unless you are a computer. You can see a more human-friendly debug version of the stream at http://localhost:8080/trip-updates?debug and http://localhost:8080/vehicle-positions?debug. The output of the trip updates feed should look something like:

header {
  gtfs_realtime_version: "1.0"
  incrementality: FULL_DATASET
  timestamp: 1330105721847
}
entity {
  id: "3433"
  trip_update {
    trip {
      route_id: "Airport"
    }
    stop_time_update {
      arrival {
        delay: 180
      }
      stop_id: "Temple U"
    }
    vehicle {
      id: "3433"
    }
  }
}

Hey, that's a GTFS-realtime service trip updates feed!

Digging into the Code

So how does this all work? Let's look at the code! Most of the work is done in the following class:

org.onebusaway.gtfs_realtime.trip_updates_producer_demo.GtfsRealtimeProviderImpl

The class does the following:

  • Periodically downloads vehicle data from the SEPTA alert data-stream.
  • Extracts vehicle information from the SEPTA data.
  • Constructs a GTFS-realtime trip update and vehicle position for each vehicle in the original feed.
  • Save the resulting GTFS-realtime feeds.

Let's step through the code for each of these steps.

We setup a recurring task that downloads data from the SEPTA vehicle data-stream. The data comes in the form of an array of JSON objects:

JSONArray vehicleArray = downloadVehicleDetails();

Next we create two FeedMessage.Builder objects that we will use to build the FeedMessage that represents the GTFS-realtime feed for both trip updates and vehicle positions. We use a convenience method from the OneBusAway GTFS-realtime library to setup a FeedMessage with a FeedHeader already added.

FeedMessage.Builder tripUpdates = GtfsRealtimeLibrary.createFeedMessageBuilder();
FeedMessage.Builder vehiclePositions = GtfsRealtimeLibrary.createFeedMessageBuilder();

Next, we iterate over the SEPTA vehicle objects, extracting important information.

for (int i = 0; i < vehicleArray.length(); ++i) {

  JSONObject obj = vehicleArray.getJSONObject(i);
  String trainNumber = obj.getString("trainno");
  String route = obj.getString("dest");
  String stopId = obj.getString("nextstop");
  double lat = obj.getDouble("lat");
  double lon = obj.getDouble("lon");
  int delay = obj.getInt("late");

We construct a TripDescriptor and VehicleDescriptor, which will be used in both trip updates and vehicle positions to identify the trip and vehicle. Ideally, we would have a trip id to use for the trip descriptor, but the SEPTA api doesn't include it, so we settle for a route id instead.

  TripDescriptor.Builder tripDescriptor = TripDescriptor.newBuilder();
  tripDescriptor.setRouteId(route);

  VehicleDescriptor.Builder vehicleDescriptor = VehicleDescriptor.newBuilder();
  vehicleDescriptor.setId(trainNumber);

To construct our TripUpdate, we create a StopTimeEvent for the arrival at next stop for the vehicle, with the specified arrival delay. We add the StopTimeUpdate to a TripUpdate builder, along with the trip and vehicle descriptors.

  StopTimeEvent.Builder arrival = StopTimeEvent.newBuilder();
  arrival.setDelay(delay * 60);

  StopTimeUpdate.Builder stopTimeUpdate = StopTimeUpdate.newBuilder();
  stopTimeUpdate.setArrival(arrival);
  stopTimeUpdate.setStopId(stopId);

  TripUpdate.Builder tripUpdate = TripUpdate.newBuilder();
  tripUpdate.addStopTimeUpdate(stopTimeUpdate);
  tripUpdate.setTrip(tripDescriptor);
  tripUpdate.setVehicle(vehicleDescriptor);

To finish off the TripUpdate, we create a new FeedEntity to wrap the trip update and add it to the GTFS-realtime trip updates feed.

  FeedEntity.Builder tripUpdateEntity = FeedEntity.newBuilder();
  tripUpdateEntity.setId(trainNumber);
  tripUpdateEntity.setTripUpdate(tripUpdate);
  tripUpdates.addEntity(tripUpdateEntity);

To construct our VehiclePosition, we create a Position for the vehicle. We add the position to a VehiclePosition builder, along with the trip and vehicle descriptors.

  Position.Builder position = Position.newBuilder();
  position.setLatitude((float) lat);
  position.setLongitude((float) lon);

  VehiclePosition.Builder vehiclePosition = VehiclePosition.newBuilder();
  vehiclePosition.setPosition(position);
  vehiclePosition.setTrip(tripDescriptor);
  vehiclePosition.setVehicle(vehicleDescriptor);

To finish off the VehiclePosition, we create a new FeedEntity to wrap the vehicle position and add it to the GTFS-realtime vehicle positions feed.

  FeedEntity.Builder vehiclePositionEntity = FeedEntity.newBuilder();
  vehiclePositionEntity.setId(trainNumber);
  vehiclePositionEntity.setVehicle(vehiclePosition);
  vehiclePositions.addEntity(vehiclePositionEntity);

We build out the final GTFS-realtime feeds and save them for later.

_tripUpdates = tripUpdates.build();
_vehiclePositions = vehiclePositions.build();

We've now got GTFS-realtime trip update and vehicle position feeds!

Of course, we've left out a few details. We take advantage of a couple of OneBusAway libraries to simplify our application:

Next Steps

We provided this demo application as an example that you can build on when generating your own GTFS-realtime feed. The format of your vehicle data source may differ, but hopefully we've given you a simple model that you can adapt to produce your own feed.

Also be sure to check out all our GTFS-realtime resources.

Clone this wiki locally