← ontrack lab

Five million GPS points

Every GPS point my Garmin has recorded since 2014, drawn as the places I run — with a live view you can zoom all the way down to a single sample.

11 June 2026

A few people have asked how to make one of these from their own data. Now you can: drop your Garmin or Strava export into your footprints and watch your own decade draw itself — it parses and renders entirely in your browser, and your GPS data never uploads anywhere.

This is every data point my Garmin has recorded since 2014 — a little over five million points in total. For most of those eleven years the watch wrote a sample every second; for three short stretches (the first half of 2015, the summer of 2016, and the first weeks of a new watch after Christmas 2021) it was on Garmin's "smart recording", which only writes a point every few seconds when pace or direction changes — something I only discovered by checking the gaps between timestamps while building this page. Nothing is summarised or smoothed. Each GPS sample carries a latitude and longitude, and these have been projected on to a 2D canvas. Each spot is slightly transparent, so areas where I have run more show up brighter. The static images below are created using datashader. If you tap or click on the image, you'll be taken to a (fairly gpu intensive) scene which renders 100s of thousands of these points as an interactive 3D point cloud. Totally unnecessary, but really quite beautiful!

The best way to understand it is to try it. This is Cambridge, where I lived for a few years, and where 1.2 million of those points landed. Tap or click the image, watch a decade of runs arrive in date order, then stir the cloud and zoom in as far as you like.

Cambridge

What you're looking at

Each dot is one GPS sample, drawn almost completely transparent. A road only becomes visible once I've run it a few times, and it only glows once I've run it hundreds of times. The brightness isn't a style choice — it's just overlap.

The large images are pre-rendered from the full dataset. The view behind each one is live, though: your browser is drawing every sample in that region as a separate particle.

When you open one, the points arrive in date order, and the date in the corner ticks along as they land. Training blocks show up as floods of points; injuries show up as pauses. Once everything has settled you can stir the cloud — sweep your cursor through it, or drag a finger across it on a phone — pan around, and zoom right down to individual samples.

If you're curious how it's built, the notes below cover that — and after them, the rest of the places.

How it’s made — the technical bit

From watch to browser

The raw data is eleven years of Garmin .FIT files. A Python script reads every sample, converts the coordinates (FIT stores them as 32-bit "semicircles") and projects them to Web Mercator so streets keep their shape. Most files carry a point per second, but checking the timestamp gaps revealed three stretches — early 2015, summer 2016, and Jan–March 2022 — where the watch was on Garmin's default "smart recording" and only sampled every three seconds or so. About 250 of the 2,500 runs are recorded that way, so those routes contribute proportionally fewer points to the picture, but every run since mid-March 2022 is a true one-second trace. The still images are rendered with Datashader, which rasterises the points rather than drawing them individually — five million points cost about the same as fifty — and histogram-equalised shading keeps both the saturated home streets and one-off races visible in the same image.

The live view needs the actual points. Five million coordinates as JSON would run to tens of megabytes, so each region's samples are packed into a binary of int16 pairs — four bytes per point. Cambridge, the densest region at 1.2 million samples, comes to about 5 MB, plus a smaller file of uint16 timestamps that drives the date-ordered build.

One deliberate exception to "every point": a short, randomised stretch around each run's start and finish is removed before anything is exported. Watch start/stop points cluster on doorsteps, and this is still the public internet.

One draw call

The point cloud is a single three.js Points mesh. Each point's start position, final position and arrival time are baked into vertex attributes, so the whole fly-in is one draw call driven by a clock uniform — the CPU does no per-point work. Brightness comes free with additive blending: it's literally a count of how many samples hit each pixel.

The smoke effect

Stirring the cloud runs a small GPU simulation. A texture holds a displacement and a velocity for every particle, and a render pass updates it each frame: particles near the cursor get pushed along its direction of travel, then damped and pulled back to where they belong. Each particle derives its own push angle, strength, damping and return speed from a hash of its texture coordinate, which is what stops the cloud moving as one solid blob — neighbouring points scatter slightly differently and come back at different speeds.

The trails

The streaks during the fly-in are an old trick: while the animation runs, the previous frame is faded towards the background instead of cleared, so anything moving leaves a short tail. The fade ramps up to a full clear as the animation finishes, which dissolves the trails into the final image.

Performance

Millions of additively-blended points are limited by fill rate, so the explorer adapts in a few ways. Zoomed out, it draws every other sample at double opacity — which sums to the same brightness for half the cost — and brings every sample back as you zoom in. It measures its own frame times and lowers the render resolution until whatever machine it's on holds a steady frame rate, with dot sizes compensated so nothing visibly changes. And when nothing is moving, it stops rendering altogether.

The other places

Everywhere else the last eleven years of running added up to. Same idea as Cambridge: tap any of them to explore.

London

Hampshire

Somerset

The west country

The New Forest