Files
Marcelo de Oliveira Rosa Prates 3ef70792e2 Replace tutorial notebook with marimo + static docs (#144)
* Replace tutorial notebook with marimo + static docs

Jupyter notebooks (.ipynb) are hostile to code review: every cell output
is a base64-embedded blob that churns on every run, and a 27 MB
examples.ipynb is essentially unreviewable. Moving the canonical
tutorial to a git-friendly marimo notebook and a static Markdown
walkthrough keeps code review diffs small, makes the source diffable
in pull requests, and lets the rendered tutorial live alongside the
library on the GitHub Pages site.

- Replace notebooks/examples.ipynb with notebooks/tutorial.py
  (marimo notebook, 35 cells, 15.1 KB).
- Add docs/tutorial.md with the same walkthrough rendered as Markdown
  plus 10 inline PNGs at docs/img/tour-01..tour-10-*.png extracted
  from the original notebook's cell outputs.
- Add Tutorial entry to mkdocs.yml nav; link from docs/index.md and
  README.md. README's inline tutorial block is replaced with a pointer
  table to docs/tutorial.md (README: 1062 -> 112 lines).
- Add marimo>=0.23.0 to requirements.txt; update .gitignore to keep
  the new tutorial.py whitelisted and to ignore marimo caches
  (__marimo__/, *.marimo).
- Fix mkdocs.yml site_url placeholder
  (yourusername.github.io -> marceloprates.github.io) so the GitHub
  Pages site's canonical links and sitemap point at the real domain.
- The 23 unrelated demo notebooks that lived alongside the tutorial
  are kept untracked in notebooks/untracked/ (gitignored as before).

* Restore README tutorial pointer and update Colab link

Follow-up to the tutorial-marimo conversion. The previous rebase onto
the upstream CI commit (ba59f1f) accidentally dropped the new README.md
rewrite because 'git checkout --ours' during a rebase selects the
upstream side, not the commit being applied. This restores the
112-line README.md with the tutorial pointer and updates the Colab
link from notebooks/examples.ipynb to notebooks/tutorial.py.

* Remove obsolete generate-readme CI workflow

The generate-readme.yml workflow and the notebooks_to_readme.sh script
it depended on were designed to run notebooks/examples.ipynb and
regenerate README.md from its cell outputs. Since examples.ipynb is
gone (replaced by the marimo notebook and static docs/tutorial.md),
this pipeline has no purpose and was failing the PR check.

Also removes the 13 orphan temp_readme_*.png files that the CI had
committed to pictures/README/ in ba59f1f — their source notebook
no longer exists.
2026-07-30 12:24:36 -03:00

8.9 KiB

prettymaps Tutorial

A minimal Python library to draw customized maps from OpenStreetMap created using the osmnx, matplotlib, shapely and vsketch packages.

Heerhugowaard sample

This tutorial is generated from notebooks/tutorial.py, a marimo notebook. To run it interactively:

uv run --with marimo marimo edit notebooks/tutorial.py

Or with the locally installed copy:

.venv/bin/marimo edit notebooks/tutorial.py

Installation

Install locally

pip install prettymaps

Install on Google Colaboratory

!pip install -e "git+https://github.com/marceloprates/prettymaps#egg=prettymaps"

Then restart the runtime (Runtime -> Restart Runtime) before importing prettymaps.

Run front-end

After prettymaps is installed, you can run the front-end (streamlit) application from the prettymaps repository using:

streamlit run app.py

Plotting

Plotting with prettymaps is very simple. Run:

prettymaps.plot(your_query)

your_query can be:

  1. An address (Example: "Porto Alegre"),
  2. Latitude / Longitude coordinates (Example: (-30.0324999, -51.2303767)),
  3. A custom boundary in GeoDataFrame format.

Default preset

import prettymaps

plot = prettymaps.plot('Stad van de Zon, Heerhugowaard, Netherlands')

Heerhugowaard, default preset

Presets

You can also choose from different "presets" (parameter combinations saved in JSON files). See below an example using the "minimal" preset:

plot = prettymaps.plot(
    'Stad van de Zon, Heerhugowaard, Netherlands',
    preset='minimal',
)

Heerhugowaard, minimal preset

Run prettymaps.presets() to list all available presets. To inspect a specific preset, run prettymaps.preset('default').

Customizing parameters

Instead of using the default configuration you can customize several parameters. The most important are:

  • layers — A dictionary of OpenStreetMap layers to fetch.
    • Keys: layer names (arbitrary)
    • Values: dicts representing OpenStreetMap queries
  • style — Matplotlib style parameters
    • Keys: layer names (the same as before)
    • Values: dicts representing Matplotlib style parameters
plot = prettymaps.plot(
    your_query,
    layers,
    style,
    preset,
    save_preset,
    update_preset,
    circle,
    radius,
    dilate,
)

plot is a Python dataclass containing:

@dataclass
class Plot:
    geodataframes: Dict[str, gp.GeoDataFrame]
    fig: matplotlib.figure.Figure
    ax: matplotlib.axes.Axes

Here's an example of running prettymaps.plot() with customized parameters (Macau):

plot = prettymaps.plot(
    'Praça Ferreira do Amaral, Macau',
    circle=True,
    radius=1100,
    layers={
        "green": {
            "tags": {
                "landuse": "grass",
                "natural": ["island", "wood"],
                "leisure": "park",
            }
        },
        "forest": {"tags": {"landuse": "forest"}},
        "water": {"tags": {"natural": ["water", "bay"]}},
        "parking": {
            "tags": {
                "amenity": "parking",
                "highway": "pedestrian",
                "man_made": "pier",
            }
        },
        "streets": {
            "width": {
                "motorway": 5, "trunk": 5, "primary": 4.5,
                "secondary": 4, "tertiary": 3.5, "residential": 3,
            }
        },
        "building": {"tags": {"building": True}},
    },
    style={
        "background": {"fc": "#F2F4CB", "ec": "#dadbc1", "hatch": "ooo..."},
        "perimeter": {"fc": "#F2F4CB", "ec": "#dadbc1", "lw": 0, "hatch": "ooo..."},
        "green": {"fc": "#D0F1BF", "ec": "#2F3737", "lw": 1},
        "forest": {"fc": "#64B96A", "ec": "#2F3737", "lw": 1},
        "water": {
            "fc": "#a1e3ff", "ec": "#2F3737",
            "hatch": "ooo...", "hatch_c": "#85c9e6", "lw": 1,
        },
        "parking": {"fc": "#F2F4CB", "ec": "#2F3737", "lw": 1},
        "streets": {"fc": "#2F3737", "ec": "#475657", "alpha": 1, "lw": 0},
        "building": {
            "palette": ["#FFC857", "#E9724C", "#C5283D"],
            "ec": "#2F3737", "lw": 0.5,
        },
    },
)

Macau, custom parameters

Plot an entire region

To plot an entire region (not just a rectangular or circular area), set radius=False:

plot = prettymaps.plot('Bom Fim, Porto Alegre, Brasil', radius=False)

Bom Fim, Porto Alegre

Access GeoDataFrames directly

You can access a layer's GeoDataFrame directly:

plot = prettymaps.plot('Centro Histórico, Porto Alegre', show=False)
plot.geodataframes['building']

Search by name

Search a building by name and display it:

plot.geodataframes['building'][
    plot.geodataframes['building'].name
    == 'Catedral Metropolitana Nossa Senhora Mãe de Deus'
].geometry[0]

Mosaic of building footprints

import numpy as np
import osmnx as ox
from matplotlib import pyplot as plt

plot = prettymaps.plot('Porto Alegre', show=False)
buildings = plot.geodataframes['building']
buildings = ox.projection.project_gdf(buildings)
buildings = [b for b in buildings.geometry if b.area > 0]

n = 6
fig, axes = plt.subplots(n, n, figsize=(7, 6))
fig.patch.set_facecolor('#5cc0eb')
fig.suptitle('Buildings of Porto Alegre', size=25, color='#fff')
for ax, building in zip(np.concatenate(axes), buildings):
    ax.plot(*building.exterior.xy, c='#ffffff')
    ax.autoscale(); ax.axis('off'); ax.axis('equal')

Buildings of Porto Alegre mosaic

Customizing the matplotlib axes

Access plot.ax or plot.fig to add new elements to the matplotlib plot:

plot = prettymaps.plot(
    (41.39491, 2.17557),
    preset='barcelona',
    show=False,
)

plot.fig.patch.set_facecolor('#F2F4CB')
plot.ax.set_title('Barcelona', font='serif', size=50)

Plotter mode

Use plotter mode to export a pen plotter-compatible SVG (thanks to abey79's amazing vsketch library):

plot = prettymaps.plot(
    (41.39491, 2.17557),
    mode='plotter',
    layers=dict(perimeter={}),
    preset='barcelona-plotter',
    scale_x=0.6,
    scale_y=-0.6,
)

Barcelona plotter

Other examples

plot = prettymaps.plot(
    'Barra da Tijuca',
    dilate=0,
    figsize=(22, 10),
    preset='tijuca',
    adjust_aspect_ratio=False,
)

Barra da Tijuca, tijuca preset

Create a preset

Use prettymaps.create_preset() to create a preset:

prettymaps.create_preset(
    "my-preset",
    layers={
        "building": {
            "tags": {"building": True, "leisure": ["track", "pitch"]},
        },
        "streets": {
            "width": {
                "trunk": 6, "primary": 6, "secondary": 5,
                "tertiary": 4, "residential": 3.5,
                "pedestrian": 3, "footway": 3, "path": 3,
            }
        },
    },
    style={
        "perimeter": {"fill": False, "lw": 0, "zorder": 0},
        "streets": {"fc": "#F1E6D0", "ec": "#2F3737", "lw": 1.5, "zorder": 3},
        "building": {"palette": ["#fff"], "ec": "#2F3737", "lw": 1, "zorder": 4},
    },
)

prettymaps.preset('my-preset')

Multiplot

Use prettymaps.multiplot and prettymaps.Subplot to draw multiple regions on the same canvas:

plot = prettymaps.multiplot(
    prettymaps.Subplot(
        'Cidade Baixa, Porto Alegre',
        style={'building': {'palette': ['#49392C', '#E1F2FE', '#98D2EB']}},
    ),
    prettymaps.Subplot(
        'Bom Fim, Porto Alegre',
        style={'building': {'palette': ['#BA2D0B', '#D5F2E3', '#73BA9B', '#F79D5C']}},
    ),
    prettymaps.Subplot(
        'Farroupilha, Porto Alegre',
        layers={'building': {'tags': {'building': True}}},
        style={'building': {'palette': ['#EEE4E1', '#E7D8C9', '#E6BEAE']}},
    ),
    preset='cb-bf-f',
    figsize=(12, 12),
)

Porto Alegre multiplot

Add hillshade

plot = prettymaps.plot(
    'Honolulu',
    radius=5500,
    figsize='a4',
    layers={
        'hillshade': {
            'azdeg': 315,
            'altdeg': 45,
            'vert_exag': 1,
            'dx': 1,
            'dy': 1,
            'alpha': 0.75,
        },
    },
)

Honolulu hillshade

Add keypoints

plot = prettymaps.plot(
    'Garopaba',
    radius=5000,
    figsize='a4',
    layers={'building': False},
    keypoints={
        'tags': {'natural': ['beach']},
        'specific': {
            'pedra branca': {'tags': {'natural': ['peak']}},
        },
    },
)

Garopaba keypoints