How to open a GTFS Bus Feed in the Browser
Tags: gtfs, transit, protobuf, leaflet
Published: 2020-04-05 20:57:27

---
title: How to open a GTFS Bus Feed in the Browser
published: true
description: Add a GTFS feed to a map in the browser
tags: gtfs, transit, protobuf, leaflet
cover_image: https://i.imgur.com/hlgIWmQ.png
---

The [GTFS Real-time feed](https://developers.google.com/transit/gtfs-realtime) is a data specification for sharing real-time transit data. In other words, many transit systems share a public feed in this format for developers to use to build apps, websites, and more.

The most basic way to get going with this type of feed is to show the current locations on a map in a browser. This is not too tough, but there are a few things you need to get correct.

## Decoding the PBF Feed

The GTFS Specification says that the data feed will be encoded using [Protocol Buffers](https://developers.google.com/protocol-buffers/) (aka protobuf or PBF). There are a few JavaScript libraries for decoding protobufs - I used the [pbf library](https://github.com/mapbox/pbf).

The thing about protobuf is that the "schema" of the data is not sent when the data is sent - your client that reads the data must "already know" the schema. Luckily, we know our feed is in the GTFS scheme and they even provide the [schema file](https://developers.google.com/transit/gtfs-realtime/gtfs-realtime-proto) (protobuf schema files, when published, are usually named `___.proto`). 

We must convert that `.proto` file into something our JavaScript library (pbf) can use. The library provides a [command-line interface to do this](https://github.com/mapbox/pbf#proto-schema-to-javascript), but I've already done that for you and published the files onto npm so we can use them [via a CDN](https://unpkg.com/browse/gtfs-realtime-pbf-js-module@1.0.0/). In other words, the first step is to import these JS files into your file:

- `https://unpkg.com/pbf@3.0.5/dist/pbf.js`
- `https://unpkg.com/gtfs-realtime-pbf-js-module@1.0.0/gtfs-realtime.browser.proto.js`

Then we must write some code to actually get the data feed and decode it. That will look like this:

```js
const url =
  "https://my-transit-agency.org/path/to/feed.pb?cacheBust=" +
  new Date().getTime();
let response = await fetch(url);
if (response.ok) {
  // if HTTP-status is 200-299
  // get the response body (the method explained below)
  const bufferRes = await response.arrayBuffer();
  const pbf = new Pbf(new Uint8Array(bufferRes));
  const obj = FeedMessage.read(pbf);
  
  console.log("the data!", obj.entity);
} else {
  console.error("error:", response.status);
}
```

The keys to the above code:
1. We're using the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), but you could use plain XMLHttpRequest or any other request library just as easily, as long as you do the following:
2. We grab the data as an arrayBuffer (`await response.arrayBuffer()`)
3. .. then put the data into a Uit8Array (`new Uint8Array(bufferRes)`)
4. The `new Pbf(...)` and `FeedMessage.read(...)` are functions provided in the [pbf package](pbf#proto-schema-to-javascript).

You then just use `obj.entity` to loop through your data and do what you want with it. In our case, add it to a Leaflet GeoJSON layer and map it!

## Mapping the Data

To convert the GTFS feed data to GeoJSON to map it, loop through the array of data and return back a GeoJSON feature for each item:

```js
const gtfsArrayToGeojsonFeatures = (gtfsArray) => {
  return gtfsArray.map((gtfsObject) => {
    // console.log("gtfsObject", gtfsObject);
    return {
      type: "Feature",
      properties: {
        // Depending on your data source, the properties available on "gtfsObject" may be different:
        route: gtfsObject.vehicle.trip.route_id,
        route_start: gtfsObject.vehicle.trip.start_time,
        vehicle_label: gtfsObject.vehicle.vehicle.label
      },
      geometry: {
        type: "Point",
        coordinates: [
          gtfsObject.vehicle.position.longitude,
          gtfsObject.vehicle.position.latitude
        ]
      }
    };
  });
};
```

Place those features into a [Leaflet GeoJSON layer](https://leafletjs.com/reference-1.6.0.html#geojson), add it to a map, and you've got your mapped locations!

![Mapped GTFS Locations](https://i.imgur.com/xdi63kD.png "Mapped GTFS Locations")

## Full Code

Putting it all together, [here's what the code would look like](https://codepen.io/gavinr/pen/oNXrYGw?editors=0010):

{% codepen https://codepen.io/gavinr/pen/oNXrYGw default-tab=js,result %}

## CORS

Your GTFS feed must have a [CORS header](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) to allow your webpage to access the feed. If it doesn't, you may be able to use a proxy to add that header, or talk to the data owner asking them to add the CORS header to allow easier usage.

## Links

- [More feature-filled live-updating example using the ArcGIS API for JavaScript](https://codepen.io/gavinr/pen/XWbzGMW?editors=0010)
- [More info on using Protobuf in a browser](https://gavinr.com/protocol-buffers-protobuf-browser/)

What public GTFS feeds are you using, and how are you using them? Let me know in the comments! Thanks!