skip to content
Scott's Ramblings
Photo by lucas Favre / Unsplash

Vectorized Sports for Fun and Profit

/ 3 min read

Strava is pretty jazzy, and has essentially cornered the market on the more serious side of fitness tracking. For those of us with a penchant for data hoarding and shiny UIs however it leaves some itches un-scratched:

  • I’ve been using this internet thing for a while, and I’d rather avoid the desperate 11th hour scramble to get my data back out when you inevitably shut down
  • It’s really hard to find routes again once you’ve traveled them. You can view everywhere you’ve been in heatmap form, but there’s no way to go from here back to individual activities
  • It’s not possible to get an overview of all activities grouped by some property of them - e.g., sport, year, etc.

Back in 2016 I built a simple app that uses the strava API and mapbox to extract and visualize the data and have been using it ever since - check it out on github!

Fetching the data

Back when I wrote this, I was very much #TeamRuby, and as a result the project uses a dockerized chunk of ruby to pull all the activities for a particular strava account. The code is not particularly elegant but has been battle-hardened over the years of continuous use pulling my activities out, and I see no need to rewrite it. The least pleasant bit is probably the coarse-grained backoff to deal with Strava’s API limits, but the ruby syntax for retrying on exceptions makes it concise at least:

begin
fullActivity = newClient.activity(activity['id'])
rescue Strava::Errors::RatelimitError => e
# retry on rate limits forever!
puts("Strava rate limit hit; backing off for 5 minutes and retry")
sleep(300)
retry
end

To paraphrase a Western Australian entrepreneur - it’s not fancy, but it’s cheap. Data is cut up into three different formats:

  • Raw strava activity data, with all the different “streams” included - heartrate, power, and so on
  • GeoJSON
  • A mbtiles vector tileset containing everything

In this fashion we’ve got the entire set of our strava data backed up locally, plus a nice vector-tiled representation to drop into our maps.

Mapping the data

The frontend is a simple static webapp and the code is available here.

I’m a big fan of Mapbox GL JS and the great job they’ve done popularising client-side vector rendering of web maps and used it in this project. If I was to start again today i’d probably use maplibre, if only because it’s easier to reckon about the billing, but MapboxGL maintains a big enough free-tier to easily personally host this.

To wrap the thing up, I use a create-react-app’d react app, material UI, and Typescript. This is wonderfully unexciting, and has been my “let’s quickly build a webapp!” stack for some years now.

Hosting the thing

The fetcher and the frontend are published to Docker as part of the Github build process, and can be deployed easily wherever docker runs. I’ve written a simple Kubernetes manifest to make things easy if you have a Kubernetes cluster around, but it’s not necessary; there are also scripts to run the fetcher and the frontend with Docker itself. The containers share a volume so that the tiles generated by the fetcher can be accessed by the frontend.

Wrapping Up

Check out the repository readme and try it out!