I wrote this after hand-rolling the same geocoding calls for the third time, badly.
Nominatim’s usage policy is an unusual design brief. One request per second against the public server, no bulk geocoding, no systematic downloading, and a user agent that identifies you. Every one of those is a rule a client can help you keep or help you break.
So the library argues before the request. A missing city, a latitude of 200, three mutually exclusive options: all caught locally, all explained. That is not politeness. A geocoder is a service you depend on, and spending your allowance on requests that were invalid when you wrote them is a self-inflicted outage.
What is in it
/search,/reverse,/lookup,/statusand/details, each with a request struct that knows its own parameters, plussearch_one/1for the single-result case that most code actually wants.- Validation before the call, overridable with
force: truewhen you know better. - Errors collected in
:errorswith a:valid?flag on the struct, so a form can show all of them at once instead of discovering them one reload at a time. - Rate limiting that respects your cache: an ETS table rather than a GenServer, checked after the
cache and before the HTTP dispatch, so a cached answer never spends a request. Exceeding it returns
{:error, {:rate_limited, retry_after_ms}}, andrate_limit_retry: truewill sleep and retry up to three times for the callers that would rather wait than think about it. - Telemetry for the events you would otherwise add by hand: requests, denials, cache hits. A geocoder that silently slows down is a mystery you solve once and never want to solve again.
- Optional geohash on results that carry coordinates, which is the cheapest way to make “nearby” queries possible later.
- Per-application and per-endpoint defaults, including
:base_urlfor a self-hosted instance and cache settings per operation. Expensive lookups and cheap ones do not deserve the same TTL. User-Agent: ExNominatim/{version}by default, as the policy requires.
What it works against
The pattern where a thin HTTP wrapper is considered finished. A wrapper that validates nothing turns
every caller into a validator, every caller does it differently, and the failure arrives as a puzzling
:ok with an empty list.
Installation
def deps do
[
{:ex_nominatim, "~> 3.1"}
]
end
Documentation on HexDocs, code on GitHub, questions on the Elixir Forum thread.