Skip to content
andrew.dunn.dev

Patinate

Source Live demo

Patinate renders Strava rides over an OpenStreetMap basemap as an SVG plate. Each ride is one low-alpha stroke; where rides overlap, the strokes add up to brighter colour, so the streets ridden often come forward like patina on handled metal. The plate prints as a poster or embeds as a page hero.

A rendered plate in the cycle_heat theme, framed as a printed plate with its technical caption

One Rust binary carries the whole path, so a pipeline job can run it with nothing else installed.

NETWORKONE RUST BINARYsyncbasemap—anonymizeSOURCESStrava + OSMrides + basemapCACHESQLiteactivities, watermarkPRIVACYhidden zonesalted clipRENDERrendertheme JSONOUTPUTSVG plateposter or hero
From Strava rides to a themed plate
  • sync pulls activities through Strava OAuth into a SQLite cache. The watermark advances only after every upsert lands, so a partial run resumes.
  • fetch-osm geocodes a city through Nominatim and pulls the basemap from Overpass, over https only.
  • The clipped activity is the only type the renderer accepts.
  • --anonymize drops rider, bike, year and type attributes from the SVG and cuts its description to city and country; --web is the embed preset.
  • Themes are JSON. noir_heat, blueprint_heat, warm_beige, cycle_heat, newsprint and verdigris ship inside the binary; --themes-dir adds or overrides by name.
  • Configuration is TOML plus PATINATE_* environment variables, so a pipeline sets it without a file.

Clipping at an exact circle around home does not hide it: every cut end lies on that circle, and a circle fit over the render recovers the centre to within metres.

home diskHIDDEN ZONEcentre drawn from the salt,within the offset of homeradius = r + offset + 1 mcut ends trimmed 0..r further,so they scatter in a banda circle fit lands on the zonecentre, not on home
The hidden zone offset from home
  • The zone’s centre is drawn once from a secret salt, uniform over a disk of the configured offset around home. Its radius is the privacy radius plus the offset plus a metre, so home stays strictly inside.
  • Each cut end is trimmed a further distance along the track, seeded by salt, activity and crossing, so the ends scatter in a band.
  • All randomness is SHA-256 over the salt: repeated renders are identical and cannot be averaged against each other. A positive radius with no salt refuses to run.
  • Tracks still converge toward the gap. The zone hides the address, not the neighbourhood.

Single rider only. Cities across the antimeridian are not handled, and the basemap renders in full with no viewport culling. MIT licensed.