Creating an Interactive NYC Live MTA Bus Tracker: Reverse Engineering the MTA Bus Time API's Route Naming Convention, Fetching and Processing Live Bus Data with AWS Lambda, and Mapping with Mapbox GL JS

(For the GitHub repository, please click here, and for the interactive map, please click here.)

Summary

As an NYC resident and avid MTA bus rider, while I appreciate the MTA app, I wish it would actually visualize your bus options. Instead of showing them on their map, you have to pull up an info panel that just lists nearby routes. That’s why I decided to create this interactive map: to be able to see every single MTA bus currently in service across the city.

This map makes an API call to the MTA Bus Time API every 45 seconds. When a user hovers (desktop) or taps (mobile) on a bus, a popup appears showing the bus number, destination, and next few stops — along with the full route path on the map. Users can also select a route from the borough and express dropdowns, which highlights all buses on that route and displays the full route path.

The frontend is hosted on GitHub Pages, while the backend lives in an AWS Lambda function. Every 45 seconds, the browser makes a direct API call to the Lambda URL, which fetches the latest bus data from the MTA Bus Time API and returns it to the map.

Considerable time was spent reverse engineering the MTA Bus Time API's route naming convention. This is because the API returns a PublishedLineName field that's sometimes a string and sometimes a list, so I first had to normalize that inconsistency before parsing the route name.

NYC bus routes follow a prefix-based system that encodes both borough and service type. I learned that express routes get their own category regardless of borough, and the prefixes are “BXM” (Bronx express), “QM” (Queens express), “BM” (Brooklyn express), “SIM” (Staten Island express), and “X” (a general express prefix that’s apparently a legacy naming convention). For local routes, a single or double letter prefix indicates the borough: “M” for Manhattan, “B” for Brooklyn, “Q” for Queens, “S” for Staten Island, and “BX” for the Bronx.

In order to add the route shapes to the map, I downloaded GTFS (General Transit Feed Specification) data from the MTA, which includes a shapes.txt file in one of six folders - one for each borough and then the sixth for express buses in the MTA Bus Company folder. Those files contain sequences of latitutde/longitude coordinates defining the physical path of every bus route.

Then, I converted the shapes.txt data into GeoJSON format, which is what Mapbox GL JS understands for rendering lines on the map. The GeoJSON was hosted on GitHub and loaded into Mapbox as a source layer, so when a user hovers a bus or selects a route from the dropdown, Mapbox can instantly draw the route path on the map by filtering to that route's GeoJSON feature.

Tools Used

  • Python

  • MTA Bus Time API

  • AWS Lambda

  • Mapbox GL JS

  • GitHub Pages

  • Javascript

  • GTFS Shape Data converted to GeoJSON

Next
Next

Interactive NYC Restaurant Week Map Using Selenium, Places & Geocoding APIs, React