#19: Road refs: derive ref_class at import; pick the primary ref from multi-number labels #54
No reviewers
Labels
No labels
claude-wip
map-report
map:is
map:se
needs-review
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
art/ismap!54
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "night/issue-19-ref-class"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #19
Issue #19 — Road refs: derive ref_class at import; pick the primary ref from multi-number labels
Closes #19 (absorbs #39, merged into it 2026-08-04).
What changed
Road-number semantics are decided at import, next to each country's ref mapping in
conf/*.yaml, instead of by text-shape predicates in the style. A sharedref_classenum (
european/national/regional/local, declared inconf/common.yamlenums:) is derived per country in theroadssource query and carried through thecascade into the tiles (
map_roads_<z>_<cc>+zxy_map_roads). The style'sroad_shieldslayer now matchesref_classonly — green/blue/yellow sprites foreuropean/national/regional, no shield for
localor NULL — and contains noper-country expression at all (the old
["get","country"]match is gone).The ladder is phrased as a feature-type predicate ("is this ref an E-route number / a
national number / a county code"), per the maintainer's directive, so a fourth country
slots in by writing its own
CASEand reuses the same enum and style rule.ref_class per country (the derivations in conf/*.yaml)
semap.yaml):Vagnummer_Europavag <> 0→ european (the 11 SwedishE-roads: E4 E6 E10 E12 E14 E16 E18 E20 E22 E45 E65);
Vagnummer_Huvudnummer_Vard1–2 digits → national (riksväg 1–99); 3–4 digits → regional (länsväg). The
4-digit secondary länsvägar (111 450 segments at z14) are exactly what the old
"longer than 3 characters, not A/S/E" shield filter dropped — they are ordinary
numbered roads and now carry a shield. NULL Huvudnummer → NULL (no shield).
fimap.yaml): E-road present (DR_EUROOPPATIENRO_K) → european;TIENUMERO 1–2 digits → national (valtatie 1–39, kantatie 40–99); 3 digits →
regional (seututie 100–999); 4–5 digits → local (yhdystie 1000+, no shield);
no number → NULL.
plmap.yaml): derived from the chosen primary ref by shape:^E…→european;
^[AS]…or 1–2 digits → national (autostrada/ekspresowa/krajowa);3 digits → regional (droga wojewódzka); everything else — the 4+ digit powiat
codes like
5501W/240408W— → local, no shield.ismap.yaml): every numbered road is the one national network → constantnationalwhenvegnris set, NULL otherwise.Primary ref rule (Poland, absorbed #39)
NUMERDROGIcarries several numbers separated by|or;(7|E77,E77;S7;62).The primary ref is the first non-E number; a segment carrying only E numbers shows
the first. Justification: Poland signs the A/S/DK number on the main shield and the
E-route on a tab beneath it, so an E-number is never the primary label when a national/
regional number exists. Verified against real data:
;lists are rare (299 of 6 788 484 rows) and behave like|lists (2153E77|S7etc.); both order E first when present (
E77;S7;62→S7,7|E77→7).DKprefix (the old common.yaml comment'sDK92example was stale —removed with this change).
240481W;240486W;85(6 rows) picks the powiat code over DK85 —the source lists a county code first there; 6 rows out of 6.79M, noted not fixed.
631;85pick wojewódzka 631 over DK85 — same class of junction quirk.Verification numbers
Inherited DB was in a mixed state (the takeover brief said FI/PL had the new column
and SE/IS did not — the reverse was true): the
ref_classcolumn existed on everypartition (parent ALTER propagates), but only SE and IS had populated values
(16 329/58 302 IS, 690 328/3 397 786 SE), while FI (3 166 070) and PL (6 788 484) were
all-NULL — their live partitions were from the pre-ref_class import. A killed Finland
rerun had left empty
map_roads_*_fi_newshells (0 rows), which the importer'sschemaTables()DROP TABLE IF EXISTS … CASCADEremoves on rerun — no manual dropneeded; the live
map_roads_*tables were never touched.roadswith the new config(
./importer/importer -layer roads conf/fimap.yaml/conf/plmap.yaml); SE and ISwere already current and were verified instead of re-imported.
regional 519 088 (of 3 397 786; 111 450 of the regional are 4-digit länsvägar) / NULL
2 707 458. IS national 16 329 (of 58 302). FI european 24 268 (the E-road table has
24 269 rows; one link is filtered by the layer WHERE) / national 46 200 / regional
77 100 / local 335 138 / NULL 2 683 364 (of 3 166 070). PL: european 11 (E-only
segments) / national 124 463 (A/S + 1–2 digit krajowa) / regional 194 152 (3-digit
wojewódzka) / local 2 032 129 (powiat codes) / NULL 4 437 729 (of 6 788 484).
ref_class;rows with a road number have it non-NULL (0 ref_class-without-ref rows in SE).
zxy_map(14,…)): SE 4-digit länsväg1067in tile(8771,4604) carries
ref_class='regional'; unnumbered service rows carry NULL.PL
E77;S7;62concurrency north of Warsaw (tile 9128,5377) decodes to a singleprimary ref:
S7/62as national, powiat240607W/240637Was local. FIseututie
170→ regional, yhdystie40934→ local.make build,go test ./...(importer, mapsrv): pass.gl-style-validate:clean. Browser CDP check (queryRenderedFeatures on
road_shields, fresh mapsrvon :18080, zero console errors): SE european
E4+ national26+ 4-digitregional
1052/1350render; PL nationalS7/62/77/A4+ regional835render while 374
localfeatures in the same tiles render no shield; FIregional
170/178+ europeanE18; IS national (1,40, …).a NULL/missing
ref_classrenders no shield (filter admits only the threeshield classes — cannot crash or shield everything).
Files
conf/common.yaml—ref_classenum +roadscontract attrs.conf/semap.yaml,conf/fimap.yaml,conf/plmap.yaml,conf/ismap.yaml— per-countryderivation + comments.
html/style.json—road_shieldsfilter + sprite/text-colour on ref_class.mapsrv/sprite.go— comments updated to class semantics (shield-red retained,currently unassigned).
docs/schema.md,docs/style.md,docs/country-poland.md,docs/country-finland.md— mechanism + conventions (the Tornio E4 paragraph updated: both sides are now
european/green).
Left undone
E40-flagged SE segment (1 row, source quirk) renders as european — harmless,not special-cased.
noted, not fixed.
shield-redsprite is now unused by the style; kept as a superset, drop when sure.Road-number semantics are now decided at import, next to each country's mapping, instead of by text-shape predicates in the style. A shared ref_class enum (european/national/regional/local) is derived per country in conf/*.yaml: - SE: E-flagged numbers -> european; 1-2 digit riksvägar -> national; 3-4 digit länsvägar (incl. the 4-digit ones the old "longer than 3 chars, not A/S/E" shield filter dropped) -> regional. - FI: E-road present -> european; TIENUMERO 1-2 digits (valtatie/ kantatie) -> national; 3 digits (seututie) -> regional; 4+ digits (yhdystie) -> local. - PL: E-prefix -> european; A/S + 1-2 digit krajowa -> national; 3-digit wojewódzka -> regional; powiat codes ('5501W') -> local. - IS: every numbered road is the one national network -> national. Poland's multi-number NUMERDROGI ('E77;S7;62', '7|E77') is reduced to a single primary ref at import: the first non-E number, because Poland signs the A/S/DK number on the main shield and the E-route on a tab beneath it; a segment carrying only E numbers shows the first. The ';' separator (299 rows) is folded into the existing '|' handling. The style's road_shields layer now matches ref_class only: green/blue/ yellow sprites for european/national/regional, and no shield at all for local or NULL — no per-country predicates remain in the style. Checkpoint of the uncommitted work (SE and IS roads already re-imported with this schema; FI and PL re-imports still to run). Co-Authored-By: Hermes Agent <noreply@nousresearch.com>Review — #54 (ref_class at import)
The architecture is right. Moving the semantics next to each country's ref mapping and
leaving the style with a single
matchonref_classis a clear improvement over fourcountry-conditional text predicates, and
ref/ref_classbranch identically in everyconfig so they cannot disagree. Builds clean,
go vetandgo testpass, and the columnis imported for all four countries.
E numbers should win in Poland
This is the one to change before merging. Poland's rule — take the first non-E number,
fall back to an E number only when there is nothing else — produces this:
Eleven segments. A road signed both E30 and S2 becomes
national, so E67 is a greenshield in Sweden and a blue one the moment it crosses into Poland.
Following the national signing convention is usually the right call, but E numbers are
the exception on a map that is trying to be European: they are the only road identifier
that means the same thing in all four countries, and continuity across a border is
exactly what they are for. That is the thing this map can show and a national road atlas
cannot.
Worth noting that Poland is already the odd one out here rather than the consistent one —
the other two configs with E-roads both prefer E:
conf/semap.yaml:WHEN Vagnummer_Europavag <> 0 THEN 'E' || Vagnummer_Huvudnummer_Vardconf/fimap.yaml:WHEN e.EURTIENRO IS NOT NULL THEN 'E' || ...conf/plmap.yaml: first non-E, E only as fallbackSo this is not a Poland-vs-Sweden design tension, it is three configs disagreeing about
the same predicate. Inverting Poland's two
COALESCEsubselects harmonises them and makesthe shared
ref_classladder mean one thing everywhere. Thedocs/schema.mdandconf/plmap.yamlcomments explaining the "E is a secondary tab beneath it" reasoning needto flip with it.
btrimthe split partsstring_to_array(regexp_replace(numerdrogi, ';', '|', 'g'), '|')does not trim, so asource value like
'E77; S7'yields the element' S7'. It passes the non-E test, becomesthe ref verbatim, then fails
^[AS][0-9]+$in theref_classCASE and falls through tolocal— no shield at all. Real, not hypothetical:Four rows of 6.8M, but
btrim(n)in both subselects is a one-word fix.Iceland and Poland shields change colour — confirm that is intended
ref_classis a constantnationalfor Iceland, so all 16 329 numbered segments becomeblue; ismap previously drew yellow for trunk and white otherwise. Poland's droga krajowa
red is dropped too, and
shield-redis now documented as assigned by nothing.The old
docs/style.mdspecifically argued the sprites matched what each country's signslook like ("which is what its signs actually look like"); the new text drops that claim.
Fine if unification is the goal — flagging it because it is a visible change to two
countries' appearance rather than a refactor, and it is the kind of thing that is easier
to agree to now than to notice later.
Smaller notes
shield-redkept as a superset with a comment saying to drop it when no class wants it:good, no objection.
localtier is correct — E, riksväg, or länsväg, nothing else.localsegments correctly get no shield, matching the intent of theold
length <= 3filter.6f492512b2tob5c14930e3Addressed — thanks for the review.
E numbers win in Poland
Inverted the two
COALESCEsubselects inconf/plmap.yaml: the primary ref is now the first E number when one exists, else the first other number — the same E-first predicatesemap.yamlandfimap.yamluse. After reimporting the PL roads layer,ref_class='european'goes 11 → 28 658 (fi 24 268, se 87 070), so E67 carries a green shield on both sides of the border.docs/schema.md,docs/country-poland.mdand theconf/plmap.yamlcomment now explain the E-first rule instead of the old "E is a secondary tab beneath it" reasoning.btrim the split parts
btrim(n)in both subselects, so'E77; S7'yields'E77'rather than' S7'. After reimport:Iceland/Poland shield colours — yes, intended
Not a regression, and not a refactor that happens to change two countries' appearance: the unification is the point of issue #19. One shared
ref_classladder that means the same thing everywhere cannot keep per-country sprite semantics —ref_classis a constantnationalfor Iceland, Poland's droga krajowa red was exactly the per-country matching a shared ladder replaces, and the style now matchesref_classonly, with no per-country predicates. The olddocs/style.mdclaim that "the sprites matched what each country's signs actually look like" was deliberately dropped: on a map whose whole point is that E67 is E67 in all four countries, cross-border consistency beats national sign realism, and the green-shield-across-the-border outcome above is the same tradeoff applied to Poland.shield-redstays as a documented superset until some class wants it. If you'd rather keep national colours at merge, veto and we'll revisit — but please treat this as the intended behaviour, not an oversight.Smaller notes
Agreed, no changes:
shield-redkept as a superset with the drop-it-later comment; Sweden's no-localtier is correct; Poland's 2 032 129localsegments correctly get no shield.b5c14930e3to6038438363