Bryan Hamilton / writing

bryanhamilton.info / writing / article

I Calculated How Far 99 DJs Tour in a Year. With Math From the 1700s.

Haversine great-circle math, a Null Island coordinate guard, and one venue-matching rule: how a tour-miles leaderboard is computed from raw geo-tagged gig data - zero APIs, zero dependencies.

One of the pages I'm proudest of on my DJ tour-date site isn't a tour date at all. It's a leaderboard: how many miles each tracked DJ travels between consecutive tour stops, ranked.

The whole thing runs on a JSON file, the haversine formula, and one rule about duplicate venues that looks trivial and isn't. No commercial geo API, no distance-matrix billing, no background jobs. Here's how it works, and the three places the naive version lies to you.

The data

Every artist in my catalog has a tour_dates array. Each gig that can be geo-tagged carries a latitude, a longitude, and a date. Some come in with lat/lng of exactly 0.0, 0.0 - the GPS equivalent of "somewhere."

That's guard-rail number one: a stop at exactly (0.0, 0.0) is not in the ocean southwest of Ghana. It's missing data. Skip it before it drags a route through the Gulf of Guinea.

if ($lat === 0.0 && $lng === 0.0) continue; // null island, not a venue

The distance: haversine, not hype

For the leg between consecutive stops, you can't just subtract coordinates - degrees aren't miles, and straight lines go through the planet. The standard fix is the haversine formula, which computes great-circle distance on a sphere. It's older than the railways, it fits in a handful of lines of PHP, and for tour-routing purposes it's accurate to well under 1%:

$t = deg2rad($q['lat'] - $p['lat']);
$u = deg2rad($q['lng'] - $p['lng']);
$h = sin($t/2) ** 2
   + cos(deg2rad($p['lat'])) * cos(deg2rad($q['lat'])) * sin($u/2) ** 2;
$km += 6371 * 2 * asin(min(1.0, sqrt($h)));

That min(1.0, ...) looks paranoid. It isn't - floating-point jitter can push the haversine term a hair above 1 for near-identical points, and asin of that is NaN. One guard, or the whole leaderboard prints NaN mi and you file a bug report against trigonometry.

Kilometers convert to miles once, at the end, on the total. Round the total, never the legs - rounding per leg compounds error across a thirty-stop tour.

The trap: same venue, next day

Here's where the naive version lies. A DJ plays Friday and Saturday at the same festival. Coordinates: identical. The naive route computes a "leg" of roughly zero miles - fine - but at different venues in the same complex, or venues tagged slightly differently, you get phantom short legs that add up to fake hundreds of miles over a season.

The rule that held up: skip a leg when venue and city both match, case-insensitively, trimmed:

if (strcasecmp(trim($p['venue']), trim($q['venue'])) === 0
 && strcasecmp(trim($p['city']),  trim($q['city']))  === 0) { continue; }

Residencies still leak a little, because a venue's own billing changes night to night - but the leaderboard stopped rewarding whichever data pipeline writes venue names most inconsistently. Distance should measure travel, not data noise.

Ordering, legs, and honesty

Stops get sorted chronologically before any math - the route is first city to last city, and the count of legs (actual travel segments, not stops) is what keeps the numbers defensible. An artist with two stops and one leg is a valid row. An artist with too few geo-tagged stops gets skipped, and the skip is counted:

Computed from geo-tagged tour routes; artists with too few geo-tagged stops don't get ranked - and the leaderboard says so.

That sentence is a feature. The fastest way to poison a data project is to quietly fill gaps with guesses. Publish what you measured, count what you skipped, and the journalists who check your methodology find a methodology.

The performance part is boring, on purpose

One JSON file, one pass per artist, one sort per route, done. I haven't needed a caching layer for this yet, and I'd rather ship the honest version and add caching if the data grows than scaffold infrastructure for a leaderboard that fits in memory.

The payoff

The leaderboard is now the hook for my outreach to music-industry press: it carries its own schema (a Dataset, so machines can cite the methodology and not just the page), it lives at thedjcalendar.com/miles, and it answers a question nobody else was answering in public - who actually lives on an airplane?

Total dependencies: zero. Total cost: zero. Total math: one formula you last saw in a navigation textbook and one paranoia guard.

If your favorite DJ's mileage looks wrong - good. Check the venue names. That's usually where the truth leaks.

Fun Coupons · one engineering story a month, straight from production - no spam, just scars. Get the next one →

More from the workshop

All writingEvery article, on this domain. Open source11 production-extracted projects.