Skip to content

Instantly share code, notes, and snippets.

@apiarian
Last active January 3, 2022 14:47
Show Gist options
  • Select an option

  • Save apiarian/7ee56dd130fb983dccd562805f5d3acf to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

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

@apiarian
Copy link
Copy Markdown
Author

apiarian commented Jan 1, 2021

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

@jfischetti22
Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown

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