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, /status and /details, each with a request struct that knows its own parameters, plus search_one/1 for the single-result case that most code actually wants.
  • Validation before the call, overridable with force: true when you know better.
  • Errors collected in :errors with 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}}, and rate_limit_retry: true will 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_url for 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.

blog-post
ExNominatim 2024
/images/software/stack/Elixir.png Elixir

Have something worth building?

A paragraph about what you are working on is enough to start.

Say hello