style: let road, rail and transit widths keep growing past z14 #31

Merged
art merged 1 commit from night/issue-2-road-widths into main 2026-08-02 05:59:56 +00:00
Collaborator

Closes #2


Issue #2 — streets/roads style improvements

Branch: night/issue-2-road-widths (one commit, ee36b16, not pushed, not merged)
Files: html/style.json, CLAUDE.md

What was actually wrong

Not the widths at z15 as such — the ramps. Every line-width ramp in the style ended at
z14
, and interpolate clamps above its last stop. So from z14 upwards a road was pinned to
a fixed number of screen pixels while the world doubled under it every zoom. At z15 a
street was already thinner than its own label; at z16 a minor street was a 2.0 px band
under a 10.5 px label, which is exactly the "too thin" the issue describes. The same
defect was in railways_*, transit_* and waterways — all clamped at z14.

Hornsgatan is road_class = tertiary in the Stockholm data (4.1 km of it; some short
stretches are minor), and its side streets — Brännkyrkagatan, Krukmakargatan,
Timmermansgatan, Bastugatan — are minor. So the issue's acceptance test reads: tertiary
must hold a road name at z15, minor at z16.

What I changed

Road, rail and transit ramps now run to z17.

Roads grow ~2× per zoom above z13 — i.e. a constant width on the ground, which is what
"the street stays a street as you zoom" means. Fills at z15 / z16:

class z14 z15 z16 z17
service 1.6 3.2 6.4 12.8
minor 3.5 7.2 14.4 28.8
tertiary 5.9 14.4 28.8 57.6
secondary 7.1 15.2 30.4 60.8
primary 8.3 16.0 32.0 64.0
trunk 9.4 17.0 34.0 68.0
motorway 10.5 18.0 36.0 72.0

The two bold numbers are the issue's test: tertiary at z15 and minor at z16 are the same
14.4 px. That is one class step per zoom, so minor is exactly half of tertiary at every
zoom.

Above tertiary the ladder is deliberately compressed rather than continuing to scale.
The big classes are exaggerated at small scale so they stay visible at z5–z13; carrying that
exaggeration into a doubling ramp made the E20 interchange at Roslagstull a single orange
mass at z15 with the ramps merged into the carriageways. I rendered both and picked the
narrower one. Up there the class is carried by colour anyway.

Rail and transit are extended at ~1.4× per zoom instead of 2× — a track is drawn as a
line, not at its width on the ground — but they had to move: left clamped they became threads
beside the new roads. Casings keep a ring that widens from ~0.8 px at z14 to ~1.8–2.8 px at
z17.

Everything at z13 and below is untouched. Each ramp keeps its old stops and gains an
explicit z13 stop pinned to the value the old ramp interpolated there.

How I verified it

  • Sub-z14 curves unchanged: evaluated both versions of every ramp over z5–z13 in 0.1
    steps. Max deviation 0.0033 px, from rounding the pinned z13 values to two decimals.

  • Rendered and measured, Chrome over CDP in real time (no --virtual-time-budget),
    against the live mapsrv on :8080, at z12–z17 over Södermalm, Roslagstull, Göteborg and
    a rural view near Finspång. Measurement: switch the label halo off (it is the same white as
    the road fill and contaminates any pixel measurement), rotate the crop until the street runs
    horizontal, then compare the white fill's row span with the label ink's.

    band ink white above / below
    z15 Hornsgatan (tertiary), before 9.5 px 9.5 px −1.5 / +1.5 (overhangs)
    z15 Hornsgatan (tertiary), after 14.0 px 10.5 px 2.5 / 1.0 — inside
    z16 Krukmakargatan (minor), before 2.0 px 10.5 px +3.0 / +5.5 (overhangs)
    z16 Krukmakargatan (minor), after 14.0 px 10.0 px 3.0 / 1.0 — inside
  • gl-style-validate html/style.json — clean.

  • Class hierarchy monotone (minor < tertiary < … < motorway) and every casing wider than its
    fill, checked at z13–z17.

  • make build and cd mapsrv && go test ./... — pass (no Go was touched).

No import is needed: this is style only, and it needs no reimport or regeneralize.

Left undone / uncertain

  • waterways has the same clamp at z14 and I did not touch it, because its width is a
    step on importance nested inside the interpolate stops, and because a wide river at high
    zoom is drawn by the water_bodies polygon rather than the centreline — so how much the
    centreline should grow is a separate judgement, not a mechanical extension. A river is now
    visibly thinner than the streets around it at z16. Worth its own issue if it bothers you.
  • Several other ramps clamp early and are untouched because they are supposed to
    (road_names/station_labels text size, station_dots radius — screen-sized things), but
    buildings_dots, buildings_icons and peaks icon-size also clamp at z13/z14 and are
    arguably the same bug on the Iceland layers. Not looked at.
  • Above z17 everything clamps again. Deliberate — no layer has data past z14 and the
    numbers get silly (motorway would be 144 px at z18) — but if you routinely zoom to 18–19,
    say so and the ramps can be extended.
  • The exact z15 targets are a judgement call anchored on one legibility test. If tertiary at
    14.4 px feels fat on a rural tertiary (it is ~35 m of ground there), the fix is to lower
    the whole ladder and shrink road_names text-size to match — they move together.
  • Sweden only. I did not look at Iceland or Poland at z15+; nothing in the change is
    country-specific, but Poland's class mix differs and its service share is large.
Closes #2 --- # Issue #2 — streets/roads style improvements Branch: `night/issue-2-road-widths` (one commit, `ee36b16`, not pushed, not merged) Files: `html/style.json`, `CLAUDE.md` ## What was actually wrong Not the widths at z15 as such — the ramps. **Every `line-width` ramp in the style ended at z14**, and `interpolate` clamps above its last stop. So from z14 upwards a road was pinned to a fixed number of *screen* pixels while the world doubled under it every zoom. At z15 a street was already thinner than its own label; at z16 a `minor` street was a **2.0 px** band under a **10.5 px** label, which is exactly the "too thin" the issue describes. The same defect was in `railways_*`, `transit_*` and `waterways` — all clamped at z14. Hornsgatan is `road_class = tertiary` in the Stockholm data (4.1 km of it; some short stretches are `minor`), and its side streets — Brännkyrkagatan, Krukmakargatan, Timmermansgatan, Bastugatan — are `minor`. So the issue's acceptance test reads: *tertiary must hold a road name at z15, minor at z16.* ## What I changed Road, rail and transit ramps now run to **z17**. Roads grow **~2× per zoom above z13** — i.e. a constant width on the ground, which is what "the street stays a street as you zoom" means. Fills at z15 / z16: | class | z14 | z15 | z16 | z17 | |---|---|---|---|---| | service | 1.6 | 3.2 | 6.4 | 12.8 | | minor | 3.5 | 7.2 | **14.4** | 28.8 | | tertiary | 5.9 | **14.4** | 28.8 | 57.6 | | secondary | 7.1 | 15.2 | 30.4 | 60.8 | | primary | 8.3 | 16.0 | 32.0 | 64.0 | | trunk | 9.4 | 17.0 | 34.0 | 68.0 | | motorway | 10.5 | 18.0 | 36.0 | 72.0 | The two bold numbers are the issue's test: `tertiary` at z15 and `minor` at z16 are the same 14.4 px. That is one class step per zoom, so `minor` is exactly half of `tertiary` at every zoom. Above `tertiary` the ladder is deliberately **compressed** rather than continuing to scale. The big classes are exaggerated at small scale so they stay visible at z5–z13; carrying that exaggeration into a doubling ramp made the E20 interchange at Roslagstull a single orange mass at z15 with the ramps merged into the carriageways. I rendered both and picked the narrower one. Up there the class is carried by colour anyway. Rail and transit are extended at **~1.4× per zoom** instead of 2× — a track is drawn as a line, not at its width on the ground — but they had to move: left clamped they became threads beside the new roads. Casings keep a ring that widens from ~0.8 px at z14 to ~1.8–2.8 px at z17. **Everything at z13 and below is untouched.** Each ramp keeps its old stops and gains an explicit z13 stop pinned to the value the old ramp interpolated there. ## How I verified it * **Sub-z14 curves unchanged**: evaluated both versions of every ramp over z5–z13 in 0.1 steps. Max deviation **0.0033 px**, from rounding the pinned z13 values to two decimals. * **Rendered and measured**, Chrome over CDP in real time (no `--virtual-time-budget`), against the live `mapsrv` on :8080, at z12–z17 over Södermalm, Roslagstull, Göteborg and a rural view near Finspång. Measurement: switch the label halo off (it is the same white as the road fill and contaminates any pixel measurement), rotate the crop until the street runs horizontal, then compare the white fill's row span with the label ink's. | | band | ink | white above / below | |---|---|---|---| | z15 Hornsgatan (tertiary), before | 9.5 px | 9.5 px | −1.5 / **+1.5 (overhangs)** | | z15 Hornsgatan (tertiary), after | **14.0 px** | 10.5 px | 2.5 / 1.0 — inside | | z16 Krukmakargatan (minor), before | 2.0 px | 10.5 px | **+3.0 / +5.5 (overhangs)** | | z16 Krukmakargatan (minor), after | **14.0 px** | 10.0 px | 3.0 / 1.0 — inside | * `gl-style-validate html/style.json` — clean. * Class hierarchy monotone (minor < tertiary < … < motorway) and every casing wider than its fill, checked at z13–z17. * `make build` and `cd mapsrv && go test ./...` — pass (no Go was touched). No import is needed: this is style only, and it needs no reimport or regeneralize. ## Left undone / uncertain * **`waterways` has the same clamp at z14** and I did not touch it, because its width is a `step` on `importance` nested inside the interpolate stops, and because a wide river at high zoom is drawn by the `water_bodies` polygon rather than the centreline — so how much the centreline should grow is a separate judgement, not a mechanical extension. A river is now visibly thinner than the streets around it at z16. Worth its own issue if it bothers you. * Several other ramps clamp early and are untouched because they are *supposed* to (`road_names`/`station_labels` text size, `station_dots` radius — screen-sized things), but `buildings_dots`, `buildings_icons` and `peaks` icon-size also clamp at z13/z14 and are arguably the same bug on the Iceland layers. Not looked at. * **Above z17 everything clamps again.** Deliberate — no layer has data past z14 and the numbers get silly (motorway would be 144 px at z18) — but if you routinely zoom to 18–19, say so and the ramps can be extended. * The exact z15 targets are a judgement call anchored on one legibility test. If `tertiary` at 14.4 px feels fat on a *rural* tertiary (it is ~35 m of ground there), the fix is to lower the whole ladder and shrink `road_names` text-size to match — they move together. * Sweden only. I did not look at Iceland or Poland at z15+; nothing in the change is country-specific, but Poland's class mix differs and its `service` share is large.
Issue #2: streets look right at z13 but too thin at z15. The cause is that
every line-width ramp in the style ended at z14. `interpolate` clamps above
its last stop, so from z14 up a road was pinned to a fixed *screen* width
while the world doubled under it: at z16 a minor street was a 2.0 px band
under a 10.5 px label, and the name sat on the road rather than in it.

Every road, rail and transit ramp now runs to z17. The road ramps grow by
about 2x per zoom above z13 — a constant width on the ground, which is what
"the street stays a street as you zoom" means — and the ladder is anchored
by the legibility test from the issue rather than by taste: at z15 Hornsgatan
(tertiary) is a 14.4 px fill and holds its 13 px name completely inside the
white, and `minor` reaches that same 14.4 px one zoom later at z16. That is
one class step per zoom, so minor is half of tertiary at every zoom.

Above tertiary the ladder is compressed on purpose — 15.2 / 16 / 17 / 18 px
at z15 for secondary / primary / trunk / motorway. Those classes are already
exaggerated at small scale so they stay visible at z5-z13, and carrying that
exaggeration into a doubling ramp made the E20 interchange at Roslagstull one
orange mass at z15, with the ramps merged into the carriageways. They are
told apart by colour up there anyway.

Rail and transit had the same clamp and are extended too, but at ~1.4x per
zoom: a track is drawn as a line, not at its ground width. Left alone they
would have become threads beside the new roads.

Everything at z13 and below is unchanged. Each ramp keeps its old stops and
gains an explicit z13 stop pinned to the value the old ramp interpolated
there, so the sub-z14 curve is identical to 0.0033 px (rounding to two
decimals) at every zoom — verified by evaluating both versions of every ramp
over z5..z13 in 0.1 steps.

Verified by rendering Stockholm over CDP in real time (no virtual time
budget) at z12..z17 and measuring the rendered PNGs: with the label halo
switched off, rotate the crop until the street runs horizontal, then compare
the white fill's row span to the label ink's. z15 Hornsgatan: 14.0 px band,
10.5 px ink, 2.5 px of white above and 1.0 below. z16 Krukmakargatan (minor):
14.0 px band, 10.0 px ink, 3.0 above and 1.0 below. Both were 9.5 px and
2.0 px before. gl-style-validate is clean; the class hierarchy is monotone
and every casing is wider than its fill at z13..z17.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
art merged commit 90603a81d2 into main 2026-08-02 05:59:56 +00:00
art deleted branch night/issue-2-road-widths 2026-08-02 05:59:56 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
art/ismap!31
No description provided.