# How the TeslaMate Geocoder Module Performs Automatic Address Lookup

> Discover how the TeslaMate Geocoder module automatically looks up addresses using the Nominatim OpenStreetMap API. Learn how it fetches location data from coordinates.

- Repository: [TeslaMate/teslamate](https://github.com/teslamate-org/teslamate)
- Tags: internals
- Published: 2026-06-16

---

**The TeslaMate Geocoder module performs automatic address lookup by wrapping the Nominatim OpenStreetMap API, using Tesla with Finch to fetch detailed location data from latitude and longitude coordinates.**

The `TeslaMate.Locations.Geocoder` module in the teslamate-org/teslamate repository provides the core functionality for converting raw GPS coordinates into structured address information. By leveraging the open-source Nominatim service, this Elixir module enables TeslaMate to automatically resolve vehicle locations without relying on proprietary mapping APIs.

## HTTP Client Configuration with Tesla and Finch

The Geocoder establishes its HTTP connection in [`lib/teslamate/locations/geocoder.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/locations/geocoder.ex) by configuring a **Tesla** client with the **Finch** adapter. The base URL is hardcoded to `https://nominatim.openstreetmap.org`, ensuring all requests route to the public Nominatim instance. A custom **User-Agent** header containing the current TeslaMate version is injected into every request to comply with Nominatim's usage policy. JSON middleware and a logger middleware complete the stack, handling serialization and observability respectively.

## Reverse Geocoding with `reverse_lookup/3`

The primary entry point for coordinate resolution is **`reverse_lookup/3`**, which accepts `lat`, `lon`, and an optional language parameter defaulting to `"en"`. This function constructs a query string requesting the JSON v2 format with detailed address components, extra tags, namedetails, and a high zoom level of 19 for maximum precision. These parameters ensure Nominatim returns granular address data suitable for vehicle location tracking.

## Executing Queries and Handling HTTP Responses

A private **`query/3`** helper manages the actual HTTP `GET` request execution. It attaches an `Accept-Language` header to the request and dispatches it via Tesla's `get/3` function. Successful HTTP 200 responses return `{:ok, body}`, while any network or server errors normalize to `{:error, reason}` tuples. This consistent error handling allows upstream callers to reliably pattern match on results.

## Parsing Nominatim JSON into Address Structs

Raw API responses undergo transformation via **`into_address/1`**, which converts Nominatim's JSON maps into TeslaMate's internal `Address` struct format. When Nominatim returns `{"error":"Unable to geocode"}`, the function generates a placeholder "unknown" address rather than crashing. For other errors, it returns `{:error, {:geocoding_failed, reason}}`. Successful responses extract fields like `display_name`, `osm_id`, `osm_type`, house number, road, neighbourhood, city, county, postcode, state, and country using alias lists to accommodate varying OpenStreetMap tag conventions.

## Batch Address Refresh with `details/2`

To minimize API round-trips when updating stored locations, the module exposes **`details/2`**. This function accepts a list of existing `Address` structs, extracts their OSM identifiers (`osm_id` and `osm_type`), and constructs a comma-separated `osm_ids` parameter. A single request to the `/lookup` endpoint fetches current details for all entities simultaneously, significantly improving efficiency when refreshing historical location data.

## Request Logging and Middleware Configuration

The logger middleware implements **`log_level/1`** to maintain clean log output. Successful responses log at `:info` level, while any response with an HTTP status code greater than or equal to 400 triggers a warning. This differentiation helps operators identify problematic API calls without cluttering logs with routine successful geocoding operations.

## Practical Code Examples

```elixir

# Simple reverse lookup (latitude, longitude)

{:ok, address} = TeslaMate.Locations.Geocoder.reverse_lookup(52.5200, 13.4050)

# → %{

#     display_name: "Berlin, Germany",

#     city: "Berlin",

#     country: "Germany",

#     latitude: "52.5200",

#     longitude: "13.4050",

#     …}

```

```elixir

# Batch refresh of address details for a list of stored addresses

addresses = [
  %TeslaMate.Locations.Address{osm_id: 12345, osm_type: "way"},
  %TeslaMate.Locations.Address{osm_id: 67890, osm_type: "node"}
]

{:ok, updated_addresses} = TeslaMate.Locations.Geocoder.details(addresses, "de")

```

## Summary

- The **`TeslaMate.Locations.Geocoder`** module wraps the Nominatim OpenStreetMap API to convert GPS coordinates into readable addresses.
- It uses **Tesla** with the **Finch** adapter and configures a custom User-Agent header for API compliance.
- **`reverse_lookup/3`** handles single coordinate resolution with detailed address component extraction.
- **`into_address/1`** parses JSON responses, handling both successful lookups and "Unable to geocode" errors gracefully.
- **`details/2`** enables efficient batch updates by querying multiple OSM IDs in a single request.
- Comprehensive logging middleware differentiates between routine operations and warnings for failed requests.

## Frequently Asked Questions

### What geocoding service does the TeslaMate Geocoder use?

The module utilizes the **Nominatim** service from OpenStreetMap, specifically targeting `https://nominatim.openstreetmap.org`. This open-source solution provides free reverse-geocoding capabilities without requiring proprietary API keys.

### How does the Geocoder handle coordinates that cannot be resolved?

When Nominatim returns `{"error":"Unable to geocode"}`, the **`into_address/1`** function generates a placeholder "unknown" address structure. For other network or parsing failures, it returns `{:error, {:geocoding_failed, reason}}`, allowing the application to handle missing data gracefully.

### What is the purpose of the `details/2` function in the Geocoder module?

The **`details/2`** function performs batch lookups for existing addresses by querying the `/lookup` endpoint with a comma-separated list of OSM identifiers. This reduces API traffic when refreshing metadata for multiple stored locations simultaneously.

### Which HTTP client adapter does the Geocoder module employ?

The module configures **Tesla** to use **`Tesla.Adapter.Finch`** as its HTTP client adapter. This provides efficient HTTP/2 support and connection pooling for requests to the Nominatim servers.