Liking cljdoc? Tell your friends :D

geo

Multi-provider geocoding abstraction with automatic fallback chain, DB-backed caching, per-provider rate limiting, and Haversine distance calculation.

Key namespaces

NamespaceLayerResponsibility

wagoe.geo.schema

shared

Malli schemas: GeoPoint, AddressQuery, GeoResult, GeoConfig

wagoe.geo.ports

shared

GeoProviderProtocol, GeoCacheProtocol, GeoServiceProtocol (lookup, reverse-lookup)

wagoe.geo.core.math

core

haversine-distance, bearing

wagoe.geo.core.address

core

normalize-query, query-hash (SHA-256)

wagoe.geo.shell.adapters.osm

shell

Nominatim adapter (no API key required)

wagoe.geo.shell.adapters.google

shell

Google Maps adapter (requires WAG_GEO_API_KEY)

wagoe.geo.shell.adapters.mapbox

shell

Mapbox adapter (requires WAG_GEO_API_KEY)

wagoe.geo.shell.adapters.pdok

shell

PDOK Locatieserver adapter: Dutch addresses, no API key

wagoe.geo.shell.cache

shell

DbGeoCache — next.jdbc-backed, geo_cache table

wagoe.geo.shell.service

shell

Public API: geocode!, reverse-geocode!, distance

Configuration

;; OpenStreetMap only (no API key)
:wagoe/geo-service
{:provider   :openstreetmap
 :user-agent "MyApp/1.0 (contact@example.com)"
 :cache-ttl  86400   ; 24 hours
 :cache?     true}

;; PDOK: street, city and point from postcode + house number (no API key)
:wagoe/geo-service
{:provider  :pdok
 :cache-ttl 86400
 :cache?    true}

;; Fallback chain: OSM first, then Google
:wagoe/geo-service
{:provider   [:openstreetmap :google]
 :api-key    #env WAG_GEO_API_KEY
 :user-agent "MyApp/1.0"
 :cache-ttl  86400
 :cache?     true}

:cache? true caches lookups in geo_cache through :wagoe/db-context, which the module wires itself; run the migration first. A config that does not match wagoe.geo.schema/GeoConfig fails at boot.

Usage

(require '[wagoe.geo.shell.service :as geo])

;; Geocode an address
(geo/geocode! service {:address "Prinsengracht 263, Amsterdam"})
;=> {:lat 52.3747 :lng 4.8844 :formatted-address "Prinsengracht 263, Amsterdam" :provider :openstreetmap :cached? false}

;; Postcode + house number (PDOK)
(geo/geocode! service {:postcode "1012 JS" :house-number 1 :house-number-addition "A"})
;=> {:street "Dam" :house-number "1" :house-number-addition "A" :city "Amsterdam" :lat ... :lng ...}

;; Only the named providers, for this call; cached separately
(geo/geocode! service {:postcode "1012 JS" :house-number 1} {:only #{:pdok}})

;; The same through the component's port, without requiring the shell namespace
(require '[wagoe.geo.ports :as geo-ports])
(geo-ports/lookup service {:postcode "1012 JS" :house-number 1} {:only #{:pdok}})

;; Reverse geocode
(geo/reverse-geocode! service {:lat 52.3747 :lng 4.8844})
;=> {:formatted-address "Prinsengracht 263, 1016 GV Amsterdam"}

;; Haversine distance (pure, no HTTP)
(geo/distance {:lat 52.3747 :lng 4.8844} {:lat 52.3702 :lng 4.8952})
;=> 0.84  ; kilometres

Rate limiting and timeouts

  • OpenStreetMap (Nominatim): 1 request/second enforced

  • Google / Mapbox / PDOK: 100 ms between requests

  • One adapter per provider, shared across threads: the limit is per instance. A waiter holds no slot, so a cancelled lookup delays no one.

  • :timeout-ms (default 3000) bounds each call’s connect and read; a timeout is a miss, logged as an ERROR, and the chain moves on.

DB migration

Apply the geo_cache table migration before first use:

clojure -M:migrate up

Migration files: resources/wagoe/geo/migrations/ (20260324010000-geo-cache.up.sql and later)

Testing

clojure -M:test :geo

Can you improve this documentation? These fine people already did:
thijscreemers & Thijs Creemers
Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close