Cartography & Geovisualization
Purpose: maps that communicate honestly. Cartographic choices (class
breaks, ramps, normalization, projection) can manufacture or hide
patterns; this skill treats them as analytical decisions with stated
rationale, not styling.
The first three questions
- What's the message? One map = one message. If two variables
compete, consider small multiples or a bivariate scheme — not twelve
legend classes.
- Normalized? Choropleths of raw counts are population maps in
disguise. Rates, densities, or per-capita for area-based color; raw
magnitudes → proportional symbols instead.
- Static or interactive? Print/PDF/paper → matplotlib/QGIS layout;
exploration/stakeholders → Folium/MapLibre; big point data →
Kepler.gl/deck.gl (GPU).
Thematic map type selection
| Data |
Map type |
| Rate/ratio by polygon |
Choropleth |
| Count/magnitude by place |
Proportional/graduated symbols |
| Two related rates |
Bivariate choropleth (3×3 max) |
| Individual-level density |
Dot density or KDE surface (label bandwidth) |
| Continuous field (raster) |
Classified or stretched render + hillshade context |
| Movement/OD |
Flow map (width∝volume), aggregate to avoid hairballs |
| Change over time |
Small multiples > animation for analysis; animation for outreach |
Classification — the honesty lever
- Natural breaks (Jenks): default for skewed data; breaks are
data-specific, so NOT comparable across maps/dates.
- Quantiles: guaranteed color balance; can split near-identical values.
- Equal interval: comparable and intuitive; fails on skew.
- Manual/defined: the ONLY correct choice for map series (same breaks
across all dates/regions) and for domain thresholds (WHO limits, slope
classes).
- 5±2 classes; show the histogram with breaks in the workflow; state the
scheme in the caption/metadata. Try two schemes — if the story changes
materially, the story is the classification, and the reader must be told.
Color
- Ramps from ColorBrewer/
cmcrameri/viridis family: sequential (ordered),
diverging (meaningful midpoint — zero, mean, threshold), qualitative
(categories, ≤ 8).
- Colorblind-safe by default (~8% of male readers); never red-green
diverging without checking a CVD simulator.
- NoData ≠ zero: render as neutral gray with its own legend entry, never
the ramp's low end.
- Muted basemaps (CartoDB Positron) under thematic layers — the basemap
must never win.
Projection for display
- Web tiles = Web Mercator: fine for city scale; area comparisons at
continental scale on Mercator are visual lies — use equal-area
projections (Albers, Mollweide, Equal Earth) for static thematic maps of
large extents.
- National mapping → the national grid; polar work → polar stereographic.
- Label the projection on publication maps.
Required furniture (publication static maps)
Title (the message, not the filename), legend (units!, sensible number
formatting), scale bar (projected CRS only — degrees have no fixed scale),
north arrow (only when north isn't up or the audience expects it), data
source + date + projection + author, and an inset locator map for
unfamiliar regions.
# GeoPandas static map core
ax = gdf.plot(column="rate_per_1k", scheme="naturalbreaks", k=5,
cmap="YlGnBu", legend=True, edgecolor="white", linewidth=0.3,
missing_kwds={"color": "#d9d9d9", "label": "No data"})
ax.set_axis_off()
Export: 300 dpi PNG/PDF for print; SVG when editors will touch it; COG +
style for GIS handoff.
Interactive maps
- Folium/MapLibre: tooltips with formatted values, layer control, sensible
initial bounds (
fit_bounds), legend included (Folium needs a manual
HTML/branca legend — don't ship without one).
- Performance: >~50k vector features → tile it (tippecanoe → PMTiles) or
switch to deck.gl/Kepler; never dump 500k GeoJSON features into Leaflet.
- Every popup number formatted (thousands separators, units, rounding
matched to precision honesty).
Verification protocol
- Squint test: does the message survive at thumbnail size?
- CVD simulation pass.
- Legend audit: units, rounding, class edges non-overlapping.
- Cross-check 3 features' rendered values against the attribute table
(classification bugs are silent).
- For map series: identical breaks, ramp, and extent across panels.
Pitfalls checklist
- Raw-count choropleth (population in disguise).
- Jenks breaks compared across two dates.
- Red-green diverging ramp, unlabeled midpoint.
- NoData painted as the lowest class.
- Scale bar on an unprojected (degree) map.
- Continental-area comparisons on Web Mercator.
- Interactive map with no legend or units.
Execution contract
- Workflow: inspect audience, data semantics, scale, and output medium; select projection, normalization, classification, and visual hierarchy; render; verify; export.
- Decision rules: choose map type from the analytical question, normalize counts when exposure differs, and keep breaks fixed for comparisons.
- Verification protocol: run the five checks above and reconcile rendered values, units, class edges, and missing-data treatment against the source.
- Failure modes: stop or qualify delivery when denominators, CRS, units, accessibility, or cross-panel comparability are unresolved.
- Deliverables: final map, legend and units, data/source note, projection and classification rationale, accessibility note, and reproducible style or code.
- Source freshness: consult the authoritative source registry before using version-sensitive APIs and record the checked date.
1---2name: cartography-geoviz3description: Always invoke before answering any request to create, compare, design, or review a user-facing map, even if the request is terse or underspecified. Covers publication maps, choropleths, map series and small multiples, comparable multi-date panels, proportional/bivariate/flow maps, raster rendering, and interactive web maps. Includes classification, color, legends, projections, accessibility, and large-data aggregation. Do not trigger for a temporary diagnostic plot inside another analysis.4license: MIT5---67# Cartography & Geovisualization89Purpose: maps that communicate honestly. Cartographic choices (class10breaks, ramps, normalization, projection) can manufacture or hide11patterns; this skill treats them as analytical decisions with stated12rationale, not styling.1314## The first three questions15161. **What's the message?** One map = one message. If two variables17 compete, consider small multiples or a bivariate scheme — not twelve18 legend classes.192. **Normalized?** Choropleths of raw counts are population maps in20 disguise. Rates, densities, or per-capita for area-based color; raw21 magnitudes → proportional symbols instead.223. **Static or interactive?** Print/PDF/paper → matplotlib/QGIS layout;23 exploration/stakeholders → Folium/MapLibre; big point data →24 Kepler.gl/deck.gl (GPU).2526## Thematic map type selection2728| Data | Map type |29|---|---|30| Rate/ratio by polygon | Choropleth |31| Count/magnitude by place | Proportional/graduated symbols |32| Two related rates | Bivariate choropleth (3×3 max) |33| Individual-level density | Dot density or KDE surface (label bandwidth) |34| Continuous field (raster) | Classified or stretched render + hillshade context |35| Movement/OD | Flow map (width∝volume), aggregate to avoid hairballs |36| Change over time | Small multiples > animation for analysis; animation for outreach |3738## Classification — the honesty lever3940- **Natural breaks (Jenks)**: default for skewed data; breaks are41 data-specific, so NOT comparable across maps/dates.42- **Quantiles**: guaranteed color balance; can split near-identical values.43- **Equal interval**: comparable and intuitive; fails on skew.44- **Manual/defined**: the ONLY correct choice for map series (same breaks45 across all dates/regions) and for domain thresholds (WHO limits, slope46 classes).47- 5±2 classes; show the histogram with breaks in the workflow; state the48 scheme in the caption/metadata. Try two schemes — if the story changes49 materially, the story is the classification, and the reader must be told.5051## Color5253- Ramps from ColorBrewer/`cmcrameri`/viridis family: sequential (ordered),54 diverging (meaningful midpoint — zero, mean, threshold), qualitative55 (categories, ≤ 8).56- Colorblind-safe by default (~8% of male readers); never red-green57 diverging without checking a CVD simulator.58- NoData ≠ zero: render as neutral gray with its own legend entry, never59 the ramp's low end.60- Muted basemaps (CartoDB Positron) under thematic layers — the basemap61 must never win.6263## Projection for display6465- Web tiles = Web Mercator: fine for city scale; area comparisons at66 continental scale on Mercator are visual lies — use equal-area67 projections (Albers, Mollweide, Equal Earth) for static thematic maps of68 large extents.69- National mapping → the national grid; polar work → polar stereographic.70- Label the projection on publication maps.7172## Required furniture (publication static maps)7374Title (the message, not the filename), legend (units!, sensible number75formatting), scale bar (projected CRS only — degrees have no fixed scale),76north arrow (only when north isn't up or the audience expects it), data77source + date + projection + author, and an inset locator map for78unfamiliar regions.7980```python81# GeoPandas static map core82ax = gdf.plot(column="rate_per_1k", scheme="naturalbreaks", k=5,83 cmap="YlGnBu", legend=True, edgecolor="white", linewidth=0.3,84 missing_kwds={"color": "#d9d9d9", "label": "No data"})85ax.set_axis_off()86```8788Export: 300 dpi PNG/PDF for print; SVG when editors will touch it; COG +89style for GIS handoff.9091## Interactive maps9293- Folium/MapLibre: tooltips with formatted values, layer control, sensible94 initial bounds (`fit_bounds`), legend included (Folium needs a manual95 HTML/branca legend — don't ship without one).96- Performance: >~50k vector features → tile it (tippecanoe → PMTiles) or97 switch to deck.gl/Kepler; never dump 500k GeoJSON features into Leaflet.98- Every popup number formatted (thousands separators, units, rounding99 matched to precision honesty).100101## Verification protocol1021031. Squint test: does the message survive at thumbnail size?1042. CVD simulation pass.1053. Legend audit: units, rounding, class edges non-overlapping.1064. Cross-check 3 features' rendered values against the attribute table107 (classification bugs are silent).1085. For map series: identical breaks, ramp, and extent across panels.109110## Pitfalls checklist111112- Raw-count choropleth (population in disguise).113- Jenks breaks compared across two dates.114- Red-green diverging ramp, unlabeled midpoint.115- NoData painted as the lowest class.116- Scale bar on an unprojected (degree) map.117- Continental-area comparisons on Web Mercator.118- Interactive map with no legend or units.119120## Execution contract121122- **Workflow:** inspect audience, data semantics, scale, and output medium; select projection, normalization, classification, and visual hierarchy; render; verify; export.123- **Decision rules:** choose map type from the analytical question, normalize counts when exposure differs, and keep breaks fixed for comparisons.124- **Verification protocol:** run the five checks above and reconcile rendered values, units, class edges, and missing-data treatment against the source.125- **Failure modes:** stop or qualify delivery when denominators, CRS, units, accessibility, or cross-panel comparability are unresolved.126- **Deliverables:** final map, legend and units, data/source note, projection and classification rationale, accessibility note, and reproducible style or code.127- **Source freshness:** consult [the authoritative source registry](references/authoritative-sources.md) before using version-sensitive APIs and record the checked date.