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 optional tags (OSM filter expressions) and width (road-type line widths).

  • style – Specifies Matplotlib-compatible visual properties per layer. Common keys include fc (fill color), ec (edge color), lw (line width), hatch, zorder, and palette.

  • circle – Boolean flag; when true, 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; raises FileNotFoundError for missing presets.

  • manage_presets() (lines 698–825) – The orchestration hub called by plot(); handles the preset, save_preset, and update_preset arguments 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) – Wraps read_preset() output in a Preset dataclass, enabling attribute-style access to params.

  • override_preset() (lines 853–880) – Deep-merges a loaded preset with runtime arguments passed to plot(), 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:

  1. Base preset values from the JSON file
  2. Runtime overrides passed directly to plot() (e.g., radius=1200 overrides preset radius)
  3. 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 require layers, style, circle, radius, and dilate keys.
  • Seven functions in draw.py manage the preset lifecycle, from presets_directory() path resolution to override_preset() argument merging.
  • create_preset() enables programmatic preset authorship without manual JSON editing.
  • Custom presets load via the preset argument in plot(), with runtime parameters overriding stored values through manage_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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →