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

364 lines
8.9 KiB
Markdown

# prettymaps Tutorial
A minimal Python library to draw customized maps from [OpenStreetMap](https://www.openstreetmap.org) created using the [osmnx](https://github.com/gboeing/osmnx), [matplotlib](https://matplotlib.org/), [shapely](https://shapely.readthedocs.io) and [vsketch](https://github.com/abey79/vsketch) packages.
![Heerhugowaard sample](img/tour-01-heerhugowaard.png)
This tutorial is generated from [`notebooks/tutorial.py`](../notebooks/tutorial.py), a marimo notebook. To run it interactively:
```sh
uv run --with marimo marimo edit notebooks/tutorial.py
```
Or with the locally installed copy:
```sh
.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:
```python
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
```python
import prettymaps
plot = prettymaps.plot('Stad van de Zon, Heerhugowaard, Netherlands')
```
![Heerhugowaard, default preset](img/tour-01-heerhugowaard.png)
### Presets
You can also choose from different "presets" (parameter combinations saved in JSON files). See below an example using the "minimal" preset:
```python
plot = prettymaps.plot(
'Stad van de Zon, Heerhugowaard, Netherlands',
preset='minimal',
)
```
![Heerhugowaard, minimal preset](img/tour-02-minimal-preset.png)
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
```python
plot = prettymaps.plot(
your_query,
layers,
style,
preset,
save_preset,
update_preset,
circle,
radius,
dilate,
)
```
`plot` is a Python dataclass containing:
```python
@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):
```python
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](img/tour-03-macau-custom.png)
### Plot an entire region
To plot an entire region (not just a rectangular or circular area), set `radius=False`:
```python
plot = prettymaps.plot('Bom Fim, Porto Alegre, Brasil', radius=False)
```
![Bom Fim, Porto Alegre](img/tour-04-bomfim-region.png)
### Access GeoDataFrames directly
You can access a layer's GeoDataFrame directly:
```python
plot = prettymaps.plot('Centro Histórico, Porto Alegre', show=False)
plot.geodataframes['building']
```
### Search by name
Search a building by name and display it:
```python
plot.geodataframes['building'][
plot.geodataframes['building'].name
== 'Catedral Metropolitana Nossa Senhora Mãe de Deus'
].geometry[0]
```
### Mosaic of building footprints
```python
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](img/tour-05-mosaic.png)
### Customizing the matplotlib axes
Access `plot.ax` or `plot.fig` to add new elements to the matplotlib plot:
```python
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](https://github.com/abey79/vsketch) library):
```python
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](img/tour-06-barcelona-plotter.png)
### Other examples
```python
plot = prettymaps.plot(
'Barra da Tijuca',
dilate=0,
figsize=(22, 10),
preset='tijuca',
adjust_aspect_ratio=False,
)
```
![Barra da Tijuca, tijuca preset](img/tour-07-tijuca.png)
### Create a preset
Use `prettymaps.create_preset()` to create a preset:
```python
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:
```python
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](img/tour-08-multiplot.png)
### Add hillshade
```python
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](img/tour-09-honolulu-hillshade.png)
### Add keypoints
```python
plot = prettymaps.plot(
'Garopaba',
radius=5000,
figsize='a4',
layers={'building': False},
keypoints={
'tags': {'natural': ['beach']},
'specific': {
'pedra branca': {'tags': {'natural': ['peak']}},
},
},
)
```
![Garopaba keypoints](img/tour-10-garopaba-keypoints.png)