I have spent many hours riding CTA elevated trains. Most notably from my home on the NW side of Chicago to Loyola University’s Downtown campus — Logan Square on the Blue Line to Washington, walk through the pedestrian tunnel to the Red Line, to Chicago Avenue. After graduation, I got an apartment in Evanston and took the Purple line from Linden in Wilmette to the Merchandise Mart where I worked.
So I took a great interest in an ad for this product:

It’s a map of the CTA lines with LEDs that light to indicate where the trains currently are — in real time. I wanted it. But at $300, it was out of reach. So, I’ll make one for myself.
The blog post is about my efforts to create a proof of concept in software before I consider a hardware solution.
The first step is to source the data. It turns out the CTA has a free, public REST API regarding where its trains are. There are multiple endpoints to the API. The most important to this project is ttpositions that shows the location of every active train on a particular line – Red, Blue, Orange, etc…
http://lapi.transitchicago.com/api/1.0/ttpositions.aspx?rt=red
and returns the following interesting values:
Train No. | Next Stop | Approaching? | Latitude | Longitude | Heading |
811 | Chicago Ave. | No | 41.903 | -87.631 | 89 |
813 | Wilson | Yes | 41.964 | -41.964 | 344 |
A little explanation will help here. Each active train has a unique identifying number. When a train reaches a terminus, the number is recycled.
Next stop shows the station name at which the train will make its next stop. Even if a train is sitting stopped at some station, it will be the next station down the line that will be returned. A train might also be between two stops.
If the train is just about to pull into a station, Approaching is “yes”.
Latitude and Longitude are the map coordinates of the train.
Heading is the compass direction for the train’s motion.
It’s simple to take these lat/lon data and create a KML (Keyhole Markup Language) file and feed that file into Google Earth Pro. KML files are XML. A KML file will contain XML elements to specify colors, font size, icon, and the position and orientation of the viewpoint — the point in space that determines what you’re looking at, etc.
The most critical element of the KML is a place marker for each train. The place marker looks like this:
<Placemark id="7"> <name>605 Ridgeland 89°</name> <styleUrl>#g</styleUrl> <Point id="7"> <coordinates>-87.79378,41.88699,300</coordinates> </Point> </Placemark>
In this example, the train number, next station name and compass heading are used to label each dot on the map. styleUrl references a previously defined style that specifies the formatting of the place marker. The element coordinates specifies the lon, lat.
My minimally viable product runs a query for each CTA Line, outputs the data to the KML file, and opens the file in Google Earth.

If this is done repeatedly every 15, 30 or 60 seconds, you’ll be able to watch each train move through the system.
MACE, You’re Off the Rails!
A glaring hole in this version is the lack of context. We can’t see the elevated tracks.
The API can again help us with that. Another API endpoint is ttfollow which gives details about a specific train — including its coordinates
http://lapi.transitchicago.com/api/1.0/ttfollow.aspx?runnumber=811
My strategy to plot the train tracks was to find a train that was just leaving a terminus and repeatedly query that run number (train number) and collect lat/lon until that train reached the opposite terminus. That set of points can be coded into a KML file as a PolyLine drawn from point to point.
Sounds simple, but that’s when my frustrations began to grow.
Challenges
Zeroth, The documentation for the API says that when you query a line for all trains, it returns an array of train dictionaries. But if there is only one active train on the line, rather than a one element array, it’s a scalar dictionary. And if no trains are running on the line, the “trains” JSON key doesn’t even exist. This wasn’t a problem earlier until I did the Purple line during non-rush hour and the Yellow (Skokie Swift) line. I found solutions, but it makes the code kludgey.
First, it’s difficult to catch a train at a terminus. It’s not like the train is sitting at the O’Hare terminal, broadcasting its GPS and a station name of O’Hare. The best I could do was randomly catch a train soon after it left a terminus moving toward its next stop – Rosemont. So I had to frequently query the Blue Line hoping to see a Rosemont train HEADING EAST (as opposed to a Rosement train heading west from Cumberland!). Once I saw a train starting a run, I had to take its number and train queries.
OK — annoying, but then, patience is a virtue.
Second, as I was monitoring the progress of a train it got to somewhere near the Belmont station when POOF — It drops off the radar never to return. I have no idea why that train disappeared and I have seen this anomaly multiple times since.
OK — annoying, but…
Third, after I’d collected about 60 data points, I plotted them and saw a most bizarre path.

The train appeared to jump forward then back then forward again. I observed this behavior many times and I didn’t believe this is what the train was actually doing. I concluded there is something wrong with the data being returned from the API. Letters to the API manager have gone unanswered.
I ended up developing a workflow where I could identify these spacetime anomalies and correct them. If you look at the illustration above, if you get rid of point #3 (which is the exact same coordinates of point #7), the data looks realistic and reasonable. I imported the data points into Excel and used conditional formatting to highlight duplicates:

Then I simply deleted the first duplicate (which unformats that dup’s twin) and then moved on to the next “first” duplicate. Once no duplicates are present, I put the datapoints into the KML file to plot the line.
I went through this process for each of the CTA Lines.
The end result of all this is the following graphic:

When the train positions are overlayed onto the colored Lines, you get:

If you repeatedly plot the trains, you get a timelapse of the movement of the trains through the system.
This exercise shows the hardware version of this project is entirely possible. A prototype of the Purple Line seems very doable. It will need 9 LEDs for each stop plus 8 more for between-station lights. I want a string of LEDs for Southbound trains and a second string for Northbound trains. That makes 36 LEDs total. The processing flow would be:
Query the Purple line and get its trains
For each train in AllTrains
Look at the Next Stop and direction of each train
If Approaching is Yes,
light up the station
Else
If Approaching is No,
light the between-station-LED just prior to Next Stop
Rinse, Repeat
There is still the issue of time warping, but I no longer care. I’ll just assume that if a train is reported at a given lon/lat, then that’s where it is.
Next Stop
The next step is to build a prototype using LED lights and migrate the software to a Raspberry Pi.