#164: Rendering style problems. #165
No reviewers
Labels
No labels
blocked
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!165
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "issue-164-road-level-hierarchy"
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 #164
Rendering style problems — the shared
levelhierarchy, and the fat-street / building-order fixesThis closes #164 and with it #18. Three deliverables, in the issue's order.
1. The global grade-separation hierarchy (underpass / normal / overpass)
The shared
roadscontract gains a third axis,level, with valuesunderpassandoverpass; NULL means normal and is deliberately not a value — ground leveldominates every source and omitting the attribute costs nothing in the MVT (the issue's
point about
NULLbeing the cheap default). It is a shared, cross-country axis becausegrade separation is a geometric fact, not a classification judgement: the same E4 must
read as a bridge on both sides of the Tornio–Haparanda border, exactly the argument
road_classalready makes. This is the change #18 parked under R25 ("ship a shared columnand its rendering together") — both land here.
Each country maps its own bridge/tunnel source:
einingvegakerfis: 4 → underpass, 2 → overpass (mapped for R25; this delivery has no bridges).Bro_och_tunnel_Konstruktion: tunnel/underfart → underpass, överfart → overpass,överfart och underfart(a segment that is both, the Gullmarsplan–Skanstull stack) → normal.POLOZENIE:pod powierzchnią gruntu→ underpass, the fourponad powierzchnią gruntu poziom Ncodes → overpass (R137 — the source distinguishes height, not grade).SILTA_ALIK: positive → underpass. Digiroad's only signal is one-sided (positive = passing under a bridge), so Finland has no overpass half.docs/geo-primer.mdanddocs/country-requirements.mdcarry the reasoning, and the R25 note documents the both/neither (both-over-and-under) case.Style. Every road class (
service..motorway) is split into underpass / normal /overpass variants — underpass a fill only (no casing), normal and overpass each a casing+fill
pair except
service(fill only) — 34 road line layers total (was 23). Level dominatesthe draw order: all underpasses, then all normal, then all overpasses on top, and within
a level all casings before all fills, so a bridge always reads over a tunnel and over any
ground-level road. Underpasses render as a faded fill (75% opacity, no casing — a
casing under a 75%-opacity fill would show through the road above as a dashed edge, so it
is omitted) drawn lowest; normal is the cheap
["!", ["has", "level"]]filter; overpass isthe same paint as normal but drawn last so the bridge reads elevated.
roads_unpavedsitsabove all three — it is a surface texture, not a grade. The metro line is drawn with
line-join: miterand at 60% opacity, which reads much better than round joins whenoverlaid on the underlying streets.
2. Streets are no longer too fat
Issue #164's buildings import gave the measuring stick: footprints render near their
on-ground size, so the old ladder — several times wider than the gap between buildings —
looked wrong when zooming. The ramps now approach real carriageway widths as you zoom in
(
minor~4.4 px ≈ 10 m at z16,motorway~12.8 px ≈ 31 m, near a real dual carriageway;~7 m at z17), while below z13 they keep a legibility exaggeration and the class ordering
is preserved at every zoom. The 11–13 px road names now exceed the road they label at the
mid zooms — expected, and the
road_nameshalo carries it rather than forcing absurdwidths.
3. Buildings drawn early
The
buildingsfootprint fill moved right after water, before roads/rail/transit/labels,so a metro or an underpass line renders over it and a town name is never covered. The
buildings_dots/buildings_icons/building_namessymbol layers stay up with the labels —a symbol competes for placement and belongs there.
Verification (live DB + real browser)
roadssharedcontract): fi=28714, is=15, pl=60210, se=69838 non-NULL
levelrows, none invalid.levelkey and bothunderpass/overpassvaluesare present in the
roadslayer.normal 2417 / overpass 104 features, with
levelcorrectly routed to each variantlayer; the underpass casings were later removed in review (a casing under a faded fill
showed through as dashed edges on the road above). Vision-model corroboration with
negative controls: buildings under roads (not covering), roads narrower, NOT every road
dashed, normal roads not wrongly covered.
gl-style-validate, exit 0); layer counts match the docs(94 total: 89 map + 4 is + background; 40 road layers).
make acceptance-all— 45/46 passed. The one failure is apre-existing marginal timeout on the Poland buildings R120 test (~11 s query
against a 10 s timeout, data itself correct: footprints == source parts == 16 351 829,
unclassified 0) — unrelated to this change (I touched only the roads layer and the
style); filed separately.
What I did not do
level-equivalent forrail yet, and a metro line under a city is already served by position in the draw order.
Documented as the standing simplification.
The
geo-primernote points at the per-countrywaterwaysmapping shape for the day asource provides it — filed as a follow-up.
level(underpass/overpass) hierarchy, plus the fat-street and building-order fixes 06f9049fc6Closes #164to #164: Rendering style problems.Three follow-ups from the review, all about checks that said less than they looked like they said. R121's decoded-tile check compared the tile's key set to the contract's for *equality*, and `ST_AsMVT` omits a NULL value per feature — so a declared attribute only appears in a tile that has a feature carrying it. `roads.level` is sparse enough to expose this: at z5 the whole map holds about ten non-NULL rows per country, so whether the busiest tile happened to contain one decided the verdict. It does today, in all four configs, which is the only reason the check was green. Now a key the tile does not carry is chased to a tile that does — rows where the column is non-NULL are turned into tile coordinates and served through the same function — and only a key absent from every probed tile is a failure. A leak is still judged on the first tile alone; a genuinely dropped column is missing from every tile, so it still fails. Both directions were exercised by temporarily lying to the decoder: a simulated sparse key passes with `[level] carried by no feature here, confirmed in level at 5/13/8`, and a simulated dropped column fails with `[level] absent from every probed tile`. R25 also checked `level NOT IN ('underpass', 'overpass')`, which cannot be true: `level` is a Postgres enum, so a bad value fails the cast at import, hours before any test runs. Replaced with the two things a mapping bug actually produces — a country with no non-NULL level at all (the all-NULL case #18 exists to close) and a country where level is *not* the minority, which is the CASE falling through the wrong way. NULL-is-normal is the premise of the design, so `levelled < total / 4` asserts the premise; the real numbers are 0.03%–2%, three orders of magnitude of headroom. Plus one cross-map assertion that both enum values occur somewhere, since Finland's signal is one-sided and Iceland has no bridges in this delivery. Both simulated bugs were checked to fail the new predicate. And the merge cost of `level`, which the review flagged as bounded-and-small and which measurement says is neither. `merge_lines` groups by every non-geometry attribute, and `level` changes at every bridge, so each one cuts a chain in two. On the z11 merge: se 69 420 chains → 80 746 (+16.3%), pl 88 768 → 99 064 (+11.6%), and a chain is two more pinned endpoints at every zoom below. Those chains buy nothing where they are paid — grade separation is invisible below about z13 and the merge runs from z12 down — but saying so needs a mechanism the line cascade does not have (`enum_rollups` is polygon-only and replaces the zoom's whole step). Measured, documented next to the attribute and under `merge_lines` in docs/generalization.md, and filed as issue #167 with a sketch. Verified: `go test ./...` green (new `TestMissingFrom` covers the set difference both ways), `go vet` clean, `config-check` and `tiles --check` OK on all four configs, R25 and R126 pass against the live database.