Municipal Mapper
A Revenue Development Foundation initiative supporting local governments with open geographic data infrastructure. Access layers, boundaries, postal codes, and address lookups for all registered municipalities.
Each municipality exposes its public layers as WMTS tiles, OGC API · Features (QGIS-ready vector data), and a REST API.
Base URL:
· All endpoints return JSON · Write operations require X-API-Key
/api/v1/municipalities
List all municipalities
Returns the id and name of every registered municipality.
GET /api/v1/municipalities
Response 200:
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Springfield",
"created_at": "2025-01-15T09:00:00Z"
}
]
/api/v1/municipalities/{municipality_id}/layers
List layers for a municipality
Returns all layers with their style config and geometry type.
| Path param | Type | Description |
|---|---|---|
municipality_id | UUID | Municipality identifier |
GET /api/v1/municipalities/{municipality_id}/layers
Response 200:
[
{
"id": "a1b2c3d4-...",
"municipality_id": "3fa85f64-...",
"name": "Admin Boundaries",
"geometry_type": "Polygon",
"style": { "fillColor": "rgba(100,200,100,0.3)", "strokeColor": "#4CAF50" },
"z_index": 10,
"visible": true
}
]
/api/v1/municipalities/{municipality_id}/postal-codes
List postal codes with boundaries
Returns all postal codes assigned to boundary features, including GeoJSON geometry.
GET /api/v1/municipalities/{municipality_id}/postal-codes
Response 200:
[
{
"id": "...",
"feature_id": "...",
"code": "1234AB",
"label": "Central District",
"geometry": {
"type": "Polygon",
"coordinates": [[[4.89, 52.37], ...]]
},
"properties": { "district": "Central" },
"created_at": "2025-02-01T10:00:00Z",
"updated_at": "2025-02-01T10:00:00Z"
}
]
/api/v1/municipalities/{municipality_id}/address-lookup
Reverse address lookup (point-in-polygon)
Address System
Given a WGS84 coordinate, returns the postal code(s) whose boundary polygon contains that point.
Uses PostGIS ST_Contains for accurate point-in-polygon matching.
| Param | In | Type | Required | Description |
|---|---|---|---|---|
municipality_id | path | UUID | Yes | Target municipality |
lon | query | float | Yes | Longitude in WGS84 (EPSG:4326), e.g. 4.8952 |
lat | query | float | Yes | Latitude in WGS84 (EPSG:4326), e.g. 52.3702 |
| Field | Type | Description |
|---|---|---|
code | string | Postal code, e.g. 1234AB |
label | string | null | Human-readable area name |
feature_id | UUID | ID of the boundary feature |
boundary_properties | object | Custom properties from the boundary feature |
GET /api/v1/municipalities/{municipality_id}/address-lookup?lon=4.8952&lat=52.3702
Response 200:
[
{
"code": "1012AB",
"label": "City Centre",
"feature_id": "b3d9e2f1-...",
"boundary_properties": {
"district": "Central",
"population": 12400
}
}
]
Response 404 (no match):
{ "detail": "No postal code found for the given coordinates" }
curl "/api/v1/municipalities/3fa85f64-5717-4562-b3fc-2c963f66afa6/address-lookup?lon=4.8952&lat=52.3702"
const res = await fetch(
`/api/v1/municipalities/${municipalityId}/address-lookup` +
`?lon=${longitude}&lat=${latitude}`
);
if (res.ok) {
const results = await res.json();
console.log(results[0].code, results[0].label);
} else if (res.status === 404) {
console.log("No postal code at this location");
}
/wmts
OGC WMTS 1.0.0
Implements the OGC Web Map Tile Service standard. Supports
GetCapabilities and GetTile operations
over the WebMercatorQuad tile matrix set (zooms 0–20).
| Query param | Value | Notes |
|---|---|---|
SERVICE | WMTS | Required |
REQUEST | GetCapabilities | GetTile | Required |
LAYER | layer UUID | GetTile only |
TILEMATRIX | 0–20 | Zoom level |
TILEROW | integer | Y tile coordinate |
TILECOL | integer | X tile coordinate |
GET /wmts?SERVICE=WMTS&REQUEST=GetCapabilities
GET /wmts?SERVICE=WMTS&REQUEST=GetTile&LAYER={layer_id}&TILEMATRIX=14&TILEROW=8428&TILECOL=8354
import WMTSCapabilities from 'ol/format/WMTSCapabilities';
import { optionsFromCapabilities } from 'ol/source/WMTS';
import TileLayer from 'ol/layer/Tile';
import WMTS from 'ol/source/WMTS';
const parser = new WMTSCapabilities();
const res = await fetch('/wmts?SERVICE=WMTS&REQUEST=GetCapabilities');
const caps = parser.read(await res.text());
const options = optionsFromCapabilities(caps, {
layer: '{layer_id}',
matrixSet: 'WebMercatorQuad',
});
const layer = new TileLayer({ source: new WMTS(options) });
/tiles/basemap/{municipality_id}/{z}/{x}/{y}.png
Composite basemap (one layer for other apps)
All of a municipality's public layers flagged for the basemap, rendered together
in draw order as a single tile layer — the easiest way for another application to
reuse this map as its basemap. Also available via WMTS as layer
basemap-{municipality_id}. See BASEMAP_INTEGRATION.md in
the repository for ready-made snippets.
L.tileLayer('/tiles/basemap/{municipality_id}/{z}/{x}/{y}.png').addTo(map);
/tiles/{layer_id}/{z}/{x}/{y}.png
Slippy map XYZ tiles
Standard XYZ tile endpoint, compatible with Leaflet, OpenLayers, Maplibre, and any tile-based client.
L.tileLayer('/tiles/{layer_id}/{z}/{x}/{y}.png').addTo(map);
new ol.source.XYZ({
url: '/tiles/{layer_id}/{z}/{x}/{y}.png',
maxZoom: 20,
})
The modern successor to WFS. Serves every public layer as live vector features (GeoJSON) that you can open directly in QGIS, ArcGIS Pro, or any OGC-compatible GIS client — no GeoServer required. Non-public layers are not exposed. Only the single landing-page URL below is needed; the client discovers every layer from there.
/ogc
OGC API · Features landing page
Entry point for the service. Links onward to
/ogc/conformance and /ogc/collections
(one collection per public layer). All coordinates are WGS84 (CRS84,
lon/lat).
| Path | Returns |
|---|---|
/ogc | Landing page (JSON links) |
/ogc/conformance | Supported conformance classes |
/ogc/collections | All public layers as collections |
/ogc/collections/{layer_id} | One collection's metadata + extent |
/ogc/collections/{layer_id}/items | Features as GeoJSON (paged, bbox-filterable) |
/ogc/collections/{layer_id}/items/{feature_id} | A single feature |
| Items query param | Value | Notes |
|---|---|---|
limit | 1–10000 | Page size (default 1000) |
offset | integer | Paging offset; follow the next link |
bbox | minx,miny,maxx,maxy | CRS84 lon/lat filter |
GET /ogc
GET /ogc/collections
GET /ogc/collections/{layer_id}/items?limit=1000&bbox=-1.2,50.7,0.5,51.9
/ogc as the URL.
All mutation endpoints require the municipality's API key in the
X-API-Key request header.
API keys are issued when a municipality is created via the admin interface.
/api/v1/municipalities/{id}/layers
Create a new layer.
{
"name": "Roads",
"geometry_type": "LineString",
"style": { "strokeColor": "#e74c3c", "strokeWidth": 2 },
"z_index": 20
}
/api/v1/layers/{id}/features
Add a GeoJSON feature to a layer.
{
"geometry": {
"type": "Polygon",
"coordinates": [[[4.89,52.37],[4.91,52.37],[4.91,52.38],[4.89,52.38],[4.89,52.37]]]
},
"properties": { "name": "City Park" }
}
/api/v1/features/{id}/postal-code
Assign or update the postal code for a boundary feature.
{
"code": "1234AB",
"label": "Central District"
}
/api/v1/features/{id}/documents
Upload a supporting document (multipart/form-data, 50 MB max).
curl -X POST \
-H "X-API-Key: {api_key}" \
-F "file=@report.pdf" \
/api/v1/features/{id}/documents
Enter a coordinate (WGS84) to query which postal code boundary it falls within.
Results will appear here…