Skip to content

New adaptor: OpenStreetMap (Overpass, OSM API, Nominatim) #1811

Description

@jackohilts

Request

We want to build a new adaptor for OpenStreetMap (OSM), the open, community-maintained map of the world (ODbL licensed). Governments increasingly use OSM as a source of truth (or a cross-check) for health facilities, schools, roads, admin boundaries and addresses, but getting that data into their registries, HMIS and planning tools is usually a manual export.

OSM is not one API but a family of services. This adaptor should wrap the three that matter for integration:

Service What it's for Auth Default base URL
Overpass API Read-only queries over OSM data (by tag, area, bbox, "changed since") None https://overpass-api.de/api/interpreter
OSM API v0.6 Read elements/changesets; create Notes (bug reports for mappers) OAuth 2.0 Bearer (only for writes) https://api.openstreetmap.org/api/0.6
Nominatim Geocoding / reverse geocoding None No default (see 6 below)

Why now / driving use case: Jack is giving a talk with live demo at State of the Map LATAM 2026 (Open Source Tech Talks, Tue 27 Oct 2026, Mexico City). The organiser (DPGA) asked specifically for "how a government team could connect OSM data with its other systems using OpenFn". We need a working v1 by ~Fri 23 Oct to build and record the demo.

Demo workflows (to validate the adaptor against)

  1. Scheduled "latest cut" of OSM to downstream systems (primary demo). Cron-triggered workflow (daily for the demo; weekly is a sensible default for registries) that:
    • queries Overpass for health facilities in an area (e.g. amenity~"hospital|clinic|doctors" / healthcare=* in CDMX), only elements changed since the last run (newer: filter with a cursor stored in state);
    • converts to GeoJSON and maps to the format each downstream system needs (e.g. DHIS2 org units with coordinates via @openfn/language-dhis2, a FHIR Location, or a CSV/GeoJSON drop for a planning tool);
    • upserts into the target and stores the new cursor.
      Incremental beats a full re-export: smaller payloads, kinder to public Overpass, and naturally gives a "what changed this week" log.
  2. Registry vs OSM gap check, feeding back to the community. Compare a government facility list against OSM (match on ref/name within a radius). For facilities missing or mismatched in OSM, create an OSM Note at the location rather than editing the map directly. This respects the Automated Edits code of conduct and lets local mappers verify. Good story for a SotM audience: governments give back, not just take.
  3. Geocode / enrich incoming records (optional, only with a self-hosted or commercial Nominatim). E.g. reverse geocode GPS points from an ODK/Kobo submission to municipality/state before loading into a registry.

To start, this adaptor should:

  1. Configuration & auth. All base URLs overridable (so users can point at self-hosted Overpass/Nominatim, regional instances, or the dev sandbox). Optional OAuth 2.0 access_token sent as Authorization: Bearer ... to the OSM API only. Reads must work with no credentials.
  2. Mandatory userAgent. OSMF policies require an identifying User-Agent (stock library UAs get blocked). Make it required in the config schema and send it on every request.
  3. Generic HTTP helpers (request, get, post) scoped to each service, e.g. get('/map', { query: { bbox } }) against the OSM API, so any unwrapped endpoint can be called.
  4. Overpass:
    • query(overpassQL, options) posts raw Overpass QL and returns JSON (force [out:json] if not set; expose timeout).
    • Convenience helper, e.g. findFeatures({ tags, bbox | area, newerThan }, options) that builds the QL for the common "these tags, in this place, changed since X" case.
    • Handle 429/504 (rate-limited / server busy) with backoff and a clear error; optionally check /api/status before heavy queries.
  5. OSM API v0.6 (read + Notes):
    • getElement(type, id, { full }), getElements(type, ids), getMap(bbox) (JSON variants via .json endpoints).
    • listNotes({ bbox, closed }), getNote(id), createNote({ lat, lon, text }), commentNote(id, text).
    • Out of scope for v1: creating changesets / editing map data (see Automated Edits policy). Follow-up issue if needed.
  6. Nominatim: geocode(query, options) and reverseGeocode({ lat, lon }, options). No default URL: the Nominatim usage policy explicitly says the public API must not be built into or offered by no-code/low-code platforms as a generic geocoding service, and limits use to 1 req/s (4 req/min for scheduled scripts). So: nominatimUrl must be set deliberately by the user, enforce a client-side rate limit, and link the policy in the JSDoc/README.
  7. GeoJSON helper: toGeoJSON(osmJson) (e.g. wrapping osmtogeojson) so downstream mapping works with standard geometry.
  8. Results in state.data, errors that pass through the server message, and never log the access token.
  9. README must cover: ODbL attribution/share-alike obligations, Overpass/Nominatim fair-use, and the "use Notes, not edits" pattern.
  10. Unit tests with mocks for every operation, plus JSDoc examples for each.

Credentials

  • Login credentials: No shared account yet.
    • Overpass / Nominatim reads: no credentials needed.
    • OSM API (Notes, authenticated reads): use the OSM dev sandbox, not production: https://master.apis.dev.openstreetmap.org/ (alias api06.dev.openstreetmap.org). Its database is separate from live OSM, so create a fresh account there, then register an OAuth 2 app (Settings > OAuth 2 applications) with scopes read_prefs, write_notes. Save the login, client ID/secret and access token in LastPass as OSM Dev Sandbox - Adaptor Dev. Please don't post credentials here.
    • Only once tests pass on the sandbox should we create a production OAuth app (and only write_notes, not write_api).
  • Test record(s):
    • Overpass: query live data with a small bbox around central CDMX (e.g. Centro Histórico, 19.42,-99.15,19.44,-99.12). Capture real responses as test fixtures.
    • Sandbox: it's mostly empty. Copy a small CDMX area into it with osm_to_sandbox, then create/comment test Notes there.
    • Nominatim (optional): run locally via Docker (mediagis/nominatim-docker) loaded with the Geofabrik Mexico extract. Note there is no Overpass on the dev sandbox; for fully offline CI, mock Overpass or run Overpass API Docker on a small extract.
  • Authentication method(s): OAuth 2.0 (Authorization Code / PKCE). Access tokens currently don't expire, so a one-time token stored in the credential is fine for v1. Prod: https://www.openstreetmap.org/oauth2/authorize and /oauth2/token. Sandbox: same paths on master.apis.dev.openstreetmap.org. OAuth docs
  • Suggested configuration-schema.json: userAgent (required), access_token (optional, sensitive), apiUrl, overpassUrl (optional, defaults above), nominatimUrl (optional, no default).

Sample Code

// Workflow 1: weekly/daily "latest cut" of health facilities in CDMX
query(`
  [out:json][timeout:90];
  area["name"="Ciudad de México"]["admin_level"="4"]->.cdmx;
  nwr["amenity"~"hospital|clinic|doctors"](area.cdmx)(newer:"${$.lastRun ?? '2026-01-01T00:00:00Z'}");
  out center tags;
`);
fn(state => {
  state.facilities = toGeoJSON(state.data).features;
  state.lastRun = new Date().toISOString();
  return state;
});
// ...then map state.facilities into DHIS2 / FHIR / CSV in the next step

// Same thing via the convenience helper
findFeatures(
  { tags: { amenity: ['hospital', 'clinic', 'doctors'] }, area: 'Ciudad de México', newerThan: $.lastRun },
  { geojson: true }
);

// Workflow 2: flag a registry facility missing from OSM as a Note
createNote({
  lat: 19.4326,
  lon: -99.1332,
  text: 'Government health registry lists "Centro de Salud X" (CLUES ABC123) here, not found in OSM. Please verify. #OpenFn',
});

// Workflow 3 (self-hosted Nominatim only)
reverseGeocode({ lat: $.data.gps.lat, lon: $.data.gps.lon }, { zoom: 10 });

// generic escape hatch
get('/map.json', { query: { bbox: '-99.14,19.43,-99.13,19.44' } });

Resources

Logo Links

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions