How the Streamlit app.py Frontend Provides an Interactive Interface for Prettymaps
The Prettymaps Streamlit frontend uses 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 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 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 lines 18-50 |
| State management | Persists generated images and file paths in st.session_state to prevent redundant re-rendering |
app.py lines 25-28 |
| Map generation | Collects widget values, invokes prettymaps.plot(), and serializes outputs |
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:
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 forprettymaps.plot())st.checkbox("Circular map")— toggles circular crop versus rectangular boundsst.selectbox("Preset", presets["preset"])— selects predefined style configurations- Dynamic color pickers — builds
custom_palettedictionary for building colors st.selectbox("Page size")andst.number_input("DPI", 50, 1200)— controlsfigsizepassed to Matplotlib
Layer Toggles
A dictionary of checkboxes enables selective rendering of OSM features:
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:
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
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
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
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 lines 61-73, which handles OSM data retrieval via OSMnX, preset application, and layered rendering.
Step 4: Serialize and Cache Results
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:
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:
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:
- Widget change → Streamlit re-runs
app.py, preservingsession_statevalues - Generate click → Heavy
prettymaps.plot()executes once, results cached - State update →
st.session_statereceives new image buffer and file paths - UI refresh → Image display and download buttons activate automatically
- 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 logic for custom deployments:
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 |
Streamlit frontend implementation — UI widgets, session handling, download logic |
prettymaps/draw.py |
Core rendering engine — plot() function for OSM fetching and layer drawing |
prettymaps/__init__.py |
Public API exports — plot(), preset(), presets() |
prettymaps/presets/*.json |
Style configurations — color schemes, layer defaults, visual themes |
prettymaps/utils.py |
Timing utilities and logging helpers |
Summary
- The Prettymaps Streamlit frontend in
app.pyacts as a thin controller around the core library, not a reimplementation st.session_statecaches 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_buttonfor browser-native downloads - All heavy computation—OSM queries, geometry processing, Matplotlib rendering—delegates to
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 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 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 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.
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 →