How to Dilate and Style Streets by Highway Type in Prettymaps

Control street thickness and color in Prettymaps by passing a dictionary to the width argument, mapping OpenStreetMap highway tags to numeric widths.

Prettymaps renders beautiful maps from OpenStreetMap data by converting graph geometries into styled visualizations. The key to customizing how different road types appear—from motorways to footpaths—lies in understanding how width dilation and per-highway styling flow through the drawing pipeline. This guide breaks down the exact mechanism in marceloprates/prettymaps and provides runnable code examples.


How Width Dilation Works in the Drawing Pipeline

The dilation of street geometries happens in three connected functions inside prettymaps/draw.py. Each plays a distinct role in transforming raw OSM data into weighted, styled shapes.

graph_to_shapely: The Core Dilation Engine

Located at lines 75-89, graph_to_shapely receives:

  • A geopandas.GeoDataFrame of street edges
  • A width parameter (float or dictionary)

When width is a dictionary, the function maps each row's highway attribute to its corresponding numeric width. It creates a temporary width column, drops rows without matching highway types, then buffers each line geometry by its specific width using Shapely's buffer() operation. The result is a unified Shapely polygon collection.

gdf_to_shapely: Layer Dispatch

This helper at lines 86-89 routes street data to graph_to_shapely when the layer name is streets, railway, or waterway. For other layers, it falls back to geometries_to_shapely, which handles generic dilation via point_size and line_width arguments.

plot_gdf: Rendering with Matplotlib

Lines 33-35 define plot_gdf, which receives:

  • The dilated Shapely collection
  • A styling dictionary (palette, ec for edge color, fc for fill color)
  • The width configuration forwarded from above

The function iterates through geometries and renders each using matplotlib.patches.PolygonPatch for polygons or ax.plot for lines.


Configuring Width by Highway Type

The layers argument in prettymaps.plot accepts a "width" key that controls dilation. This can be:

  • A single float: Applies uniform width to all streets
  • A dictionary: Maps OSM highway tags to custom widths

Supported Highway Tags

Common OpenStreetMap highway values include: motorway, trunk, primary, secondary, tertiary, unclassified, residential, service, footway, cycleway, and path.


Practical Code Examples

Basic Width Dictionary

Apply different thicknesses to major road classes:

import prettymaps as pm

street_widths = {
    "motorway": 3.0,
    "primary": 2.0,
    "secondary": 1.5,
    "residential": 0.5
}

pm.plot(
    query="Porto Alegre, Brazil",
    layers={"streets": {"width": street_widths}},
    style={"streets": {"ec": "#222222", "fc": "#222222"}},
    figsize=(12, 12),
    show=True,
)

Per-Highway Colors with Dynamic Styling

Combine width dilation with color mapping using lambda functions:

import prettymaps as pm

highway_widths = {
    "motorway": 4,
    "trunk": 3,
    "primary": 2,
    "secondary": 1.5,
    "tertiary": 1,
    "residential": 0.5
}

highway_colors = {
    "motorway": "#ff0000",
    "trunk": "#ff7f00",
    "primary": "#ffff00",
    "secondary": "#00ff00",
    "tertiary": "#00ffff",
    "residential": "#0000ff",
}

street_style = {
    "streets": {
        "ec": "#000000",
        "fc": lambda road_type: highway_colors.get(road_type, "#777777"),
        "lw": 0,
    }
}

pm.plot(
    query="Amsterdam, Netherlands",
    layers={"streets": {"width": highway_widths}},
    style=street_style,
    figsize=(14, 14),
    show=True,
)

Reusable Presets

Encapsulate width and style configurations for repeated use:

import prettymaps as pm

pm.create_preset(
    name="my_road_preset",
    layers={"streets": {"width": {"motorway": 3, "primary": 2, "residential": 0.5}}},
    style={"streets": {"ec": "#333333", "fc": "#333333"}},
)

pm.plot(
    query="Berlin, Germany",
    preset="my_road_preset",
    use_preset=True,
    figsize=(10, 10),
    show=True,
)

Where Styling Meets Dilation

The draw_layers function (lines 99-110 in prettymaps/draw.py) applies final styling to each layer. While width controls geometric dilation, the style argument controls:

  • ec: Edge color (matplotlib color string)
  • fc: Fill color (string or callable receiving highway type)
  • lw: Line width for unfilled geometries
  • hatch: Pattern fill
  • alpha: Transparency

These style properties are applied per-layer after width-based dilation has occurred, meaning you can independently control how thick a road appears and how it's colored.


Key Source Files

File Purpose Critical Functions
prettymaps/draw.py Core pipeline for geometry conversion and rendering graph_to_shapely (L75-89), gdf_to_shapely (L86-89), plot_gdf (L33-35), draw_layers (L99-110)
prettymaps/fetch.py OSM data retrieval get_gdfs
prettymaps/preset.py Configuration persistence create_preset, read_preset

Summary

  • Pass a dictionary to layers["streets"]["width"] to dilate each highway type independently
  • The mapping flows through gdf_to_shapely → graph_to_shapely → Shapely buffer() operation
  • Style properties (ec, fc, lw) are applied separately in draw_layers
  • Use lambda functions for dynamic color assignment based on highway type
  • Save configurations with create_preset() for reproducible workflows

Frequently Asked Questions

What happens if a highway type isn't in my width dictionary?

Rows without matching highway keys are dropped entirely from the output per lines 75-89 in prettymaps/draw.py. To include all streets, ensure your dictionary contains entries for every highway type present in your query area, or use a default width as a single float.

Can I use different widths for railway or waterway layers?

Yes. The gdf_to_shapely dispatcher at lines 86-89 handles streets, railway, and waterway identically. Pass a width dictionary to any of these layer names: layers={"railway": {"width": {"rail": 2.0, "subway": 3.0}}}.

Why does my street appear as a filled polygon instead of a line?

When you provide a width value (float or dict), graph_to_shapely calls buffer() on line geometries, converting them to polygons. To keep streets as lines, either omit the width parameter or set it to None, then control visual thickness with lw (line width) in your style.

How are width values interpreted geographically?

Width values are in the same units as your query's coordinate reference system. Prettymaps typically works in meters when using query with place names. Extremely large values (e.g., 1000) will produce massive dilations; realistic values for urban streets range from 0.5 to 10 meters.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →