Pacific Coast Bike Tour

The "LA to LA" ride: Los Altos to Los Angeles.

Colophon

How this site was built

Eight GPX tracks go in; eight day pages come out, each with an elevation profile, a map, live conditions and a list of places to stop. Everything below is either free, keyless, or running on a public token. This page exists so that anyone wondering where a number came from can go and check it.

The shape of it

It is a static site with a hand-rolled generator: two Python scripts, some Jinja templates, and one dependency. build_data.py reads the GPX tracks and writes JSON; generate_pages.py renders that JSON through the templates into plain HTML. There is no framework, no bundler, and no package.json. The published site is HTML, CSS, about 640 lines of vanilla JavaScript, and a single PHP file.

That last file is the only server-side code, and it exists for one specific reason, explained below.

Routes

Most days come from GPX tracks drawn by hand in Ride with GPS or exported from Garmin Connect. Days 3–5 were re-routed in August 2026 when a fire closed the Big Sur coast; those replacements were generated with BRouter, an open routing engine that runs on OpenStreetMap data and lets you pick a cycling profile rather than accepting whatever a car router thinks a bike wants. The original coast tracks are kept rather than deleted, in case the road reopens.

The profile choice matters more than it sounds. Routing Day 5 with a general touring profile produced a line with an unpaved section of Santa Rita Road; a road-cycling profile avoided it and found Highway 41 instead, for a third of a mile more and 280 ft less climbing.

Distance, elevation and climbs

All computed from the GPX in pure Python, no library:

Every elevation chart is drawn on the same vertical scale, so a flat day looks flat. Charts that auto-fit their own range make every day look equally mountainous, which is worse than useless for comparing them.

Maps

Leaflet with raster tiles from Mapbox. Three basemaps, because the two things this route is judged on are climbing and shade: Elevation (contours and hillshade, the default), Vegetation (satellite, where tree cover is directly visible), and Plain for reading road names.

Clicking any point hands that coordinate to Google Maps, Apple Maps or Street View. Neither Google nor Apple lets a third-party site embed an interactive map without a paid, billed key, so handing the coordinate off is the honest substitute.

Stops, water and towns

Everything in the Stops along the way lists comes from OpenStreetMap, queried through the Overpass API and written into the GPX files as waypoints so they also appear on a head unit.

Two things had to be handled carefully. A Garmin course has a capped course-point budget shared between turn cues and waypoints, and going over truncates it silently — losing turn prompts, which matter more than a café. So POIs are thinned per category, with priorities: a supermarket survives over the fast-food place next door, and water is never thinned.

And amenity=toilets says a toilet exists, not that a passing cyclist may use it. Auditing the route turned up private units, permit-only beach facilities, and six separate toilet nodes inside a college campus. Restricted access and school, church or club operators are now filtered out; customers-only stops are kept and labelled, because buying a coffee is a fair trade and a café is in practice the most reliable restroom on any of these roads.

Where OpenStreetMap simply has nothing — Los Olivos has well-known places to eat and none of them are tagged as anything the query matches — a short hand-maintained list fills the gap. Waypoints say which they are, because an OSM-derived point may be stale or mistagged while a hand-picked one was chosen deliberately.

Shade

The most involved calculation, and the one that settled an actual decision. Two mechanisms, measured separately:

Sun position comes from the NOAA solar algorithm, implemented directly; ground elevation from Open-Meteo's elevation API, which is free and needs no key.

At 3 pm in early September the sun is still about 49° up, so terrain shade comes out at 0% almost everywhere — which is a real result, not a broken model. Sweeping the hour confirms it behaves: by 6 pm terrain shade on Day 3 is 18%, and by 7 pm it is 50%. During riding hours on these routes, tree cover is the only shade on offer.

Live conditions

Two feeds, fetched differently for one reason: whether a key is involved.

Weather comes straight from the National Weather Service API. No key, CORS open, so the browser can call it directly. It takes three hops: the grid point for the coordinate, that grid's forecast, then the nearest station's latest observation — the forecast payload carries no humidity.

Air quality comes from AirNow, and cannot be fetched the same way. Unlike a Mapbox public token, an AirNow key cannot be restricted to a domain, and its rate limit is enforced per key. Publish it and anyone can drain the hourly quota, after which AirNow returns nothing until the next hour — the panel would go blank, plausibly right when someone on the road is checking for smoke. So the key stays on the server behind a small PHP proxy, which caches for ten minutes, refuses coordinates outside this route so it cannot be used as a free general-purpose proxy, and serves a stale-but-labelled reading rather than nothing when the upstream is rate-limiting.

Every air quality reading names the reporting station and its distance. That is deliberate: the network is sparse inland — Monterey has no station within 75 miles — and smoke varies sharply over short distances. A bare number would imply a precision the reading does not have.

What this does not tell you

OpenStreetMap is volunteer-mapped, and thin in rural California. A day showing no water means nobody has mapped any, not that there is none; Day 4 comes back with zero mapped water across 62 miles, which is a reason to load the support car rather than a survey result. The same caveat applies to canopy: comparing two routes in the same region is far more trustworthy than any single absolute figure.

The route notes mix measured figures with rider knowledge. Distances, gradients and percentages are all traceable to the data above. Judgements about traffic and road surface are not — OpenStreetMap has no shoulder, speed limit or width tag on most of these roads — so those come from people who have ridden them, and are marked as unchecked where nobody has.

Source

All of it is on GitHub: scottgruber/pacific-coast-2026. The interesting parts, if you want to check the working:

The two algorithms worth reading up on rather than taking on trust: Ramer–Douglas–Peucker for line simplification, and solar position for the shade model — the implementation here follows NOAA’s solar calculator equations.

Credits