Sprite Generation
Overview
Sprites are combined image sheets containing all map icons. Mapbox GL loads a single sprite sheet to render POI icons, road shields, and other symbols efficiently. The sprite generator is a Go library with Python bindings via ctypes.
Pipeline
graph LR
A[SVG icons + manifest.json] --> B[Layout]
B --> C[Render]
C --> D["sprite.png / sprite@2x.png"]
C --> E["sprite.json / sprite@2x.json"]
1. Icon library
The input is an .iconLibrary directory:
woosmap.iconLibrary/
├── manifest.json # Which icons to include and how
├── icons/ # SVG source icons (rendered as SDF)
└── raster/ # PNG icons (placed as-is)
2. Layout (bin-packing)
Icons are packed into a sprite sheet using a bin-packing algorithm. For stretchable icons (like road shields), the layout step also computes 9-patch metadata (content, stretchX, stretchY) so the icon can stretch to fit text labels with icon-text-fit: "both".
3. Render
For each icon in the packed layout:
- Draw casing at alpha ≈ 0.25 (the background shape — circle, rect, or filled)
- Rasterize SVG at alpha = 1.0 (the foreground icon)
- Raster (PNG) icons are placed directly without SDF processing
This alpha separation is what enables the dual-color trick.
4. Output
| File | Description |
|---|---|
sprite.png |
1x sprite sheet |
sprite@2x.png |
2x (Retina) sprite sheet |
sprite.json |
1x sprite metadata (positions, sizes, SDF flag) |
sprite@2x.json |
2x sprite metadata |
manifest.json
The manifest lists all icons to include with their rendering options:
[
{"name": "restaurant", "raster": false},
{"name": "road", "raster": false, "stretchable": true, "cornerInset": 3.0, "padding": 0, "casing": "filled"},
{"name": "logo", "raster": true}
]
| Field | Description |
|---|---|
name |
Icon filename (without extension) |
raster |
true for PNG icons, false for SVG/SDF |
stretchable |
Enables 9-patch stretching |
cornerInset |
Corner radius for stretchable content area and Filled casing |
padding |
Padding around the icon content |
casing |
Override casing type: "circle", "square", "filled" |
Casing types
Each SDF icon is rendered with a background casing shape drawn at low alpha (≈ 0.25). At runtime Mapbox GL maps icon-halo-color to the casing and icon-color to the foreground — see SDF & Dual-Color Trick.
| Kind | Manifest value | Description |
|---|---|---|
| None | — | No casing |
| Rect | "square" |
Rounded rectangle, hardcoded 4px corner radius |
| Circle | "circle" |
Circle (default for POI icons) |
| Filled | "filled" |
Rounded rectangle using cornerInset from manifest as corner radius |
Casing resolution order
The renderer picks the casing for each icon in this order:
- Manifest override — if the icon entry has a
"casing"field, use it - Hardcoded map — a few icons (e.g.
bus) have built-in casing types in the Go code - Default —
Circleif nothing else matches
Python API
Building the manifest
"""Building the icon manifest for sprite generation."""
from map_style.generator.sprite import build_required_icons
required = build_required_icons(
icon_names={"restaurant", "road"},
raster_icons=set(),
stretchable_icons={"road": 3.0},
casing_overrides={"road": "filled"},
)
Generating sprites
"""Creating an icon library and generating sprites."""
from pathlib import Path
from map_style.generator.sprite import SpriteGenerator, build_required_icons
gen = SpriteGenerator()
required = build_required_icons(icon_names={"restaurant", "road"})
# Create icon library from source icons
gen.create_icon_library(
icon_library_path=Path("woosmap.iconLibrary"),
required_icons=required,
icons_source_dir=Path("icons/"),
raster_source_dir=Path("raster/"),
)
# Generate sprite sheet
gen.generate_sprite(
icon_library_path=Path("woosmap.iconLibrary"),
output_dir=Path("output/"),
)
Slicing an existing sprite
"""Slicing an existing sprite into individual icons."""
from pathlib import Path
from map_style.generator.sprite import SpriteGenerator
gen = SpriteGenerator()
gen.slice_sprite(
sprite_path=Path("sprite@2x.png"),
output_dir=Path("sliced/"),
)
Native library
The Python bindings use ctypes to call the Go shared library. The library is searched in order:
PYSPRITE_LIBenvironment variablesprite/dist/libpysprite.{so,dylib,dll}generator/lib/libpysprite.{so,dylib,dll}
Build it locally:
make lib
Next
- SDF & Dual-Color Trick — how SDF rendering enables two-tone icons