Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Geographic Marks

Prism ships two geo-aware marks for choropleth maps and georeferenced overlays. Both consume a projection block on the spec and resolve boundary geometry from an embedded geodata catalog — no external tile server, no runtime network dependency for the host CLI.

Marks

MarkChannelsPurpose
geoshapefeature (+ optional color)Country / admin-1 polygon (choropleth).
geopointlongitude, latitude (+ optional color, size)Point overlay (cities, events, sensors).

Spec shape

A basemap (every country in the catalog, no data binding) is one line of data:

{
  "$schema": "urn:prism:schema:v1:spec",
  "data": {"feature_collection": {"tier": "world-110m"}},
  "mark": "geoshape",
  "projection": {"type": "naturalearth"},
  "encoding": {"feature": {"field": "id", "type": "nominal"}}
}

data.feature_collection synthesizes one row per feature in the tier — the resulting table carries id, name, and parent columns (parent is the admin-0 ISO 3166-1 alpha-3 for admin-1 entries, empty otherwise). Combine with a filter transform to subset:

{
  "data": {"feature_collection": {"tier": "admin1-50m"}},
  "transform": [{"filter": "parent == \"USA\""}],
  "mark": "geoshape",
  "projection": {"type": "albers_usa", "tier": "admin1-50m"},
  "encoding": {"feature": {"field": "id", "type": "nominal"}}
}

For a choropleth, bind your own data and a color channel:

{
  "data": {"source": "country_metrics.pulse"},
  "mark": "geoshape",
  "projection": {"type": "naturalearth"},
  "encoding": {
    "feature": {"field": "iso_a3", "type": "nominal"},
    "color":   {"field": "gdp_per_capita", "type": "quantitative"}
  }
}

The feature channel binds a table column whose values are feature IDs from the geodata catalog. Admin-0 (countries) uses ISO 3166-1 alpha-3 (USA, CAN, GBR); admin-1 (states/provinces) uses ISO 3166-2 (US-CA, CA-ON, GB-ENG). Rows whose id doesn’t match a manifest entry raise PRISM_GEO_001.

Projections

TypeUse case
mercatorClassic web map default. Distorts area near poles; clips above ±85°.
equirectangularPlate carrée. Linear lat/lon → x/y. Useful for heatmaps over geographic grids.
naturalearthTom Patterson’s compromise projection. Smooth global view, low distortion.
albers_usaComposite Albers covering CONUS + Alaska + Hawaii in inset panels.
orthographicGlobe view. Honours rotate: [lambda, phi, gamma] for the view direction.

Per-projection parameters:

{
  "projection": {
    "type": "albers_usa",
    "scale": 1200,
    "translate": [400, 250]
  }
}

Leave scale / translate unset and Prism auto-fits the projection to the requested tier’s bounding box inside the plot rectangle.

Tiers

The geodata catalog ships three tiers:

TierCoverageApprox. on-disk size
world-110mCountries (admin-0) at 1:110m. Default.~200 KB gz
world-50mCountries (admin-0) at 1:50m. Smoother coastlines.~600 KB gz
admin1-50mStates / provinces (admin-1) at 1:50m.~5 MB gz

Select the tier the encoder pulls from:

{
  "projection": {"type": "mercator", "tier": "admin1-50m"}
}

The committed tier files carry 177 countries (110m), 242 countries (50m), and 294 admin-1 regions (50m) sourced from Natural Earth via make geodata. Tier files use a custom compact JSON shape with 3-decimal quantization; geodata/decoder.go documents the wire format. make build itself requires no network — the committed artifacts are the input.

Host CLI vs WASM

Host build (CLI / library): every tier is embedded in the binary via //go:embed. Geoshape charts work out-of-the-box; no sideload required.

WASM build (browser): only the manifest is embedded (~100 KB). The runtime fetches the tier file from ${origin}/static/prism/geodata/<tier>.geo.json on first encode. Set a custom URL via:

prism.geo.setBundleURL("https://cdn.example.com/geodata/");

prism static-bundle ./public/prism always emits the geodata artifacts under <out>/geodata/ so the WASM runtime finds them.

For pages that inline the tier bytes:

prism.geo.primeTier("world-110m", new Uint8Array(buffer));

Optional eager fetch:

await prism.geo.preload("admin1-50m");

Validation

The PRISM_SPEC_021 rule fires when:

  • mark is geoshape or geopoint but projection is missing or declares an unknown type.
  • A geoshape spec lacks encoding.feature.field.
  • A geopoint spec lacks encoding.longitude.field or encoding.latitude.field.
  • projection.tier is set to a value outside the known tiers.

Runtime errors:

  • PRISM_GEO_001 — feature id in a row is not in the manifest tier.
  • PRISM_GEO_002 — bundle fetch failed (WASM) or embed missing (host).

Custom maps

Custom feature sets live outside the v1 scope. The manifest + world-110m.geo.json files use a small documented format (geodata/decoder.go); future work surfaces a public loader so downstream apps can ship their own admin levels (e.g. ZIP codes, census tracts) via the same feature channel.