How to Structure Preset JSON Files and Create Custom Presets in prettymaps
prettymaps stores reusable map configurations in JSON files within the prettymaps/presets/ directory, containing layers, styles, and geometric parameters that can be loaded by name or created programmatically via create_preset().
The prettymaps library simplifies geospatial visualization by bundling OpenStreetMap data fetching with Matplotlib-powered styling. Presets are the mechanism that makes this reproducible—they encapsulate which OSM layers to query, how to render them, and what boundary geometry to apply. This article explains the JSON schema governing these files and demonstrates how to build your own presets using the native API.
Preset JSON File Structure
Every preset file adheres to a consistent five-key schema. According to the source code in prettymaps/draw.py, the read_preset() and preset() functions expect this exact structure:
{
"layers": {},
"style": {},
"circle": null,
"radius": 500,
"dilate": null
}
The Five Top-Level Keys
-
layers– Defines which OSM features to fetch. Each sub-key (e.g.,"water","building","streets") maps to a dictionary with optionaltags(OSM filter expressions) andwidth(road-type line widths). -
style– Specifies Matplotlib-compatible visual properties per layer. Common keys includefc(fill color),ec(edge color),lw(line width),hatch,zorder, andpalette. -
circle– Boolean flag; whentrue, clips the map to a circular boundary. -
radius– Integer in meters defining the boundary extent from the center point. -
dilate– Optional buffer distance in meters to expand the query area before clipping.
Built-in Preset Examples
The repository ships with multiple reference implementations in prettymaps/presets/:
| Preset | Purpose | Notable Characteristics |
|---|---|---|
default.json |
General-purpose urban mapping | Full layer stack (water, buildings, green space, streets); cohesive color palette; radius: 500 |
minimal.json |
Lightweight rendering | Stripped to perimeter, streets, and building only; white background for contrast |
tijuca.json |
Neighborhood-specific demo | Custom styling tuned for dense urban forest contexts |
Examining default.json reveals how layers and style work in tandem—the water layer queries OSM with {"natural": ["water", "bay"]} tags, while its style entry sets {"fc": "#a0c8f0", "ec": "#236699", "lw": 1.0} to achieve the characteristic blue fill with darker edges.
Core Preset Functions in draw.py
The preset lifecycle is managed by seven functions centralized in prettymaps/draw.py (lines 698–880):
-
presets_directory()(lines 702–711) – Returns the absolute path to the presets folder, enabling portable path resolution across installations. -
create_preset()(lines 714–750) – Serializes a Python dictionary to JSON, performing validation and atomic writes to<name>.json. -
read_preset()(lines 752–767) – Loads a preset file and returns the raw dictionary; raisesFileNotFoundErrorfor missing presets. -
manage_presets()(lines 698–825) – The orchestration hub called byplot(); handles thepreset,save_preset, andupdate_presetarguments to load, persist, or modify configurations. -
presets()(lines 828–842) – Returns a pandas DataFrame enumerating all available preset names with their full JSON content for interactive exploration. -
preset()(lines 846–851) – Wrapsread_preset()output in aPresetdataclass, enabling attribute-style access toparams. -
override_preset()(lines 853–880) – Deep-merges a loaded preset with runtime arguments passed toplot(), ensuring user overrides take precedence.
Creating Custom Presets Programmatically
The create_preset() function eliminates manual JSON editing. It accepts the same parameter names used by plot() and writes a validated file to the presets directory.
Step-by-Step Custom Preset Creation
import prettymaps as pm
# Define layer configuration — which OSM features to query
custom_layers = {
"perimeter": {},
"building": {
"tags": {"building": True}
},
"water": {
"tags": {"natural": ["water", "bay", "spring"]}
},
"streets": {
"tags": {
"highway": [
"residential", "primary", "secondary",
"tertiary", "footway"
]
},
"width": {
"residential": 2,
"primary": 4,
"secondary": 3,
"tertiary": 2.5,
"footway": 1
}
}
}
# Define style configuration — how features render
custom_style = {
"perimeter": {"fill": False, "linewidth": 0},
"building": {
"fc": "#2a2a2a",
"ec": "#1a1a1a",
"linewidth": 0.5,
"zorder": 4
},
"water": {
"fc": "#1e3a5f",
"ec": "#0d1f33",
"hatch": "///",
"zorder": 2
},
"streets": {
"fc": "#f0f0f0",
"ec": "#cccccc",
"linewidth": 0.8,
"zorder": 3
}
}
# Serialize to JSON preset file
pm.create_preset(
name="dark-urban",
layers=custom_layers,
style=custom_style,
circle=True,
radius=750,
dilate=50
)
# Verify creation
print(pm.presets()[pm.presets()["name"] == "dark-urban"])
Using the Custom Preset
Once created, the preset integrates seamlessly into the plot() pipeline:
# Load by name — manage_presets() calls read_preset() and override_preset()
plot = pm.plot(
"Shibuya, Tokyo, Japan",
preset="dark-urban",
save_preset=False, # prevent overwriting with runtime args
show=False
)
plot.fig.savefig("shibuya-dark.png", dpi=300, bbox_inches="tight")
The preset argument triggers manage_presets() in draw.py, which chains through read_preset() → preset() → override_preset() before passing the merged configuration to the rendering engine.
Advanced Preset Patterns
Generating Thematic Preset Families
Because create_preset() accepts plain dictionaries, you can automate preset generation:
import prettymaps as pm
palettes = ["viridis", "plasma", "magma", "cividis"]
for palette in palettes:
style = {
"building": {
"palette": palette,
"ec": "#000000",
"linewidth": 0.3
}
}
pm.create_preset(
name=f"heatmap-{palette}",
layers={"building": {"tags": {"building": True}}},
style=style,
radius=1000
)
Inspecting and Modifying Existing Presets
The API supports read-modify-write workflows for iterative refinement:
# Load existing preset as mutable dictionary
base = pm.preset("default").params
# Modify specific layer style
base["style"]["building"]["fc"] = "#e8d4b8" # warm sandstone
# Save as new variant
pm.create_preset(
name="default-warm",
layers=base["layers"],
style=base["style"],
circle=base.get("circle"),
radius=base.get("radius"),
dilate=base.get("dilate")
)
Preset Resolution Order in plot()
Understanding how prettymaps resolves configuration conflicts helps debug unexpected renderings. The override_preset() function (lines 853–880) implements this precedence:
- Base preset values from the JSON file
- Runtime overrides passed directly to
plot()(e.g.,radius=1200overrides preset radius) - Dynamic query parameters computed from the location string
This merge strategy ensures presets remain reusable while allowing situational adjustments.
Summary
- Preset JSON files live in
prettymaps/presets/and requirelayers,style,circle,radius, anddilatekeys. - Seven functions in
draw.pymanage the preset lifecycle, frompresets_directory()path resolution tooverride_preset()argument merging. create_preset()enables programmatic preset authorship without manual JSON editing.- Custom presets load via the
presetargument inplot(), with runtime parameters overriding stored values throughmanage_presets(). - Built-in presets (
default.json,minimal.json) serve as canonical reference implementations for layer tagging and visual styling.
Frequently Asked Questions
What happens if I specify both a preset and explicit arguments to plot()?
Explicit arguments take precedence. The override_preset() function in draw.py (lines 853–880) performs a recursive dictionary merge where user-supplied values overwrite preset values. This allows presets to serve as defaults while remaining customizable per-render.
Can I store presets outside the prettymaps installation directory?
The current implementation in presets_directory() (lines 702–711) returns a path relative to the package installation. To use external presets, load them manually with read_preset() on a file path, then pass the resulting dictionary to plot() directly rather than using the preset string argument.
How do I share custom presets with collaborators?
Since presets are standard JSON files, distribute the .json file and instruct recipients to place it in their prettymaps/presets/ directory (locatable via pm.presets_directory()). Alternatively, embed the preset dictionary directly in shared notebooks, calling create_preset() at runtime to ensure environment consistency.
Why does my custom preset fail to load with a KeyError?
The preset JSON must include all five top-level keys: layers, style, circle, radius, and dilate. Missing keys cause failures in read_preset() or preset(). Use pm.preset('default').params as a template, or validate your JSON against the schema in the prettymaps test suite at tests/test.py.
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 →