The Roman Roads Research Association (RRRA) has released Digital Britannia v1,
a GeoPackage of every engineered Roman road, fort, camp, settlement and
milestone in Britain. I wanted a web map for exploring it properly: against
LiDAR, against historic Ordnance Survey maps, and against other datasets.
So I handed Claude Code a brief and watched it build one.
- Live map: https://adrianco.github.io/romanroads/
- Code, decision records and errata: https://github.com/adrianco/romanroads
It's an unofficial viewer. The data is © Roman Roads Research Association /
Mike Haken, CC BY-NC 4.0.
The brief
I wrote the brief in a Claude session discussion about what I wanted on my phone, then on my desktop created a new GitHub repo, and pasted the brief in to Claude Code:
Pass 1: build a web map viewer for the RRRA's Digital Britannia v1 Pass 2: Pass 3: a list of candidate underlays and overlays: NLS historic maps,The full brief, as pasted
GeoPackage (about 3 MB). Non-commercial; credit the RRRA and Mike Haken; mark it
unofficial.
Segment Confidence from 3 =
certain ±30 m down to 0 = no evidence), military installations, temporary
camps, settlements, milestones, milecastles, and walls.Main site type, which has inconsistent capitalisation, and one
garbled settlement TYPE. Follow the author's QGIS styles in the
layer_styles table.
--no-line-simplification,
zoom 18+).
aerial imagery, Historic England aerial mapping, scheduled monuments,
Canmore/Coflein, BGS geology, 3D terrain, DARE, AWMC, Pleiades, OS maps.
"I'd make NLS 6-inch, aerial imagery and LiDAR the core three, with the rest
optional." Also: these come from general knowledge; verify each endpoint and
licence before wiring it in.
Mid-build, I also asked for architecture decision records (ADRs) for every
decision, and an errata document I could send back to the RRRA.
That last instruction, verify before wiring it in, shaped the whole build.
More on that below.
Timeline
| Time | Commit | What landed |
|---|---|---|
| 14:12 | — | Repo cloned |
| 14:43 | 6a111df |
Data pipeline, viewer, 11 ADRs, errata, browser-tested |
| 14:52 | f77ba37 |
Scottish LiDAR; documented why Wales isn't possible |
| 15:04 | fff4fc6 |
Optional extras: DARE, geology, 3D, heritage overlays |
| 15:10 | 3ae0722 |
Itiner-e comparison roads |
| 15:25 | b141c8e |
Datum fix for Itiner-e (see below) |
| 15:28 | ca52875 |
Data no longer waits for slow tiles |
Step 1: full-fidelity data, and a single page after all
The pipeline (scripts/process.py) reprojects each layer from British National
Grid to WGS84. It keeps every vertex and only rounds coordinates to 7 dp
(about 1 cm). The road accuracies are stated as ±30 m and ±200 m, and
simplifying would quietly degrade them.
The result is 4.19 MiB of GeoJSON, under the 5 MB line. So there's no
PMTiles and no tippecanoe, just one index.html on GitHub Pages fetching seven
files.
Cleaning the categories turned up more than the brief anticipated:
-
The garbled value:
Military vicus - size uncertaMilitary vicus - size uncertainin -
Capitalisation:
Legionary Fortress(1 record) vsLegionary fortress(11) -
Glued NULLs: the literal text
NULLstuck to the front of values, which turned one Heritage Gateway link intoNULLhttps://… - Whitespace: 592 values padded with spaces
-
Road numbers: 239 distinct non-standard forms (
220?,1b & 1c,85-linkvs85 link)
Every issue went into an errata document with counts and examples, written
for the RRRA's next release. The viewer applies non-destructive fixes and
records each one.
Step 2: the viewer
MapLibre GL JS, with no build step and no API keys. Roads follow the author's
own QGIS style: solid for certain, dashed for probable, dotted for conjectured,
each with a white halo so it stays readable over the underlays. Click a road and
its whole Margary route lights up.
Search takes place names, Roman names and road numbers:
There's also a period slider for military sites (131 Claudio-Neronian sites,
peaking at 288 Flavian), popups with every attribute, and a bottom-sheet layout
on phones.
"Verify before wiring it in" paid off, repeatedly
Several services looked right from a quick check and weren't. Each was only
caught by looking at the actual output.
CARTO base maps returned HTTP 200 and were useless. A curl check passed.
In the browser, every tile said this:
The 2 KB watermark is what CARTO now serves to keyless requests. The base maps
switched to Esri's Light and Dark Gray Canvas, and the lesson went into an ADR:
a status code doesn't verify a tile service; you have to look at the tile.
The NLS "6-inch first edition" covers Scotland only. Every England tile
returned 404. Listing the S3 bucket and reading each tileset's metadata.json
showed that 6inchfirst stops at 54.6°N. The seamless GB layer is
6inchsecond. Here it is at Housesteads, where the RRRA Wall, Vallum and
Military Way line up with "Borcovicus Roman Station" on the OS sheet:
Label fonts: one public glyph endpoint returned an HTML page; two others
returned 404. One worked.
LiDAR across three countries: three different answers
England was straightforward. The Environment Agency's 1 m composite
hillshade WMS supports Web Mercator, so it drops straight into MapLibre.
Scotland publishes LiDAR DTMs but no hillshade. Its layers only come
with colour-ramp elevation styles, which hide subtle earthworks:
The portal runs GeoServer, though, which accepts an inline style. Passing a
ShadedRelief symbolizer via SLD_BODY makes the server compute the
hillshade per request, here at the Antonine Wall:
One request stacks 11 survey layers (phases 1–6, the National LiDAR Programme
and four Historic Environment Scotland surveys), with newer data drawn on top.
That's about 4.9 KB of URL, safely under common GET limits. Here it is at
Inchtuthil legionary fortress:
Wales: not possible yet. The Welsh hillshades exist, including a
multi-directional one, but they aren't in DataMapWales's public WMS. The public
cloud-optimised GeoTIFFs (1–22 GB) have no CORS rule; the preflight returns
403 CORS not enabled. No web page can read them. The ADR records exactly what
would unblock it.
Optional extras, and what didn't make it
Added after the same checks:
![]() |
![]() |
| DARE Roman Empire map: Latin names line up with the RRRA forts | BGS geology at Bath (Aquae Sulis) |
![]() |
![]() |
| 3D terrain along Hadrian's Wall | Historic England's Roman-period aerial mapping (a cropmark road ditch) and scheduled monuments |
Also added: scheduled monuments for Scotland (HES) and Wales (Cadw), and
Pleiades links for sites with a Roman name.
Rejected, with reasons recorded:
- AWMC/CAWM ancient-world tiles: http only, so blocked on an https page.
- Canmore: its WMS gives no titles, abstract or licence.
- Coflein: no service.
- OS Maps API and Bing: need keys.
The datum bug in someone else's data
The last addition was Itiner-e, a CC BY 4.0 dataset of roads across the
whole Roman Empire, as a comparison layer. Clipped to Britain (after a box
caught eight roads on the Normandy coast), it's 1,371 segments.
Then Adrian noticed something while looking around Dorchester: the Itiner-e roads look consistently offset.
A route disagreement wanders. A consistent offset points to a coordinate
system problem. Claude fitted the single best shift between the two datasets,
region by region, and compared it with the error you'd get by treating
OSGB36 latitude/longitude as if it were WGS84:
| Area | Best-fit correction (E, N) | Predicted OSGB36-as-WGS84 error |
|---|---|---|
| Dorchester | −90, +69 m | +92, −64 m |
| Rochester | −109, +59 | +118, −55 |
| High Cross | −102, +38 | +103, −41 |
| York | −111, +12 | +105, −22 |
| Hadrian's Wall | −94, +9 | +96, −8 |
It's a mirror image, right down to the north–south part shrinking towards the
Wall, the way the datum difference does. The offset showed up in all three
contributor groups.
Before blaming Itiner-e, Claude checked its own pipeline. The same signature
would appear if our British National Grid conversion had skipped the datum
shift. PROJ uses a 2 m-accuracy OSGB36→WGS84 transformation, and the Greenwich
Airy transit circle converts to 0.0016° W, not 0.0000°. So the RRRA layer was
right.
The fix is principled, not a fitted nudge: reinterpret Itiner-e's British
coordinates as OSGB36 (EPSG:4277) and convert them properly.
The median gap fell from 81 m to 17 m, and the remaining best-fit shift is
+1, −2 m. That beats the best constant shift (24 m) because the datum
correction varies across the country the way the error does. It's written up in
ADR 0013, ready to send to the Itiner-e team.
Bugs found by actually using it
Claude tested everything in a real browser, and that's where most of the bugs
surfaced:
-
Inscribed spellings: Roman names are stored as inscribed (
VERCOVICIVM), so searching "Vercovicium" found nothing. A small V→U normaliser now feeds both search and the Pleiades links. -
Silent layer failure: a MapLibre zoom expression was nested inside a
match. MapLibre reports that as a warning event, not an exception, so the layer silently didn't exist while the panel said it had loaded. -
Data held back by slow tiles: a link to a Scottish view showed no
roads for 30+ seconds. The data fetch sat inside MapLibre's
loadevent, which waits for every visible tile, and the Scottish hillshade is rendered per request. The fix was to start fetching immediately and add layers onstyle.load. Now the roads appear in about 4 s while the hillshade fills in:
- Claims corrected before they shipped: some of the agent's own statements were checked and fixed. An errata line said "confirmed" before anything had been counted (it was then counted: 7 of 517). An ADR named a licence the service never states.
-
A wrong diagnosis, caught the next day: the LiDAR hillshade sometimes had
blank patches. While capturing screenshots, Claude found tile requests with
status 0. It put them down to requests cancelled by view changes, plus
brightness patchwork between EA surveys, and withdrew a retry fix it had
floated. That was wrong. The next day, working on an unrelated issue, a
fresh load with no view changes still lost 11 of 30 tiles. Measuring
properly showed the real cause:
- The EA server fails some tiles transiently from browsers: 7 of 29 stayed broken on the live site, and bursts of 30 lost 0–8.
- Every failed tile succeeds when requested again.
- MapLibre never retries a failed raster tile, so each failure stayed a hole.
The fix routes the LiDAR sources through a small retry:// protocol with
back-off. It was verified by forcing the first attempt of every third tile
to fail: 14 of 42 were forced, and all 42 loaded. The lesson: a plausible
explanation isn't evidence. The status-0 requests should have been tested,
not explained away.
Day 2: fixing what I found by using it
After a day of using the live map, Adrian filed two GitHub issues and gave Claude
Code a three-word prompt: check the issues. It read them, did the one it
could start straight away, and asked before the one that needed a download.
| Time | Commit | What landed |
|---|---|---|
| 08:55 | — | Issue #2 opened; work starts |
| 09:00 | e350dd2 |
3D camera sliders (closes #2) |
| 10:07 | 5924189 |
Pleiades places matched at build time (closes #1) |
| 10:17 | 02d30bb |
LiDAR tile retries (the wrong diagnosis above) |
Issue #2: the 3D view needed controls
Tilting with the mouse was the only control, and the right-drag gesture didn't
work for me. Ticking 3D terrain now shows four sliders: Tilt,
Rotation, Altitude and Relief (vertical exaggeration). They follow
the camera when you move the map, and switching 3D off returns a flat,
north-up view. MapLibre caps tilt at 60° by default; that's now raised to 85°.
Issue #1: the Pleiades links didn't work
The popups linked to a Pleiades search for each site's Roman name, and they
often missed. Dorchester's is a clear case: the dataset spells names as inscribed (DVROVARIA),
Pleiades uses the standard form (Durnovaria), and its search only matches
exact words. My issue sketched the fix: download the Pleiades open data, find
candidate places within 2 km of each site, fuzzy-match the names, and link
straight to the best match.
The first run matched 515 sites, but the review step is where it got
interesting:
-
The biggest towns missed. Pleiades has "Col. Glevum" and the dataset has
GLEVVM. Pleiades has plain "Venta", while the dataset adds the tribe:VENTA BELGARVM. Itinerary forms differ in case ending too (BOMIVMvs Bomio). Scoring the best distinctive word, with Latin endings stemmed, fixed Gloucester, Colchester, Lincoln, Winchester, Wroxeter, Canterbury and Chichester. -
Some links went to the wrong part of a site. The fort at Chester linked
to "Roman amphitheater at Deva", and Caerwent to its town walls: the
monuments matched the name just as well, and sat nearer. Ties now go to a
town or fort (from Pleiades'
featureTypes) over a monument inside it. - Modern-name matching needed guard rails. Generic words ("Roman", "fort", "hill") would have paired any two forts within 2 km, so they're stripped before comparing. Popups label these links "matched by modern place name".
The result: 533 sites link straight to their Pleiades place (219 by Roman
name, 314 by modern name). The other 466 fall back to a search and are listed
in a CSV for manual review; only 17 of them have a real Roman name. The
matches are committed, so a fresh clone rebuilds identically without the
download. Roman names now display in title case, and searching "Durnovaria"
finds Dorchester:
What turned up along the way
-
67 road segments (1.9%) have no line at all. They have full attributes
but a
NULLgeometry in the GeoPackage, including stretches of the Fosse Way, Dere Street, Sarn Helen and the Military Way. They had never been on the map, and they broke "Zoom to whole route": choosing RR13 in search threw an error. -
More errata for the RRRA: the same place spelled differently in two
layers (
PONS AELIVS/PONS AELIUS), placeholder text where a name should be empty (29 "unknown", and an "unkonwn"), 3 blank settlement rows, and 2 duplicated camps. - The LiDAR holes, described in the previous section: the bug I'd explained away on day one, found and fixed properly.
- A way back to the repo. The top of the panel now has Star repo, Issues and New issue buttons that open GitHub in a new tab, so anyone using the map can report a problem the same way I did.
The documentation is a deliverable too
- 14 ADRs. Each records the context, the decision and the evidence (curl output, tile checks, measurements), including the decisions that changed during the build.
- ERRATA.md for the RRRA: 27 issues, each with a severity, a count and a suggested fix.
Build time and token usage
| Day 1: the build | Day 2: the issues | |
|---|---|---|
| Wall-clock | ~76 min (14:12–15:28), including my pauses to revise the brief | ~81 min (08:55–10:17), including waiting for my OK on a download |
| Commits | 7 | 3 |
| Share of usage limits | ~4% of the 5-hour and weekly limits | ~4% of the 5-hour, ~2% of the weekly (a new week) |
| Context window at the end | ~503k of 1M tokens (50%) | ~676k (68%), the same conversation |
| Extra (pay-as-you-go) spend | $0 | $0 |
Models: Claude Opus 4.8 for the initial build, then Opus 5.5 from before the
first commit.
The context figure is what the conversation held at the end. It isn't a
cumulative total: each turn re-reads the context, mostly from cache, so the
tokens actually processed are several times higher. The tooling didn't expose
that total.
What's next
- Wales LiDAR, if DataMapWales publishes the hillshade in its WMS or enables CORS on its COGs.
- Report the datum issue to the Itiner-e team, and send the errata to the RRRA.
- Review the 466 unmatched Pleiades sites. Some need only a wider radius: Petuaria, at Brough, has no Pleiades place within 2 km.
- Pin the remaining endpoints, or self-host what can be self-hosted. The label glyphs come from MapLibre's demo server, which has no availability guarantee.
Credits
- Digital Britannia v1: © Roman Roads Research Association / Mike Haken, CC BY-NC 4.0
- LiDAR, England: contains Environment Agency information © Environment Agency and/or database right
- LiDAR, Scotland: © Crown copyright Scottish Government, SEPA, Fugro, Historic Environment Scotland and partners, via the Scottish Remote Sensing Portal (OGL)
- Historic maps: National Library of Scotland
- Roman Empire map: DARE, Johan Åhlfeldt (CC BY 4.0)
- Places: Pleiades, © the contributors (CC BY 3.0)
- Comparison roads: Itiner-e 2024, Brughmans, de Soto, Pažout & Bjerregaard Vahlstrup (CC BY 4.0)
- Heritage, England: © Historic England (OGL)
- Heritage, Scotland: © Historic Environment Scotland (OGL)
- Heritage, Wales: Cadw (OGL)
- Geology: contains British Geological Survey materials © UKRI
- Imagery and base maps: © Esri and partners
- Terrain: Mapzen terrain tiles on AWS Open Data



















Top comments (0)