Skip to content

Instantly share code, notes, and snippets.

@apiarian
Last active January 3, 2022 14:47
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save apiarian/7ee56dd130fb983dccd562805f5d3acf to your computer and use it in GitHub Desktop.
Save apiarian/7ee56dd130fb983dccd562805f5d3acf to your computer and use it in GitHub Desktop.
CitiBike Status iOS Widget
function lat_lon(thing) {
return {
"lat": thing.lat || thing.latitude,
"lon": thing.lon || thing.longitude,
}
}
function distance(loc1, loc2) {
loc1 = lat_lon(loc1)
loc2 = lat_lon(loc2)
// from https://www.movable-type.co.uk/scripts/latlong.html
const R = 6371e3 // meters
const phi1 = loc1.lat * Math.PI/180
const phi2 = loc2.lat * Math.PI/180
const lam1 = loc1.lon * Math.PI/180
const lam2 = loc2.lon * Math.PI/180
const x = (lam2 - lam1) * Math.cos((phi1 + phi2)/2)
const y = (phi2 - phi1)
return Math.sqrt(x*x + y*y) * R
}
now = new Date()
date_formatter = new DateFormatter()
date_formatter.useNoDateStyle()
date_formatter.useShortTimeStyle()
// pull the directory of feeds
let directory_url = "http://gbfs.citibikenyc.com/gbfs/gbfs.json"
let directory = await new Request(directory_url).loadJSON()
let feeds = directory.data.en.feeds
function extract_feed(name) {
return new Request(feeds.find(e => e.name == name).url).loadJSON()
}
// start pulling station information and status
let station_information = extract_feed("station_information")
let station_status = extract_feed("station_status")
station_information = await station_information
station_status = await station_status
// turn the station status into something easier to use
let station_status_by_id = new Map(station_status.data.stations.map(e => [e.station_id, e]))
// where are we going?
let iCloud = FileManager.iCloud()
let citibike_locations_path = iCloud.bookmarkedPath("citibike-locations")
await iCloud.downloadFileFromiCloud(citibike_locations_path)
let citibike_locations = JSON.parse(iCloud.readString(citibike_locations_path))
let location_info = citibike_locations._target
let target_location = citibike_locations[citibike_locations._target]
// sort the station information bits by distance to the target
station_information.data.stations.forEach(e => e.distance = distance(e, target_location))
station_information.data.stations.sort((a, b) => a.distance - b.distance)
let widget = await createWidget(station_information, station_status_by_id)
if (!config.runsInWidget) {
await widget.presentMedium()
}
Script.setWidget(widget)
Script.complete()
function createWidget(station_information, station_status_by_id) {
let w = new ListWidget()
const num_lines = 6
for (let i = 0; i < num_lines; i++) {
let station = station_information.data.stations[i]
let status = station_status_by_id.get(station.station_id)
let station_line = w.addText(`${station.name}: ${status.num_bikes_available} b, ${status.num_docks_available} d`)
station_line.font = Font.systemFont(15)
}
w.addSpacer(null)
let info_line = w.addText(`data: ${date_formatter.string(new Date(station_status.last_updated*1000))}, widget: ${date_formatter.string(now)}, target: ${location_info}`)
info_line.font = Font.thinRoundedSystemFont(12)
return w
}

CitiBike Status Widget

Runs as a Scriptable App iOS 14 Medium Widget. Requires a citibike-locations file shortcut in the Scriptable App. That file shortcut is a /Shortcuts/citibike-locations.json file managed by the following shortcuts:

NOTE: this downloads all of the station information and statuses every time. Those files contain everything there is to know about all of the CitiBike stations. That's a fair bit of data. I've heard of folks building middleware APIs to deal with this. Might add something like that in the future. Or perhaps a bit of caching.

@thirstbuster
Copy link

I’m getting a error on line 49 after creating citibike location and setting target.

@apiarian
Copy link
Author

apiarian commented Jan 1, 2021

did you set up the citibike-locations file link which is managed by the Shortcut?

@jfischetti22
Copy link

This widget is amazing. Thanks so much for creating it and sharing it. Is there an easy way to indicate how many electric bikes are available at a particular station too? That would be really helpful. Cheers!

@apiarian
Copy link
Author

apiarian commented Feb 9, 2021

yup! there’s a status.num_ebikes_available field along with the status.num_bikes_available.

@jfischetti22
Copy link

jfischetti22 commented Feb 9, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment