# How to Structure Preset JSON Files and Create Custom Presets in prettymaps

> Learn how to structure preset JSON files and create custom presets in prettymaps. Discover how to define layers, styles, and geometric parameters for reusable map configurations.

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

---

**`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`](https://github.com/marceloprates/prettymaps/blob/main/prettymaps/draw.py), the `read_preset()` and `preset()` functions expect this exact structure:

```json
{
    "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`](https://github.com/marceloprates/prettymaps/blob/main/default.json) | General-purpose urban mapping | Full layer stack (water, buildings, green space, streets); cohesive color palette; `radius: 500` |
| [`minimal.json`](https://github.com/marceloprates/prettymaps/blob/main/minimal.json) | Lightweight rendering | Stripped to `perimeter`, `streets`, and `building` only; white background for contrast |
| [`tijuca.json`](https://github.com/marceloprates/prettymaps/blob/main/tijuca.json) | Neighborhood-specific demo | Custom styling tuned for dense urban forest contexts |

Examining [`default.json`](https://github.com/marceloprates/prettymaps/blob/main/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`](https://github.com/marceloprates/prettymaps/blob/main/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

```python
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:

```python

# 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`](https://github.com/marceloprates/prettymaps/blob/main/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:

```python
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:

```python

# 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`](https://github.com/marceloprates/prettymaps/blob/main/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`](https://github.com/marceloprates/prettymaps/blob/main/default.json), [`minimal.json`](https://github.com/marceloprates/prettymaps/blob/main/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`](https://github.com/marceloprates/prettymaps/blob/main/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`](https://github.com/marceloprates/prettymaps/blob/main/tests/test.py).