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.GeoDataFrameof street edges - A
widthparameter (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,ecfor edge color,fcfor 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 geometrieshatch: Pattern fillalpha: 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→ Shapelybuffer()operation - Style properties (
ec,fc,lw) are applied separately indraw_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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →