Maps¶
MapsAccessor on Dhis2Client.maps covers the authoring surface over /api/maps: list_all, get, create_from_spec, clone, delete. MapSpec is a typed builder that captures the viewport (longitude / latitude / zoom / basemap) plus an ordered list of MapLayerSpec layers and produces a full Map that DHIS2's metadata importer accepts. MapLayerSpec covers the most common layer type — thematic choropleth — with sensible defaults; drop to the Map / MapView models for the full knob set (event layers, earth-engine, custom rendering strategies).
Layer types¶
A DHIS2 Map holds one or more MapView layers rendered bottom-up:
- Thematic (
layer="thematic") — the workhorse. Choropleth (thematicMapType="CHOROPLETH") colours each org unit by a data value; graduated symbols ("BUBBLE") scale point size instead. Needs geo-referenced org units (polygons for choropleth, points for bubbles). - Boundary (
layer="boundary") — outline-only base layer; typically sits below a thematic. - Facility (
layer="facility") — point markers for facility-level org units. - Earth engine / event / org unit — rarer types supported via raw
MapViewconstruction.
Georeferenced org units are required¶
Thematic + boundary layers rely on OrganisationUnit.geometry being a GeoJSON-compatible polygon / multipolygon / point. Without it the Maps app falls back to a default viewport and you see a choropleth floating over a blank / wrong-continent basemap. The seed's Sierra Leonean districts carry rough bounding polygons so the demos render in the right place.
MapSpec + MapLayerSpec — builders over the wire models¶
Map is the generated model — pydantic emitted from DHIS2's schema with every viewport, basemap and bookkeeping knob the Maps app exposes. MapView is hand-written in dhis2w_client.v{N}.maps together with the three enums it carries (ThematicMapType, OrganisationUnitSelectionMode, MapViewRenderingStrategy). The wire shape nested under Map.mapViews[] is the same on every major, and whether a given release lists mapView on /api/schemas has varied between patches of 2.41 (BUGS.md #43), so the hand-written model is what keeps the three version trees exposing one shape. It names the fields the builders read and write and keeps everything else through extra="allow". Authoring a choropleth by populating those fields directly for each map is tedious + error-prone.
MapSpec + MapLayerSpec are the authoring shapes — frozen pydantic models whose fields cover the common-case knobs: viewport (longitude, latitude, zoom, basemap), ordered layers, and per-layer (data_elements / indicators, periods, organisation_units, legend_set, thematic_map_type, classes, color_low, color_high, opacity). MapsAccessor.create_from_spec materialises the spec into a full typed Map with every derived MapView row populated.
The spec exists because the wire shape branches on MapLayerSpec.layer_kind: thematic layers need dataDimensionItems[] plus rowDimensions / columnDimensions / filterDimensions populated, while boundary and facility layers leave those fields empty and DHIS2 rejects payloads that mix the two. MapLayerSpec.to_map_view() encodes that branch once; the kwargs alternative would replay it at every call site. Same pattern as VisualizationSpec / LegendSetSpec / LegendSpec / OptionSpec — see the Legend sets doc for the full spec-vs-generated-model cross-reference table and the rule for when reaching for a spec is the right call.
Why create_from_spec always goes through /api/metadata¶
Same reason as Visualization: a direct PUT /api/maps/{uid} with nested mapViews silently drops the derived rows / columns / filters collections DHIS2 renders from. The accessor routes through POST /api/metadata?importStrategy=CREATE_AND_UPDATE so the importer expands every dimension selector — don't bypass it.
POST /api/maps is the same trap on all three majors: it accepts the payload, returns a UID, and drops each layer's organisationUnits, organisationUnitLevels and dataDimensionItems, leaving a map whose layers reference nothing. The metadata importer keeps them. POST /api/mapViews is not a route DHIS2 serves — it answers 405 — so a layer is never created on its own; it is always nested under the map the importer writes. BUGS.md #114 carries the repro on each major.
Related¶
- Visualizations + dashboards — maps share the same dimension model (
dx,pe,ou) as visualizations; same analytics query drives both. - Analytics — sanity-check the data path with
client.get_raw("/api/analytics", params={...})before saving a map. - CLI surface:
d2w metadata list maps / get / create / clone / delete+d2w browser map screenshot <uid>.
maps
¶
Map authoring helpers — Dhis2Client.maps.
A DHIS2 Map is a geographic-visualization container — a viewport
(longitude, latitude, zoom, basemap) plus an ordered list of
MapView layers rendered bottom-up. Each layer is either:
- THEMATIC (
layer="thematic") — choropleth (one fill colour per org unit, driven by a data dimension) or graduated symbols (size scales with the value). The most common layer type. - BOUNDARY (
layer="boundary") — outline-only layer, typically used as a base below thematics. - FACILITY (
layer="facility") — point markers for facility-level org units. - EARTH_ENGINE / EVENT / ORG_UNIT — rarer layer types;
supported via raw
MapViewconstruction.
Most day-to-day authoring only touches the thematic case: one data
element × one period × one org-unit level → a choropleth of Sierra
Leone's districts coloured by immunization coverage, say.
MapLayerSpec + MapSpec cover that case with sensible defaults;
drop to the Map / MapView models when you need the full knob set.
Why MapView is hand-written here¶
Map is the generated model. MapView and the three enums it carries
(ThematicMapType, OrganisationUnitSelectionMode,
MapViewRenderingStrategy) are defined in this module so all three
version trees expose one layer shape whatever a release's
/api/schemas inventory holds. 2.41.9.x omits mapView from that
inventory, so a tree generated against those releases carries no
MapView at all; 2.41.10 and every later release list it. The wire
shape nested under Map.mapViews[] is the same on every major, so one
hand-written model serves all three trees.
extra="allow" keeps every field the Maps app writes that this model
does not name.
Why always POST through /api/metadata¶
Same reason as Visualization: a direct PUT /api/maps/{uid} with
nested mapViews silently drops the derived rows / columns /
filters collections DHIS2 renders from. POST /api/maps is the
wrong path on every major — it answers 201 while discarding each
layer's organisationUnits, organisationUnitLevels and
dataDimensionItems — and POST /api/mapViews answers 405. Route
creates + updates through
POST /api/metadata?importStrategy=CREATE_AND_UPDATE so the importer
expands every dimension selector into the axes DHIS2 reads at render
time and persists each layer's references.
MapsAccessor.create_from_spec takes that path.
Classes¶
ThematicMapType
¶
OrganisationUnitSelectionMode
¶
Bases: StrEnum
How a layer expands its selected organisation units.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maps.py
MapViewRenderingStrategy
¶
Bases: StrEnum
How a layer spreads its periods across the map.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maps.py
MapView
¶
Bases: BaseModel
One layer of a DHIS2 Map, as nested under Map.mapViews[].
Names the fields the authoring helpers read and write; every other
field the Maps app stores rides along through extra="allow".
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maps.py
MapLayerSpec
¶
Bases: BaseModel
Typed builder for one MapView layer on a Map.
Covers the common thematic-choropleth case with sensible defaults.
Drop down to a raw MapView payload for the full knob surface
(event layers, earth-engine, custom rendering strategies).
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maps.py
Methods:¶
to_map_view()
¶
Materialise this layer as a typed MapView the metadata importer accepts.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maps.py
MapSpec
¶
Bases: BaseModel
Typed builder for a full Map — viewport + ordered layers.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maps.py
Methods:¶
to_map()
¶
Materialise the typed Map DHIS2's metadata importer accepts.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maps.py
MapsAccessor
¶
Dhis2Client.maps — workflow helpers over /api/maps.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maps.py
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 | |
Methods:¶
__init__(client)
¶
list_all()
async
¶
List every Map on the instance, sorted by name.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maps.py
get(uid)
async
¶
Fetch one Map with every mapViews layer resolved inline.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maps.py
create_from_spec(spec)
async
¶
Build a Map from a spec and POST via /api/metadata.
Route through the metadata importer so derived axes populate.
A direct PUT /api/maps/{uid} with nested mapViews silently
drops rows / columns / filters — don't take that shortcut.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maps.py
clone(source_uid, *, new_name, new_uid=None, new_description=None)
async
¶
Duplicate an existing Map with a fresh UID + new name.
Copies the viewport + every layer so the clone renders
identically. Stripped fields: server-owned (created,
lastUpdated, createdBy, lastUpdatedBy) and display-computed
shortcuts. mapViews carry over with fresh UIDs assigned by
the importer.