thinwrap_location (thinwrap_location v1.3.0)
View SourceRouting, distance matrix, geocoding and isochrone across Google, Mapbox, HERE, Esri, TomTom and OSRM — one normalized surface, zero dependencies.
Every function takes a provider config and an input map and returns
{ok, Result} or {error, ConnectorError}:
Config = #{provider => google, api_key => <<"...">>},
{ok, #{legs := Legs, polyline := Polyline}} =
thinwrap_location:route(Config, #{
waypoints => [#{lat => 40.7128, lng => -74.0060},
#{lat => 41.1865, lng => -73.1952}]
}).Two kinds of failure
- Operation failures — a vendor error, a transport failure or timeout,
request data the vendor would reject, an unsupported capability — come back
as
{error, connector_error()}, a plain map whosecodeyou branch on. - Misuse — a missing or unknown config/input key, a value of the wrong
type, an enum atom outside its set, a provider that lacks the operation —
raises
error({badarg, Description}). Folding those into{error, _}would make a bug in the calling code look like bad end-user input or, worse, a retryable outage.
Normalization
Distances are meters, durations seconds, coordinates #{lat, lng}
(latitude first), route geometry a Google precision-5 polyline whatever the
provider returned. Optional output fields are omitted from the result map
when the provider gave nothing — read them with maps:get/3. Every result
carries the decoded vendor body under raw (JSON null is the atom null).
Passthrough
Every input accepts passthrough => #{body => map(), headers => map(), query => map()}, forwarded verbatim and overriding what the connector set
(auth included): body deep-merged, headers and query shallow-merged.
Summary
Types
Provider configuration. provider selects the connector; the credential
keys depend on it
The operation-failure value. code is the normalized classification shared
with the sibling libraries; status is the HTTP status when the provider
answered; cause is the (credential-redacted) decoded vendor body or a
redacted transport message, with a Retry-After header surfaced as
<<"retryAfter">>.
Unix seconds, a UTC calendar:datetime(), or an RFC 3339 binary
(<<"2026-11-02T08:00:00Z">>). An unparseable binary is invalid_request.
How much traffic a route or matrix is computed against. none (default):
time-independent durations — a departure_time never adds traffic. traffic:
the traffic expected at departure_time (now when absent). live: deprecated
alias of traffic, kept permanently. The default is a billing decision too:
on Google traffic-aware routing is a Pro-tier SKU, and HERE bills time-aware
routing unless traffic is explicitly disabled.
Functions
Suggest places for the partial input (all providers except OSRM).
Forward-geocode address (all providers except OSRM).
Compute reachable-area contours around center (Mapbox, HERE, Esri, TomTom).
Compute the origins x destinations distance/duration matrix (all six
providers). Cells a provider could not route are absent from cells.
Resolve a place_id from autocomplete/2 into a full candidate (all
providers except OSRM). For Mapbox pass the same session_token as the
preceding autocomplete: Search Box bills per session.
Reverse-geocode location (all providers except OSRM).
Compute a route through waypoints (all six providers).
Whether Provider implements Operation.
Types
-type autocomplete_result() :: #{predictions := [autocomplete_prediction()], raw := term()}.
-type config() :: #{provider := provider(), api_key => secret(), access_token => secret(), arcgis_token => secret(), base_url => binary(), supported_exclude_classes => [binary()], transport => thinwrap_location_transport:transport(), sleep => fun((Milliseconds :: non_neg_integer()) -> term())}.
Provider configuration. provider selects the connector; the credential
keys depend on it:
google,here,tomtom—api_keymapbox—access_tokenesri— exactly one ofapi_keyorarcgis_tokenosrm—base_url(required, your own server; a path prefix is allowed) and optionallysupported_exclude_classes, the exclude classes your OSRM build was compiled with ([<<"toll">>, <<"ferry">>, <<"motorway">>])
Optional on every provider: transport (see thinwrap_location_transport;
default {thinwrap_location_httpc, #{}}) and sleep, the wait used between
async-matrix polls (HERE always, TomTom for large grids) — a test seam.
-type connector_error() :: #{code := thinwrap_location_error:code(), message := binary(), status => integer(), provider_message => binary(), cause => term()}.
The operation-failure value. code is the normalized classification shared
with the sibling libraries; status is the HTTP status when the provider
answered; cause is the (credential-redacted) decoded vendor body or a
redacted transport message, with a Retry-After header surfaced as
<<"retryAfter">>.
-type departure_time() :: integer() | calendar:datetime() | binary().
Unix seconds, a UTC calendar:datetime(), or an RFC 3339 binary
(<<"2026-11-02T08:00:00Z">>). An unparseable binary is invalid_request.
-type geocode_input() :: #{address := binary(), language => binary(), country_filter => [binary()], passthrough => passthrough()}.
-type geocode_result() :: #{candidates := [geocode_candidate()], raw := term()}.
-type isochrone_input() :: #{center := lat_lng(), type := time | distance, values := [number()], travel_mode => travel_mode(), departure_time => departure_time(), passthrough => passthrough()}.
-type isochrone_result() :: #{contours := [isochrone_contour()], raw := term(), meta => #{request_count := pos_integer()}}.
-type matrix_cell() :: #{origin_index := non_neg_integer(), destination_index := non_neg_integer(), distance_meters := float(), duration_seconds := float()}.
-type matrix_input() :: #{origins := [lat_lng()], destinations := [lat_lng()], travel_mode => travel_mode(), avoid_tolls => boolean(), departure_time => departure_time(), transport_mode => atom() | binary(), passthrough => passthrough(), traffic_mode => traffic_mode()}.
-type matrix_result() :: #{cells := [matrix_cell()], raw := term()}.
-type operation() ::
route | matrix | geocode | reverse_geocode | autocomplete | place_details | isochrone.
-type place_details_input() :: #{place_id := binary(), language => binary(), session_token => binary(), include => [name], passthrough => passthrough()}.
-type place_details_result() :: #{candidate => geocode_candidate(), name => binary(), raw := term()}.
-type provider() :: google | mapbox | here | esri | osrm | tomtom.
-type result(T) :: {ok, T} | {error, connector_error()}.
-type reverse_geocode_input() :: #{location := lat_lng(), language => binary(), passthrough => passthrough()}.
-type route_input() :: #{waypoints := [lat_lng()], optimize => boolean(), optimize_fixed_origin => boolean(), optimize_fixed_destination => boolean(), is_round_trip => boolean(), departure_time => departure_time(), avoid_tolls => boolean(), avoid_ferries => boolean(), avoid_highways => boolean(), travel_mode => travel_mode(), transport_mode => atom() | binary(), passthrough => passthrough(), polyline_quality => simplified | detailed, traffic_mode => traffic_mode(), include => [durationWithoutTraffic]}.
-type traffic_mode() :: none | traffic | live.
How much traffic a route or matrix is computed against. none (default):
time-independent durations — a departure_time never adds traffic. traffic:
the traffic expected at departure_time (now when absent). live: deprecated
alias of traffic, kept permanently. The default is a billing decision too:
on Google traffic-aware routing is a Pro-tier SKU, and HERE bills time-aware
routing unless traffic is explicitly disabled.
-type travel_mode() :: driving | walking | cycling.
Functions
-spec autocomplete(config(), autocomplete_input()) -> result(autocomplete_result()).
Suggest places for the partial input (all providers except OSRM).
-spec geocode(config(), geocode_input()) -> result(geocode_result()).
Forward-geocode address (all providers except OSRM).
-spec isochrone(config(), isochrone_input()) -> result(isochrone_result()).
Compute reachable-area contours around center (Mapbox, HERE, Esri, TomTom).
-spec matrix(config(), matrix_input()) -> result(matrix_result()).
Compute the origins x destinations distance/duration matrix (all six
providers). Cells a provider could not route are absent from cells.
-spec place_details(config(), place_details_input()) -> result(place_details_result()).
Resolve a place_id from autocomplete/2 into a full candidate (all
providers except OSRM). For Mapbox pass the same session_token as the
preceding autocomplete: Search Box bills per session.
-spec reverse_geocode(config(), reverse_geocode_input()) -> result(geocode_result()).
Reverse-geocode location (all providers except OSRM).
-spec route(config(), route_input()) -> result(route_result()).
Compute a route through waypoints (all six providers).
Whether Provider implements Operation.