Skip to content

Manual — using roadstyle, with live output

A hands-on walk-through of the library, with the actual rendered map embedded after each step so you can see exactly what the code produces. Every map below is the real, self-contained output of the snippet above it (the same backend="web" MapLibre map you'd .save(...)), regenerated by docs/build_maps.py from the bundled sample edges (notebooks/data/sodermalm_edges.gpkg, ~4,600 real road edges).

New here? Read Usage for install + the CLI, then come back. For every parameter, see the Parameter reference.


1. Your first map

Load road edges (any CRS; just needs a road-class column) and render. backend="web" is the default — a self-contained MapLibre map you can ship as one file.

import geopandas as gpd
import roadstyle as rs

edges = gpd.read_file("sodermalm_edges.gpkg")        # has a `highway` column
rs.render_edges(edges).save("first_map.html")         # web backend, Voyager base (settings default)

You get the OSM "geometry sandwich" (coloured fills over casings), per-zoom widths, two-way lanes, one-way arrows, street-name labels, hover/click, and a base-layer switcher — try them in the map. Prefer a dark canvas? Pass basemap="dark_matter" (Dark Matter) or basemap="satellite".

Hover a road to highlight it; click it for its attributes; use the dropdown (top-left) to switch the base map.


2. Colour roads by your own data — switchable, in the browser

Bake several "colour by" options into one map and switch between them client-side, with no re-render and no server. Pass color_options — an ordered {name: {styler kwargs}} mapping — and roadstyle adds a Colour by dropdown. A neutral base reads best, so pair it with palette="mono":

rs.render_edges(edges, backend="web", palette="mono",
    color_options={
        "Class": {},                                       # neutral mono base
        "AADT":  {"color_by": "aadt",  "cmap": "viridis"},
        "Speed": {"color_by": "speed_kph", "cmap": "magma"},
    },
).save("recolor.html")

Use the Colour by dropdown (top-right) to switch between road class, AADT, and speed. The legend (bottom-left) follows the active option. Roads keep their width/casing/lanes — only the fill swaps.

Two things worth knowing:

  • Blank edges keep the base colour. An edge with no value for a data option (NaN / unmapped) falls back to its base (mono) colour, not a flat grey — so "no data" roads blend into the base instead of reading as a separate category.
  • Drive it from your own UI. The page exposes window.rsSetColorField(name|index) and fires a rs:colorchange event, so a custom control can switch colours and stay in sync (see §4).

3. Add your own overlay layers (zones, POIs, anything)

Bring extra geometry — zone polygons, POI points, any lines — as Overlays. Each becomes its own layer drawn under or over the roads, clickable for a popup of the fields you list, and toggled from a Layers control. Overlays are your data with your style; they don't go through the road-styling compiler.

from shapely.geometry import box, Point

# (here: synthetic zones + POIs — in practice your own GeoDataFrames)
rs.render_edges(edges, backend="web", palette="mono",
    overlays=[
        rs.Overlay(taz,  kind="fill",   placement="under", color="#6aa9ff",
                   opacity=0.14, label="TAZ zones", popup=["taz_id", "name", "weight"]),
        rs.Overlay(pois, kind="circle", placement="over",  color="#ff5d5d",
                   radius=7, label="POIs", popup=["name", "type"]),
    ],
).save("overlays.html")

Translucent TAZ zones sit under the roads; red POIs sit on top. Click a zone or a POI for its popup; use the Layers control (bottom-right) to toggle each overlay. (Road clicks take precedence — an overlay is consulted only where the click misses every road.)

kind is auto-detected from the geometry (polygon → fill, point → circle, line → line) but can be forced. See Overlay for every field.


4. Drive the map from your own UI

Both outputs let you build a custom panel instead of (or alongside) the built-in controls.

Web backend (render_edges(backend="web")). The page exposes globals you can call from any HTML you add to it:

<button onclick="rsSetColorField('AADT')">AADT</button>
<script>
  document.addEventListener("rs:colorchange", e => console.log("now showing", e.detail.option.name));
</script>

window.RS_COLOR_OPTIONS lists the baked options; window.rsSetColorField(name|index) recolours; rs:colorchange fires on every switch. (A worked custom-panel page is in examples/recolor_web_backend.py.)

roadstyle.js spec page (rs.save(...) / to_html). The richer RoadStyleMap API has a real panel hook + event bus:

const m = RoadStyle.create("map", spec, { widgets: { colors: false } });
m.on("ready", map => {
  map.addPanel({ title: "Colour by", position: "topright", render(body, map) {
    map.getColorOptions().forEach((o, i) => {
      const b = document.createElement("button");
      b.textContent = o.name; b.onclick = () => map.setColorField(i);
      body.appendChild(b);
    });
  }});
});

addPanel gives you styled, auto-stacking chrome; on("select"|"deselect"|"colorchange"|"ready", …) is the event bus; setColorField / getColorOptions drive the recolour. See Embedding in a website and examples/recolor_custom_panel.py.


Where to next