# How to Dilate and Style Streets by Highway Type in Prettymaps

> Learn to dilate and style streets by highway type in Prettymaps. Control street thickness and color using a dictionary for custom map visualizations. Enhance your map designs now.

- Repository: [Marcelo de Oliveira Rosa Prates/prettymaps](https://github.com/marceloprates/prettymaps)
- Tags: how-to-guide
- Published: 2026-08-20

---

**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`](https://github.com/marceloprates/prettymaps/blob/main/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:

```python
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:

```python
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:

```python
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`](https://github.com/marceloprates/prettymaps/blob/main/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`](https://github.com/marceloprates/prettymaps/blob/main/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`](https://github.com/marceloprates/prettymaps/blob/main/prettymaps/fetch.py) | OSM data retrieval | `get_gdfs` |
| [`prettymaps/preset.py`](https://github.com/marceloprates/prettymaps/blob/main/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`](https://github.com/marceloprates/prettymaps/blob/main/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.