Script to get London Tube status using a TFL-provided API.
/************
* Get status update for London Underground lines
*
* Although TFL generally require auth, they appear
* to allow use of the Status request without it.
*/
// 1. Get the data in a JSON object using 'await'
const url = "https://api.tfl.gov.uk/Line/Mode/tube/Status"
let req = new Request(url)
let json = await req.loadJSON()
// 2. Create a UITable to present the data.
let table = new UITable()
// 3. For each tube line ..
for (line of json) {
// ... create a new row ...
let row = new UITableRow()
// ... get the line name and statuses ...
let lineName = line.name
let status = line.lineStatuses.map(s => s.statusSeverityDescription)
// ... generate a string from statuses ...
let count = status.length
let str = status.reduce((t,s,i) => {
t = t + s
if (i < (count- 1) && count > 0) { t = t + " &\n "}
return t
}, "")
// ... calculate row height ...
let height = 16 + Math.max(count,2) * 22
row.height = height
// ... draw a colored rect
const cW = 80
let c = new DrawContext()
c.size = new Size(cW, height * 4)
c.setFillColor(getLineColor(lineName))
c.fill(new Rect(0, 0 , cW, height * 4))
// ... populate the row with data ...
let imageCell = row.addImage(c.getImage())
let titleCell = row.addText(lineName)
let statusCell = row.addText(str)
// ... format columns and rows ...
row.cellSpacing = 10
imageCell.widthWeight = 5
titleCell.widthWeight = 35
statusCell.widthWeight = 60
// ... add row to table ...
table.addRow(row)
}
// ... and present table
QuickLook.present(table)
function getLineColor(line) {
switch(line) {
case "Bakerloo":
return new Color("#B26300")
break
case "Central":
return new Color("#DC241F")
break
case "Circle":
return new Color("#FFD329")
break
case "District":
return new Color("#007D32")
break
case "Hammersmith & City":
return new Color("#F4A9BE")
break
case "Jubilee":
return new Color("#A1A5A7")
break
case "Metropolitan":
return new Color("#9B0058")
break
case "Northern":
return new Color("#000")
break
case "Piccadilly":
return new Color("#0019A8")
break
case "Victoria":
return new Color("#0098D8")
break
case "Waterloo & City":
return new Color("#93CEBA")
break
default:
return new Color("CCC")
break
}
}
The API returns an array of statuses (if that’s a word!) for each line - the actual detail behind the above is that there’s severe delays on part of the line (first status) and minor delays on the rest (second status). Haven’t had time yet to work out the best way to present that.
Thanks. I don’t travel through or around London a great deal. I have seen severe and good statuses before but no others that I recall - though there must surely have been at some point and I’ve simply not noted it!
I just checked my usual app for planning my routes and the line status it provides actually looks quite similarly displayed. They just opted to include a plus to indicate a combined status.
Further updated to look a bit nicer and to include details behind status messages:
/************
* Get status update for London Underground lines
*
* Although TFL generally require auth, they appear
* to allow use of the Status request without it.
*/
// 1. Get the data in a JSON object using 'await'
const url = "https://api.tfl.gov.uk/Line/Mode/tube/Status"
let req = new Request(url)
let json = await req.loadJSON()
// 2. Parse each line into required items
const data = []
for (line of json) {
// ... create a new item ...
let dataItem = {}
// ... get the line name and store in <h1> ...
dataItem.name = line.name
dataItem.displayName = `<h2>${line.name}</h2>`
// ... get status...
let status = line.lineStatuses.map(s => [s.statusSeverityDescription, s.reason] )
// ... and generate <h1> and <p> tags ...
let count = status.length
let str = status.reduce((t,s) => {
t = t + `<h3>${s[0]}</h3>`
if (s[1]) {
let str2 = s[1]
let strSearch = `${dataItem.name} Line: `
if (str2.startsWith(strSearch)) {
str2 = str2.substring(strSearch.length)
}
t = t + `<p>${str2}</p>`
}
return t
}, "")
dataItem.displayStatus = str
data.push(dataItem)
}
// 3. Generare combined html
const FONTSIZE = 14;
const COLORWIDTH = 10;
const NAMEWIDTH = FONTSIZE * 10;
const MARGIN = 5;
let headContent = `
<meta name="viewport" content="initial-scale=1.0">
<style>
.body, h1, h2, h3, p {
font-family: -apple-system, sans-serif;
}
body {
display: grid;
grid-template-columns: (${COLORWIDTH}px, ${NAMEWIDTH}px, 1fr);
grid-row-gap: ${MARGIN}px;
grid-column-gap: ${MARGIN}px
}
h1 {
font-size: ${FONTSIZE * 1.6}px;
font-weight: 600;
margin-top: 0;
grid-column: 1/4;
}
h2 {
font-size: ${FONTSIZE *1.5}px;
font-weight: 400;
margin: 0;
}
h3 {
font-size: ${FONTSIZE *1.3}px;
margin: 0;
}
p {
margin-top: 0;
margin-bottom: 0.25em;
}
.color {
width: ${COLORWIDTH}px;
grid-column: 1;
}
.name {
display: inline-block;
width: ${NAMEWIDTH}px;
grid-column: 2;
}
.status {
display: inline;
grid-column: 3;
}
.spacer {
grid-column: 1/4;
border-top: 1px solid gray;
}
</style>
`
let bodyContent = data.reduce((t,s) => {
const c = getLineColor(s.name)
t = t + '<div class="spacer"></div>'
t = t + `<div class="color" style="background-color: ${c}"></div>`
t = t + `<div class="name">${s.displayName}</div>`
t = t + `<div class="status">${s.displayStatus}</div>`
return t
}, "")
let date = new Date()
let dateStr = date.getHours() + ":" + date.getMinutes()
let myHTML = `
<html>
<head>
${headContent}
</head>
<body>
<h1>London Tube Status @ ${dateStr}</h1>
${bodyContent}
<div class="spacer"></div>
</body>
</html>
`
WebView.loadHTML(myHTML)
// not sure why I've done this as a function, would be better as an object with the line name as a key?
function getLineColor(line) {
switch(line) {
case "Bakerloo":
return "#B26300"
break
case "Central":
return "#DC241F"
break
case "Circle":
return "#FFD329"
break
case "District":
return "#007D32"
break
case "Hammersmith & City":
return "#F4A9BE"
break
case "Jubilee":
return "#A1A5A7"
break
case "Metropolitan":
return "#9B0058"
break
case "Northern":
return "#000"
break
case "Piccadilly":
return "#0019A8"
break
case "Victoria":
return "#0098D8"
break
case "Waterloo & City":
return "#93CEBA"
break
default:
return "CCC"
break
}
}
Not sure about previous code, but with revised code they’re a css-styled div, so I imagine that setting a height property on the div (equal to width) and adding a border-radius: 50% style in the css would achieve this.