a helper tool to add open street maps addresses and shapes.
  • JavaScript 93.1%
  • HTML 2.6%
  • Python 2.1%
  • CSS 1.4%
  • Shell 0.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-08 11:47:09 +08:00
db Add overture_buildings table, config reader, and test helpers 2026-09-03 08:46:51 +08:00
docs/superpowers Fix plan doc: unique gnaf_pid overrides for multi-address batch tests 2026-09-08 11:42:00 +08:00
public Client: batch panel with editable comment and Submit batch 2026-09-08 11:47:09 +08:00
scripts Add Overture footprint import script and docs 2026-09-03 08:58:53 +08:00
src Replace single-address insert with POST /api/addresses/batch-insert (point + area) 2026-09-08 11:40:25 +08:00
tests Cover existing/footprint geometries and best-effort partial failure in batch-insert 2026-09-08 11:41:33 +08:00
.dockerignore Improve map UX and extend .dockerignore 2026-08-31 07:48:13 +08:00
.env.example Add overture_buildings table, config reader, and test helpers 2026-09-03 08:46:51 +08:00
.gitignore Add Overture footprint import script and docs 2026-09-03 08:58:53 +08:00
AGENTS.md Add AGENTS.md capturing project conventions and dev-environment gotchas 2026-08-31 07:45:13 +08:00
docker-compose.yml Add optional self-signed TLS mode; show suburb in search results 2026-08-30 14:32:37 +08:00
Dockerfile Scaffold Express app, Docker setup, and health check 2026-08-30 09:49:09 +08:00
package-lock.json Add radicaleApi.js: CardDAV discovery, listing, get/put with ETag conflict handling 2026-09-01 08:49:47 +08:00
package.json Add radicaleApi.js: CardDAV discovery, listing, get/put with ETag conflict handling 2026-09-01 08:49:47 +08:00
README.md Record WA Overture import size in README 2026-09-03 09:08:08 +08:00

Australian Address → OSM Insert Service

A self-hosted tool to search Western Australian addresses from G-NAF and, one at a time, review and manually confirm-insert them into OpenStreetMap as new nodes.

No bulk or automated writes. Every insert is a single explicit action: you search, pick one address, see the map, the OSM tags, and the exact request that would be sent (method, URL, headers, XML body), and only then click "Confirm & insert." Nothing writes to OSM without that click.

See AGENTS.md for conventions and gotchas if you're an AI agent working in this repo, and docs/superpowers/specs/2026-08-30-osm-address-insert-design.md for the full design rationale.

Features

  • Trigram search over ~1.5M imported WA addresses, with suburb/postcode shown alongside each result.
  • Map view (Leaflet) of the G-NAF point, with existing nearby OSM address nodes and building outlines overlaid as a duplicate-conflict warning / existing-shape candidates (live OSM API query, always against production regardless of OSM_API_BASE).
  • A full preview of what an insert would do before you commit to it: the OSM tags in human-readable form, and the literal PUT requests (URL/headers/pretty-printed XML body) that clicking confirm would send.
  • OSM OAuth2 login (sandbox by default), single-changeset-per-address inserts, and a "Skip" action that needs no login at all.
  • Geoscape/G-NAF attribution shown in the UI footer, as required by the data's CC BY 4.0 licence.
  • Optional: nearby Overture Maps building footprints (imported offline) shown in the map's "Existing shape" mode as create-a-building candidates, deduped against existing OSM buildings.

One-time setup

  1. Register an OAuth2 application at https://master.apis.dev.openstreetmap.org/oauth2/applications (sandbox — do not use production until the flow is verified end-to-end). Required scopes: read_prefs write_api. The redirect URI must be https://, exactly matching OSM_OAUTH_REDIRECT_URI below — OSM rejects http:// redirect URIs outright, with no exception for localhost.
  2. Copy .env.example to .env and fill in OSM_OAUTH_CLIENT_ID / OSM_OAUTH_CLIENT_SECRET from step 1, plus SESSION_SECRET (any long random string). If port 3000 is already in use on your machine, set HOST_PORT to a free port instead (the app always listens on 3000 inside the container regardless — update OSM_OAUTH_REDIRECT_URI to match whichever port you choose).
  3. Generate a self-signed TLS cert for local HTTPS (required for the OAuth redirect above; nothing else validates the cert, so self-signed is fine — your browser will show a one-time warning to click past):
    mkdir -p certs && openssl req -x509 -newkey rsa:2048 -nodes \
      -keyout certs/localhost.key -out certs/localhost.crt -days 825 \
      -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
    
    Leave TLS_CERT_PATH/TLS_KEY_PATH blank in .env instead if you're only browsing/skipping and don't need the OSM login/insert flow yet.
  4. docker compose up -d — starts Postgres/PostGIS and the app. The app runs its database migration automatically on startup.
  5. Load the flattened GNAF data via one of gnaf-loader's own options — its README documents four. Two were tried in practice:
    • Option 1 (run load-gnaf.py yourself): needs both full G-NAF and the separate Geoscape Administrative Boundaries dataset (gnaf-loader can't build GNAF without it — it depends on the boundaries data to fix addresses whose locality has no gazetted boundary). On the August 2026 release this hit a live bug: a NotNullViolation on streets.locality_name partway through Part 4, even with --states WA and --no-boundary-tag. It may work fine on other releases or once upstream fixes it — check https://github.com/minus34/gnaf-loader/issues first.
    • Option 3 (restore the maintainer's pg_dump — used here): download the pre-built dump from https://minus34.com/opendata/geoscape-<version>-gda2020/gnaf-<version>.dmp (only the GNAF dump is needed — address_principals has no runtime foreign keys into the admin boundaries schema, only during the original build) and restore it directly: docker run --rm --network host -e PGPASSWORD=postgres -v <dump-dir>:/dumps:ro postgres:16 pg_restore -h localhost -p 5432 -U postgres -d addr --no-owner --no-privileges /dumps/gnaf-<version>.dmp Note: the GDA2020 dump restores into a schema named gnaf_<version>_gda2020 (with the datum suffix), not the plain gnaf_<version> that load-gnaf.py itself would create — pass whichever schema actually exists to the next step. This path loads all states (~15M addresses, restore takes 10-15 minutes) since the dump isn't state-filtered; Stage 2 below does the WA filtering.
  6. Run the Stage 2 import: docker compose exec app python scripts/import_gnaf.py --gnaf-schema gnaf_<version>_gda2020 --state WA
  7. Open https://localhost:${HOST_PORT:-3000}/ and start reviewing addresses. Searching and Skipping both work with no login; logging in with OSM is only needed to actually insert.

Optional: import Overture building footprints

The map's "Existing shape" mode can also offer building outlines from a local copy of Overture Maps' buildings theme (ODbL, OSM-import-compatible). Footprints not already in OSM show as teal dashed candidates; selecting one creates a new building way from its simplified + squared outline.

Prerequisites: pip install overturemaps (in a venv or via pipx on Debian/Ubuntu), GDAL/ogr2ogr with Parquet support (GDAL ≥ 3.5), psql.

export DATABASE_URL=postgres://postgres:postgres@localhost:5432/addr
./scripts/import_overture.sh

This downloads the WA bounding box (112.9,-35.2,129.0,-13.7) to overture-tmp/wa_buildings.parquet (skipped if that file already exists), loads it via a staging table, keeps single-ring polygons only, and upserts into overture_buildings. Re-running is idempotent. Narrow OVERTURE_BBOX if the WA-wide extract is larger than you want. On first run, check the parquet's columns with ogrinfo -so overture-tmp/wa_buildings.parquet and adjust scripts/import_overture.sh's SELECT if Overture has renamed one.

Reference from a WA-wide run (Overture 2025-08 release): 1,515,554 rows, ~950 MB total relation size (geometry + GIST index). The parquet download itself is ~224 MB.

Tuning knobs (all optional, defaults shown) are in .env.example under "Overture Maps building footprints".

OSM tagging conventions used

Decided by reviewing a real WA address already in OSM (152 Elderberry Drive, South Lake) against the Australian Tagging Guidelines:

  • addr:street/addr:suburb are Title Case (G-NAF stores full, unabbreviated street-type words already, just upper-cased).
  • addr:state is omitted — only included per the guidelines when the postal state differs from the address's actual state, never true here.
  • addr:country=AU is included; ref:gnaf is not.
  • Changeset source is "Local knowledge" and comment is "adds address". Note this means the changeset doesn't disclose that the underlying data came from bulk G-NAF/Geoscape import — a deliberate choice, worth keeping in mind for how you use the tool. The exception is a building created from an Overture footprint: that changeset carries source="Overture Maps" and comment="adds address and building from Overture footprint".

Contacts (Radicale)

Optional: if you run a Radicale CardDAV server, this app can also audit your contacts' postal addresses and get them into OSM.

  1. Set RADICALE_BASE_URL, RADICALE_USERNAME, RADICALE_PASSWORD in .env (HTTP Basic Auth — Radicale's normal auth model), restart.
  2. Open /contacts.html: pick an address book, and for each contact flagged "No address" or "Incomplete", click "Fix address" to search G-NAF (seeded from whatever address text the contact already has) and confirm the right match — this writes the confirmed address into the contact's ADR field and links the contact to that G-NAF address.
  3. Open /contacts-osm-status.html: for each linked contact whose address isn't in OSM yet, click through to review and insert it, same as the normal search flow. Once inserted, come back and click "Write OSM link to contact" to add the resulting OSM node/way URL to the contact — a separate explicit step, not automatic.

Every write to Radicale requires an explicit click, same as every OSM insert. A write is rejected (shown as an error, not silently overwritten) if the contact changed elsewhere since it was last read.

Going live

Once the insert flow has been verified end-to-end against the sandbox API, switch to production by changing OSM_API_BASE to https://api.openstreetmap.org, OSM_WEBSITE_BASE to https://www.openstreetmap.org, and registering a separate OAuth2 application at https://www.openstreetmap.org/oauth2/applications for the new client id/secret. No code changes are required. (Nearby-lookup reads already target production unconditionally via OSM_READ_API_BASE, independent of this switch — see .env.example.)

Running tests

Backend (requires Node 20+; a TEST_DATABASE_URL Postgres/PostGIS database with the schema applied, e.g. via docker compose up -d db):

node --test --test-concurrency=1 tests/**/*.test.js

--test-concurrency=1 is required, not optional — these are integration tests sharing one real database, and cross-file parallelism races TRUNCATE against concurrent inserts otherwise. npm test already includes the flag.

Import script (requires Python 3.11+; pip install -r scripts/requirements.txt, or use the project's .venv if present):

TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/addr_test pytest tests/scripts/

Project structure

db/schema.sql            addresses + insert_log tables, extensions, indexes
src/app.js                Express app factory (dependency-injected for tests)
src/server.js             runs migration, then starts HTTP or HTTPS
src/db/                   data-access layer (addresses, insert_log, pool, migrate)
src/lib/                  Overpass client, OSM OAuth2 client, OSM REST API client
src/routes/                search, address detail/insert/skip, auth
public/                   static frontend (plain HTML/JS + Leaflet, no build step)
scripts/import_gnaf.py     Stage 2 of the import pipeline (reads a gnaf-loader schema)
scripts/import_overture.sh Optional: load WA Overture building footprints into overture_buildings
tests/                     node:test (backend) and pytest (import script)
docs/superpowers/          design spec and implementation plan

Data attribution

Address data is sourced from the Geocoded National Address File (G-NAF), © Geoscape Australia, licensed under CC BY 4.0.