A general core: roads, items, and views¶
Status: design; step 1 (views) built and merged, 2026-10-06. Builds on Overlays attached to edges,
Overlay styles and Levels and looks. Takes ideas from the v2-dev
branch (docs/v2/, an unfinished test).
Why¶
roadstyle should be a core that other libraries build on (lanestyle, mapstyle, urbanstyle) without reaching
into its page. The v2-dev branch started for this reason. It drew a new data model (Corridor, Channel,
Demarcation, Patch, Glyph) and a new page.
Most of that model is already in roadstyle:
| v2-dev | what it is | roadstyle today |
|---|---|---|
| Corridor | the road: line, width, casing, heads, band, levels | the roads table (width_m_col, casing_start_col, fill_level_col, ...) |
| Channel | a lane: an area inside the road | Overlay(edge_col=, order_col=, kind="fill") |
| Demarcation | a painted line | the same, kind="line", width_m, dash |
| Patch | a polygon (fillet, island, zebra) | an overlay, kind="fill" |
| Glyph | text or a symbol | an overlay, kind="text"; there is no icon kind |
fill_visible=False |
the items are the road's fill | road_fill=False |
| style names in the theme | lane, divider, zebra ... | config.overlays.styles |
What is missing is not the names. It is what the core cannot do, and lanestyle shows it: lanestyle adds seven pieces of JavaScript to roadstyle's page, and each one uses roadstyle's layer ids, its globals or its internals.
| lanestyle snippet | what it does | what it reaches into |
|---|---|---|
_ANCHOR_JS |
finds where a position's fill layers are | the layer ids (roads-fill-lv4) |
_FILLETS_JS |
draws junction fillets at a position, in the road class colours | layer ids, RS_CLASS_COLORS, its own source |
_LABELS_JS |
a second layer (the lane type) on the lanes' data | RS_OVERLAYS, the name layer's font |
_STREET_VIEW_JS |
a click on a lane opens Street View at its road | the click of an overlay does not reach the road |
_CASING_ZOOM_JS |
no casing below a zoom | every casing layer id |
_NO_PATTERN_ON_RINGS_JS |
no tunnel pattern on a roundabout | the pattern layers' filters |
_USE_ROWS_JS |
lane-use rows in the filter box, the colour menu hidden | the page's CSS classes |
A change inside roadstyle (a layer renamed, a filter changed) breaks lanestyle without a test failing in either repository.
The goal: a library that builds on roadstyle uses only the keywords of render_edges and the public
rs* API. Its own page code (lanestyle's turn highlight, _CLICK_JS, uses rs:select, rsQuery,
rsGetProps) is fine; reaching into layer ids and globals is not. Each step below removes one or more of
the snippets above, so the goal can be checked.
The model¶
Roads line · width · casing · heads (start and end, per road) · band · priority · look
the solver gives each road its numbers: casing (start, main, end) and fill
Items overlays, each attached to
a road edge_col drawn at the road's fill number, by order_col (today)
nothing placement under / over (today)
kind: fill · line · circle · text · icon (new)
style: a name in the theme (lane, divider, zebra, arrow, ...)
Views named sets of settings, switched in the page
One item type, not five¶
An item stays an Overlay. Lane, marking, patch and glyph are roles, and a role is a style name in the
theme, as lanestyle's lane, divider, zebra already are. A new kind of thing on a road needs a style,
not a class. If a typed call reads better in a library, it can be a small function that returns an Overlay.
What an item gets from its road¶
An item attached to a road is drawn at its road's place. It should also get the rest of what the road has, so a library does not copy it onto every item:
- The look. A lane of a tunnel is drawn as tunnel, a lane of a bridge as bridge.
v2-devdoes this in its tunnel colouring (docs/v2/tunnel-coloring.md): an item is tunnel when itsedge_idis a tunnel's. - The filters. Hiding tunnels, a road class or a filtered set hides that road's items, always
(2026-10-05). Today
rsFilteron roads and on an overlay are separate. - The selection. A click on an item selects its road as well: Street View, the docked panel and
rs:selectfollow, and the item's own popup still shows._STREET_VIEW_JSdoes this by hand now.
Clicks: some items let them through¶
From v2-dev: a marking (a dash line, a stop line) should not catch a click meant for the lane under it.
popup=[] already makes an overlay not interactive; the theme style should be able to say so too
(interactive: false), so that every library's markings let clicks through by default.
One item layer, several looks¶
An overlay is one data set drawn with one style. lanestyle needs the lanes drawn as a fill and labelled
with their type: today it adds a second MapLibre layer on the lanes' source by hand (_LABELS_JS). An
overlay should take a list of styles that all read the same data:
The data is in the page once. (This is the useful half of v2-dev's "single source": one data set, many
layers. The other half, putting roads and items into one source, does not make the page smaller: see
"Not taken" below.)
Junction pieces are road items¶
A fillet (the corner piece where roads meet) or a gap between lanes is not a new kind of item. lanestyle
already gives each one the level and colour of its nearest lane (junctions.py), so it is attached to that
lane's road with edge_col and a low order, as the road-end discs are (items.end_caps, order -2). This
replaces _FILLETS_JS and _ANCHOR_JS (its only user) with no new feature in roadstyle. The tunnel hatch on
a gap is its road's look; its popup is the overlay's popup.
The look per road¶
Today a look is all or nothing: every tunnel gets the tunnel pattern, every casing shows at every zoom. Two small things make it per road:
- Look columns:
pattern_col(true/false per road; lanestyle sets it false on a roundabout ring), and later others if a library asks. - Zoom ranges:
casing_zoom=(min, max)andfill_zoom=(min, max)for the roads' own layers.
This replaces _NO_PATTERN_ON_RINGS_JS and _CASING_ZOOM_JS.
Views¶
Today the page switches one thing from a menu: the colour (color_options, which can also switch the width
with width_by). The other settings have their own controls: the overlays (rsSetOverlay), the classes,
bridges and tunnels (rsSetClasses, rsSetBridges, rsSetTunnels), the base map (rsSetBasemap), 3D
(rsSetView3D).
A view is a name and a set of these settings, applied together:
rs.render_edges(roads, overlays=[...], views={
"Traffic": {"color": "flow", "road_fill": True, "overlays": {"lanes": False, "arrows": False}},
"Lanes": {"color": None, "road_fill": False, "overlays": {"lanes": True, "arrows": True}},
"Night": {"basemap": "dark"},
})
The page shows a View menu next to the Colour by menu (2026-10-05). rsSetView(name) applies a view from host
page code. A view sets only what it names; the rest stays as it is.
| a view can set | how the page does it | new? |
|---|---|---|
color |
an entry of color_options (with its width) |
no: rsSetColorField |
overlays |
each overlay on or off | no: rsSetOverlay |
classes, bridges, tunnels |
what is shown | no |
basemap |
the base map | no |
view3d |
3D on or off | no |
road_fill |
the roads' fill shown or not (with road_fill=False the fill layers are in the page already, at opacity 0) |
yes |
labels |
the street names from a chosen column, or none | yes |
item_color |
a colour option for an overlay ({"lanes": "use"}) |
yes |
filter |
a set of ids shown, the rest dimmed or hidden | partly: rsFilter |
camera |
where to look | partly: rsFocus |
The first rows cost almost nothing: a view is a list of calls the page already has. road_fill is the one
lanestyle needs most: one page that is a road map coloured by flow and a lane map, where it is two pages now.
v2-dev's ViewPreset is the checklist for later rows (width_mode="flow", opacity_unselected). Its
"0 ms by feature state" design is not taken: feature state is set one feature at a time and cannot change a
layout property (the label text), while the colour options already switch every road in one call, by a
column baked per option.
Event: rs:viewselect (rs:viewchange is taken by 3D).
Not taken from v2-dev, and why¶
- The five dataclasses. See "One item type": roles are style names.
- A second page (
WebMap.to_html). It has none of the 21rs*functions, Street View, the panel, tiles or snapshots. The page stays one. - Roads and items in one source.
docs/v2/monaco_benchmark_results.jsonmeasured the v2 roads page at 5.1 MB against 2.1 MB for v1, and the lanes page at 7.4 MB against 7.1 MB. Items are separate geometries: putting them in the roads' source saves nothing. - Bridge shadow, two-colour tunnel dashes. Looks, possible later as theme entries; not part of the model.
Taken separately (the solver steps of the v2 review): pair tables with overrides, heads per road at each end, the two directions of a road solved as one, priority tiers (roundabout, tunnel, road).
Steps¶
Each step is a release that is tried on local pages first.
| step | what | lanestyle snippet it removes |
|---|---|---|
| 1 | views: views=, the View menu, rsSetView, with road_fill |
(new: one page for roads and lanes) |
| 2 | an item click selects its road; items follow their road's filters | _STREET_VIEW_JS |
| 3 | several styles per overlay; interactive in a style |
_LABELS_JS |
| 4 | lanestyle only: fillets and gaps as items attached to the nearest lane's road | _FILLETS_JS, _ANCHOR_JS |
| 5 | look columns (pattern_col) and casing_zoom / fill_zoom |
_NO_PATTERN_ON_RINGS_JS, _CASING_ZOOM_JS |
| 6 | legend and filter rows from an overlay's categories | _USE_ROWS_JS |
| 7 | the solver ideas from v2-dev |
After step 6, lanestyle's page code uses only the public API.
Decided (2026-10-05)¶
- The View menu sits next to the Colour by menu.
- Items always follow their road's filters.
- No junction items and no public layer slot: junction pieces are road items (above).