Mind the Gap — a CLI app using the TFL API — Part 3

artismarti
4 min readNov 27, 2018

--

This post details how I got the station data from the API. To read about the planning, go here. To read about the MVP, go here. The final code repo can be found here.

The API

Caveat emptor: The TFL API is not very well documented or at least not well enough for someone who’s not got much experience using APIs.

After hours of trying to figure out the TFL API, this is what I learned: Tube lines could be found in this API endpoint — which returned an array of hashes for each of the tube lines:

[
{
"$type": "Tfl.Api.Presentation.Entities.Line, Tfl.Api.Presentation.Entities",
"id": "bakerloo",
"name": "Bakerloo",
"modeName": "tube",
"disruptions": [],
"created": "2018-11-20T14:36:46.437Z",
"modified": "2018-11-20T14:36:46.437Z",
"lineStatuses": [],
"routeSections": [],
"serviceTypes": [
{
"$type": "Tfl.Api.Presentation.Entities.LineServiceTypeInfo, Tfl.Api.Presentation.Entities",
"name": "Regular",
"uri": "/Line/Route?ids=Bakerloo&serviceTypes=Regular"
}
],
"crowding": {
"$type": "Tfl.Api.Presentation.Entities.Crowding, Tfl.Api.Presentation.Entities"
}
},
{
"$type": "Tfl.Api.Presentation.Entities.Line, Tfl.Api.Presentation.Entities",
"id": "central",
"name": "Central",
"modeName": "tube",
"disruptions": [],
"created": "2018-11-20T14:36:46.433Z",
"modified": "2018-11-20T14:36:46.433Z",
"lineStatuses": [],
"routeSections": [],
"serviceTypes": [
{
"$type": "Tfl.Api.Presentation.Entities.LineServiceTypeInfo, Tfl.Api.Presentation.Entities",
"name": "Regular",
"uri": "/Line/Route?ids=Central&serviceTypes=Regular"
},
{
"$type": "Tfl.Api.Presentation.Entities.LineServiceTypeInfo, Tfl.Api.Presentation.Entities",
"name": "Night",
"uri": "/Line/Route?ids=Central&serviceTypes=Night"
}
],
"crowding": {
"$type": "Tfl.Api.Presentation.Entities.Crowding, Tfl.Api.Presentation.Entities"
}
},.... etc etc for all 11 lines

The only data that was useful in this array of hashes was the line name and the line id. So since there are only 11 lines and we’re not getting any more underground lines anytime soon & because life is short, I made the executive decision to just add this data manually. (🙈 I’m sorry — don’t judge me. I promise everything else was API magic.)

From what I can tell, TFL does not have one endpoint that serves up all the lines and stations on that line. So each line id had to be plugged in to the API url to retrieve the station names for that line.
For instance, to get all the stations on the Victoria line, the url had to be customised to this¹.

https://api.tfl.gov.uk/Line/VICTORIA/Route/Sequence/outbound?serviceTypes=Regular&excludeCrowding=true

Data returned for Victoria line

The data is in a hash with one of the keys being stations. The station key has a name that contains that station name.

Step 1: Get all the line ids (from the hard coded lines):

def get_tfl_line_ids
Line.all.map{|l| l.tfl_id}
end

Step 2: Use the line ids to iterate through the API.

def get_all_tube_stations
stops_hash = Hash.new{}
get_tfl_line_ids.each do |l|
url = "https://api.tfl.gov.uk/Line/#{l}/Route/Sequence/inbound?excludeCrowding=false"
response_string = RestClient.get(url)
response_hash = JSON.parse(response_string)
tube_station_names = []
response_hash["stations"].each do |station|
tube_station_names << station["name"]
stops_hash["#{l}"] = tube_station_names
end
end
File.open("tfl.rb", 'w') { |file| file.write(stops_hash) }
end

Let’s break this down:

Create an new hash

stops_hash = Hash.new{} 

For every TFL line id iterate through the API:

get_tfl_line_ids.each do |l|
url = "https://api.tfl.gov.uk/Line/#{l}/Route/Sequence/inbound?excludeCrowding=false"

Parse the JSON:

response_string = RestClient.get(url)
response_hash = JSON.parse(response_string)

Loop through the response hash and push the stations for each line into an array:

tube_station_names = []
response_hash["stations"].each do |station|
tube_station_names << station["name"]

Add the TFL line id as the key and the tube station names from the array as the values for the key.

stops_hash["#{l}"] = tube_station_names

Lather. Rinse. Repeat. Then write the hash to a file:

File.open("tfl.rb", 'w') { |file| file.write(stops_hash) }

I chose to write this to a file, rather than make calls to TFL every time a request was made to improve response time and also because just like lines, stations aren’t going to be added anytime soon.

Now I had a file with all lines and stations in a hash!

I can haz stops!

In the next post, I’ll write about how to get disruption data from TFL and also show off some screenshots of what the app ended up looking like. 🙋🏽‍♀️

¹ You could also use this endpoint, which I discovered much later & haven’t bothered to go back to change because it also needs customised urls for each line.

--

--

artismarti

Software Developer, Product Manager, biryani loving, फ़ेमिनिस्ट किल जॉय. Jill of all trades. Mistress of some.