How 84 racecourses were measured
84 measured racecourses in the open topology dataset — and not one figure copied from a course's own materials. This page documents the entire method: where the geometry comes from, how turns and climb are derived, what we filter out and why, and the rules that decide which course record a measurement belongs to. It's the long answer to a short claim: measured, not quoted.
The sources
Two open datasets underpin everything. OpenStreetMap supplies the track geometry: most UK & Irish racecourses have their racing surface mapped as traceable ways. Copernicus EU-DEM supplies the terrain: a 25 m-resolution digital elevation model of Europe, which we sample along each circuit. Three public services sit in front of them: Nominatim for geocoding, Overpass for querying OSM geometry, and OpenTopoData for EU-DEM lookups — all rate-limit-respecting, all with a descriptive User-Agent. Both underlying datasets are open, which is why the derived dataset is open too — published under ODbL with attribution to both sources (see data licences).
Using open geodata is a deliberate constraint. Published circuit descriptions vary in precision, provenance and copyright status; a measurement pipeline over open sources is reproducible by anyone, is licence-clean, and fails in inspectable ways.
Step 1 — Finding each course
Each venue is geocoded by name through Nominatim, then track geometry is extracted from OpenStreetMap via Overpass in three tiers, stopping at the first that yields candidates:
| Tier | Strategy |
|---|---|
| Direct | The geocode hit is itself a mapped track — use its geometry directly. |
| Venue area | The hit is the racecourse grounds — query for track-tagged ways and relations within that polygon. This bounds the search and finds tracks mapped as relations. |
| Radius | Last resort: search for track candidates within 2.5 km of the venue point. |
What the services actually return, using Chester as the running example. Geocoding (annotations mark the fields we read):
GET https://nominatim.openstreetmap.org/search?q=Chester+Racecourse&countrycodes=gb&format=jsonv2&limit=1
{
"licence": "Data © OpenStreetMap contributors, ODbL 1.0 ...",
"osm_type": "way", ← the OSM object to extract from…
"osm_id": 251213860, ← …by this id
"lat": "53.1870253", ← venue centre (radius-tier fallback)
"lon": "-2.8998074",
"category": "leisure",
"type": "sports_centre",
"name": "Chester Racecourse"
}The venue-area tier then asks Overpass for track-tagged geometry inside that object — the tag filters are the query:
POST https://overpass-api.de/api/interpreter
[out:json][timeout:60];
way(251213860);map_to_area->.venue;
(way(area.venue)["leisure"~"^(track|racetrack)$"];
way(area.venue)["sport"="horse_racing"];
relation(area.venue)["leisure"~"^(track|racetrack)$"];
relation(area.venue)["sport"="horse_racing"];);
out geom;
{ "elements": [
{ "type": "way",
"id": 507383600, ← stored per course for refresh runs
"geometry": [ ← the track centreline, point by point
{ "lat": 53.1889519, "lon": -2.8992437 },
{ "lat": 53.1889837, "lon": -2.8993824 },
…86 more points… ] },
…more ways… ] }Venues the geocoder misses get a manually pinned coordinate. Every extraction produces a per-course review report; nothing enters the dataset without a human looking at what was traced.
Step 2 — Building the circuit
OpenStreetMap rarely maps a racecourse as one tidy loop — tracks arrive as fragments. The stitcher joins fragments whose endpoints sit within 15 m of each other, regardless of drawing direction, and returns candidate circuits ordered by length. By default the longest candidate of at least 1,200 m is taken; where a venue's gallops, training loops or perimeter paths stitch longer than the racecourse proper, a reviewer overrides the choice by supplying the published circuit length, and the candidate closest to it wins.
That override matters. Chester — famously the tightest circuit in Britain — first measured 2,096 m because the default took an outer perimeter loop; the candidate list also contained a 1,658 m inner circuit, within about 1% of the published figure. The reviewed pick corrected it, and the correction is recorded. When a measurement looks wrong, the candidate trail shows exactly why.
To be explicit about the boundary: published figures are used only to choose between traced candidates — never as output values. Every number in the dataset is computed from the selected trace; the published length just points the reviewer at the right circuit.
Step 3 — Turn geometry
The circuit line is resampled to evenly spaced 10 m steps, and the change of compass bearing per step — smoothed over a three-step window — becomes a curvature signal. Where the track bends more than 1.5° per step (roughly, a radius under 380 m), it is turning; sustained turning regions that sweep at least 25° become turns. Adjacent same-direction arcs separated by less than 60 m merge into one bend, because a long racecourse turn often reads as several sub-arcs. Each turn's radius follows from its arc length and swept angle, and severity is classified by radius: Tight under 150 m, Moderate under 300 m, Sweeping otherwise.
turning where |κ| ≥ 1.5°/step (≈ radius < 380 m)
radius r = arc length ÷ swept angle in radians
Worked from live data — Chester's longest turn sweeps 88° over 370 m:
Every value below is served by the open endpoint — startM, lengthM, angleDeg, radiusM, severity. Distance runs from the survey origin; negative angles are left-hand.
A sharp eye will notice the measured turns sweep 282° in total — less than the 360° a closed loop must turn through. The remainder is gentle curvature below the 1.5°-per-step threshold, or arcs sweeping less than 25°: deliberately not reported as turns, but still turning. turns[] is the set of distinct bends, not a decomposition of the full circuit.
One filter earns special mention. Where OpenStreetMap centrelines double back on themselves — chute junctions, out-and-back spurs — the raw signal reports hairpins of 10–50 m radius that no horse could take at racing speed: at roughly 15 m/s and a comfortable lateral acceleration near 4 m/s², the physical floor is about 56 m. Turns with radius under 60 m are therefore rejected as trace artifacts, both before and after arc merging. Before this filter existed, nearly a third of all detected "turns" were artifacts; the tightest genuine sustained bend in the dataset now measures around 70 m — tight, but rideable.
Step 4 — Elevation
Elevation is sampled from EU-DEM every 25 m around the circuit. Raw DEM values carry sample noise that would fabricate climb on a flat course, so the series is smoothed with a five-sample (~125 m) moving average, and climb is counted with 1 m hysteresis: only confirmed rises of at least a metre between local extremes register. From that one series come three published figures — total climb per circuit, elevation range (lowest to highest point), and the undulation index (metres of confirmed elevation change per kilometre of circuit) — plus the elevation profile itself, the [distanceM, elevationM] curve drawn on every course profile.
One elevation lookup, verbatim:
GET https://api.opentopodata.org/v1/eudem25m?locations=53.1893,-2.8908|53.1901,-2.8934
{ "results": [
{ "dataset": "eudem25m",
"elevation": 28.66, ← metres above sea level — the only field we read
"location": { "lat": 53.1893, "lng": -2.8908 } },
{ "dataset": "eudem25m",
"elevation": 33.09,
"location": { "lat": 53.1901, "lng": -2.8934 } } ],
"status": "OK" }
(batched 100 points per request, one request per ~1.1 s)climb = Σ rises ≥ 1 m between local extremes (hysteresis — noise never counts)
undulation index = climb ÷ circuit length in km
elevationProfileFrom this one series the pipeline publishes: total climb 8.2 m (confirmed rises only, after smoothing and hysteresis), elevation range 7.4 m, and the undulation index 4.95 m/km = 8.2 m ÷ 1.66 km of circuit. Note that total climb can exceed the elevation range: climb sums every confirmed rise around the circuit, while range is the single lowest-to-highest difference.
One circuit, one record
Racing data keeps multiple course records for one physical venue: turf and all-weather eras, renamed venues (Limerick Junction became Tipperary in 1986; Cork raced as Mallow), direction-change eras. The first measurement sweep worked venue-by-venue, so both records of such a pair received identical geometry — honest-looking numbers, silently duplicated. The published rule is now strict: one measured circuit belongs to exactly one course record — the record carrying the venue's most recent racing. The other records remain in the racing data but claim no measurement, and they stay unmeasured until the day their track gets its own verified trace. We would rather publish a smaller dataset than attribute one measurement twice.
The dataset also includes courses that no longer race — Towcester closed in 2018 — because the racing corpus reaches back to 1988 and those circuits still explain historical form.
Known limitations
| Limitation | Detail |
|---|---|
| Centreline, not racing line | Measurements follow the mapped track centreline. Horses race a line of their own; circuit lengths sit close to, but not exactly on, published figures. |
| Survey origin | Positions are metres from where the traced circuit begins, not from the winning post. Comparable within a course; not aligned across courses. |
| DEM resolution | EU-DEM is a 25 m grid — fine terrain features narrower than that are invisible, which is why climb is counted with smoothing and hysteresis. |
| OpenStreetMap currency | Traces reflect OSM as of measurement. Where OSM edits break a stored circuit, the refresh pipeline refuses to write rather than guess — Kelso is in this state: its original metrics stand, but it carries no elevation curve until its track is re-mapped. |
| Shared-site variants | Some venues' second course records (the Rowley Mile at Newmarket, Kempton's jumps course) are unmeasured pending their own traces — see the attribution rule. |
| Venues without usable OSM geometry | The Curragh, Brighton and Laytown are not yet measured — their tracks can't currently be traced from OpenStreetMap (Laytown is raced on a beach, so possibly never). If you improve the mapping, the pipeline will pick them up. |
The full arithmetic, so nobody has to reconstruct it: 90 course records raced in the twelve months to July 2026. The attribution rule assigns five of those to sibling records at the same venue, and three venues (the Curragh, Brighton, Laytown) can't yet be traced. The dataset holds 84 measured courses — including closed and renamed ones back to 1988 — of which all but Kelso carry an elevation curve.
Keeping it current
Re-measurement never starts from scratch: a refresh pass re-fetches each course's stored OpenStreetMap ways by id, re-stitches, and only accepts a circuit within 15% of the reviewed length — so OSM edits can update a measurement but can't silently swap the track being measured. The dataset version (e.g. v2026.07) travels with the data, in the API response and the download's data dictionary alike.
The result of all of this is free: every measurement on the open dataset page, as league tables, a zip download and an open API endpoint, under ODbL.
