# How the Streamlit app.py Frontend Provides an Interactive Interface for Prettymaps

> Discover how the Streamlit app.py frontend in Prettymaps delivers an interactive interface with reactive widgets, caching, and export options for stunning map visualizations.

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

---

**The Prettymaps Streamlit frontend uses [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py) to wrap the core library with reactive UI widgets, session state for caching generated maps, and download buttons for PNG/SVG export—delegating heavy rendering to `prettymaps.plot()` while keeping the interface responsive.**

The `streamlit` app in the Prettymaps repository transforms the Python plotting library into a browser-based map design tool. Located at [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py) in the repository root, this single-file frontend bridges user interactions with the underlying `prettymaps` package, enabling non-coders to generate stylized OpenStreetMap visualizations through point-and-click controls.

## Architecture Overview of the Prettymaps Streamlit Frontend

The [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py) implementation follows a three-layer architecture that separates presentation, state, and execution concerns:

| Layer | Responsibility | Key Code Location |
|-------|---------------|-------------------|
| **UI definition** | Renders input widgets, preset selectors, and download buttons | [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py) lines 18-50 |
| **State management** | Persists generated images and file paths in `st.session_state` to prevent redundant re-rendering | [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py) lines 25-28 |
| **Map generation** | Collects widget values, invokes `prettymaps.plot()`, and serializes outputs | [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py) lines 45-71 |

This separation ensures that adjusting a slider or color picker does not trigger expensive OSM data fetching—only clicking the **Generate** button does.

## Page Configuration and Preset Loading

The frontend initializes with explicit Streamlit configuration to maximize usable screen space:

```python
st.set_page_config(layout="wide")
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
import prettymaps
presets = prettymaps.presets().to_dict()
st.title("prettymaps")

```

The `sys.path` manipulation ensures the local `prettymaps` package takes precedence over any installed version. The `presets` dictionary loads JSON configurations from `prettymaps/presets/*.json`, populating the preset dropdown with curated map styles like `"minimal"` or `"macao"`.

## Input Widgets and Layer Controls

The interface splits into two columns via `cols = st.columns([1, 2])`, allocating one-third width to controls and two-thirds to output.

### Left Column: Map Parameters

The control panel exposes these **Streamlit widgets**:

- `st.text_area("Location")` — OSM query string (e.g., `"Paris, France"`)
- `st.slider("Radius", 0.1, 5.0)` — map radius in kilometers (converted to meters for `prettymaps.plot()`)
- `st.checkbox("Circular map")` — toggles circular crop versus rectangular bounds
- `st.selectbox("Preset", presets["preset"])` — selects predefined style configurations
- Dynamic color pickers — builds `custom_palette` dictionary for building colors
- `st.selectbox("Page size")` and `st.number_input("DPI", 50, 1200)` — controls `figsize` passed to Matplotlib

### Layer Toggles

A dictionary of checkboxes enables selective rendering of OSM features:

```python
layers = {
    "building": st.checkbox("Buildings", value=True),
    "water": st.checkbox("Water", value=True),
    "forest": st.checkbox("Forest", value=True),
    # ... additional layers

}

```

Each checkbox value determines whether the layer appears in the final map. Unchecked layers get value `False` (suppressed); checked layers receive empty dictionaries `{}` (enabled with default styling).

## Map Generation Trigger

The **Generate** button sits in the right column with primary styling:

```python
button = st.button(
    "Generate",
    key="generate_map",
    help="Click to generate the map",
    type="primary",
    icon=":material/map:",
    use_container_width=True,
)

```

When activated, the callback executes this sequence:

### Step 1: Prepare Layer Dictionary

```python
layers_dict = {k: (False if v == False else {}) for k, v in layers.items()}

```

This transforms checkbox booleans into the format `prettymaps.plot()` expects—`False` to disable, `{}` to enable with defaults.

### Step 2: Create Matplotlib Figure

```python
fig, ax = plt.subplots(figsize=(width, height), dpi=300)

```

The `figsize` tuple derives from page size selection and DPI input.

### Step 3: Invoke Core Library

```python
prettymaps.plot(
    query,
    radius=1000 * radius,  # km to meters

    circle=circular,
    layers=layers_dict,
    style={"building": {"palette": list(custom_palette.values())}},
    figsize=(width, height),
    preset=selected_preset,
    show=False,
    ax=ax,
)

```

This calls the main entry point in [`prettymaps/draw.py`](https://github.com/marceloprates/prettymaps/blob/main/prettymaps/draw.py) lines 61-73, which handles OSM data retrieval via OSMnX, preset application, and layered rendering.

### Step 4: Serialize and Cache Results

```python
buf = io.BytesIO()
plt.savefig(buf, format="png", bbox_inches="tight", dpi=300)
buf.seek(0)

# Write temporary files for download

with open("/tmp/generated_map.png", "wb") as f:
    f.write(buf.getvalue())

# Generate SVG via helper

download_svg(fig, "/tmp/generated_map_download.svg")

# Update session state

st.session_state["last_image"] = buf
st.session_state["last_png_path"] = "/tmp/generated_map.png"
st.session_state["last_svg_path"] = "/tmp/generated_map_download.svg"

```

The `io.BytesIO` buffer enables immediate display without filesystem I/O; temporary files satisfy Streamlit's `st.download_button` requirements.

## Download Buttons and Image Display

Two download buttons appear below the generate button, conditionally enabled:

```python
png_ready = "last_png_path" in st.session_state and os.path.exists(st.session_state["last_png_path"])
svg_ready = "last_svg_path" in st.session_state and os.path.exists(st.session_state["last_svg_path"])

st.download_button("Download PNG", open(st.session_state["last_png_path"], "rb"), disabled=not png_ready)
st.download_button("Download SVG", open(st.session_state["last_svg_path"], "rb"), disabled=not svg_ready)

```

The generated map renders via:

```python
st.image(st.session_state.last_image, use_container_width=True)

```

Using `use_container_width=True` ensures responsive scaling across devices.

## Reactive Interaction Flow

The **Prettymaps Streamlit frontend** implements this execution model:

1. **Widget change** → Streamlit re-runs [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py), preserving `session_state` values
2. **Generate click** → Heavy `prettymaps.plot()` executes once, results cached
3. **State update** → `st.session_state` receives new image buffer and file paths
4. **UI refresh** → Image display and download buttons activate automatically
5. **Parameter adjustment** → Clears previous buffer implicitly (re-execution), requiring new generation

This pattern—computation on demand with full-result caching—eliminates lag during exploration while maintaining output persistence.

## Code Example: Minimal Reproduction

This condensed snippet mirrors the [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py) logic for custom deployments:

```python
import streamlit as st
import prettymaps
import io
import matplotlib.pyplot as plt

st.set_page_config(layout="wide")
st.title("prettymaps demo")

# Widgets

query = st.text_input("Location", "Heerhugowaard, Netherlands")
radius = st.slider("Radius (km)", 0.5, 2.0, 0.75)
circular = st.checkbox("Circular map")
preset = st.selectbox("Preset", list(prettymaps.presets()["preset"]))

# Generation

if st.button("Generate"):
    fig, ax = plt.subplots(figsize=(8, 8), dpi=200)
    prettymaps.plot(
        query,
        radius=1000 * radius,
        circle=circular,
        preset=preset,
        ax=ax,
        show=False,
    )
    buf = io.BytesIO()
    plt.savefig(buf, format="png", bbox_inches="tight")
    buf.seek(0)
    st.image(buf)
    st.download_button("Download", buf, file_name="map.png")

```

## Key Source Files in the Prettymaps Repository

| File | Purpose |
|------|---------|
| [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py) | Streamlit frontend implementation — UI widgets, session handling, download logic |
| [`prettymaps/draw.py`](https://github.com/marceloprates/prettymaps/blob/main/prettymaps/draw.py) | Core rendering engine — `plot()` function for OSM fetching and layer drawing |
| [`prettymaps/__init__.py`](https://github.com/marceloprates/prettymaps/blob/main/prettymaps/__init__.py) | Public API exports — `plot()`, `preset()`, `presets()` |
| `prettymaps/presets/*.json` | Style configurations — color schemes, layer defaults, visual themes |
| [`prettymaps/utils.py`](https://github.com/marceloprates/prettymaps/blob/main/prettymaps/utils.py) | Timing utilities and logging helpers |

## Summary

- The **Prettymaps Streamlit frontend** in [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py) acts as a thin controller around the core library, not a reimplementation
- **`st.session_state`** caches generated maps, enabling download buttons and preventing re-rendering on widget adjustments
- **Layer toggles** and **preset selection** expose the full flexibility of `prettymaps.plot()` through intuitive widgets
- **PNG and SVG export** uses temporary files bridged to `st.download_button` for browser-native downloads
- All heavy computation—OSM queries, geometry processing, Matplotlib rendering—delegates to [`prettymaps/draw.py`](https://github.com/marceloprates/prettymaps/blob/main/prettymaps/draw.py)

## Frequently Asked Questions

### How does the Prettymaps Streamlit frontend prevent duplicate map generation?

The frontend uses **`st.session_state`** to store the PNG buffer and file paths after the first successful `prettymaps.plot()` call. When users adjust widgets, Streamlit re-executes [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py) but preserves these cached values. Only clicking **Generate** again clears and replaces the cached output, ensuring expensive rendering runs exactly when requested.

### Can I customize the color palette beyond the preset options?

Yes—the [`app.py`](https://github.com/marceloprates/prettymaps/blob/main/app.py) interface includes dynamic color pickers that build a `custom_palette` dictionary. These values pass into `prettymaps.plot()` via the `style` parameter: `style={"building": {"palette": list(custom_palette.values())}}`. You can extend this pattern in your own Streamlit apps by adding more color inputs for additional layers like `water` or `forest`.

### Where does the actual map rendering happen in the codebase?

The heavy lifting occurs in **[`prettymaps/draw.py`](https://github.com/marceloprates/prettymaps/blob/main/prettymaps/draw.py)** specifically in the `plot()` function at lines 61-73. This function coordinates OSM data fetching through OSMnX, applies preset configurations, renders individual layers, and returns a `Plot` object. The Streamlit frontend merely collects inputs and calls this core function.

### How does the frontend handle SVG export differently from PNG?

PNG generation uses **`io.BytesIO`** for in-memory buffering with `matplotlib.pyplot.savefig()`, while SVG export calls a separate **`download_svg()`** helper that writes directly to `/tmp/generated_map_download.svg`. This distinction exists because Matplotlib's SVG backend requires filesystem access for proper metadata embedding, whereas PNG can stream entirely in memory.