# How the TeslaMate Terrain Module Fetches and Caches Elevation Data from SRTM

> Discover how the TeslaMate Terrain module efficiently fetches and caches SRTM elevation data with its GenStateMachine and disk caching. Optimize your Tesla logs today.

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

---

**The TeslaMate Terrain module enriches vehicle position logs with elevation data by querying the Shuttle Radar Topography Mission (SRTM) dataset through a circuit-breaker-protected GenStateMachine that automatically caches DEM tiles to disk for subsequent lookups.**

The `TeslaMate.Terrain` module in the [teslamate-org/teslamate](https://github.com/teslamate-org/teslamate) repository provides altitude information for logged drives and charging sessions. It integrates with the external SRTM library to fetch elevation data from NASA's Shuttle Radar Topography Mission while implementing robust error handling and local disk caching to minimize external API calls.

## Circuit-Breaker Protection with Fuse

Before attempting any network request, the module validates system health using the **Fuse** circuit-breaker library. This prevents cascade failures when the SRTM service experiences downtime.

### Fuse Initialization and Policies

When the Terrain process starts, it checks for an existing fuse using `:fuse.ask/2`. If no fuse exists, the module creates one with a standard policy defined in [`lib/teslamate/terrain.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/terrain.ex): **2 failures within a 3-minute window** trigger a **15-minute reset period**. This conservative approach ensures temporary network blips don't permanentlydisable elevation lookups.

### Fuse State Handling

The module evaluates fuse state before each elevation request:

- **If OK**: The request proceeds to `do_get_elevation/2`
- **If blown**: Returns `{:error, :unavailable}` immediately, preventing further load on the SRTM service
- **If melted**: The fuse enters recovery mode based on the configured reset timeout

When the SRTM call fails, the code explicitly melts the fuse:

```elixir
with {:error, reason} <-
       call(srtm, :get_elevation, [lat, lng, [disk_cache_path: cache_path()]]) do
  :fuse.melt(name)
  {:error, reason}
end

```

## Fetching Elevation from SRTM

The actual elevation lookup occurs in the private function `do_get_elevation/2`, which coordinates between the GenStateMachine state and the external SRTM library.

### The do_get_elevation/2 Implementation

This function forwards coordinates to the SRTM library along with disk cache configuration:

```elixir
defp do_get_elevation(%{data: %{srtm: srtm}} = state, {lat, lng} = coord) do
  case call(srtm, :get_elevation, [lat, lng, [disk_cache_path: cache_path()]]) do
    {:ok, elevation} -> 
      {:ok, elevation, state}
    {:error, reason} -> 
      :fuse.melt(name)
      {:error, reason}
  end
end

```

The **disk cache path** is retrieved from the application environment via `Application.fetch_env!(:teslamate, :srtm_cache)`, ensuring each downloaded DEM tile persists between application restarts.

### Dependency Injection Pattern

The module uses a `deps` map injected during initialization, defaulting to the `SRTM` module. This architecture, visible in [`lib/teslamate/terrain.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/terrain.ex), facilitates testing through mocks while maintaining clean separation between the state machine logic and the external HTTP client.

## Disk Caching Strategy

The **SRTM library** handles tile management automatically when provided with a cache directory. This eliminates redundant network requests for coordinates falling within previously fetched tiles.

### Cache Configuration

Configure the cache location in [`config/config.exs`](https://github.com/teslamate-org/teslamate/blob/main/config/config.exs):

```elixir
config :teslamate,
  srtm_cache: Path.expand("../srtm_cache", __DIR__)

```

This path is passed to every elevation lookup via the `:disk_cache_path` option. The SRTM library writes each downloaded GeoTIFF tile to this directory, checking for local file existence before initiating HTTP requests.

### Automatic Tile Reuse

When `do_get_elevation/2` requests elevation for coordinates, the SRTM library:
1. Calculates the containing tile index
2. Checks the configured cache path for existing files
3. Returns cached elevation data if available
4. Downloads only missing tiles from the SRTM server

This strategy dramatically reduces latency for subsequent queries within the same geographic region and minimizes external bandwidth consumption.

## Background Processing Pipeline

The Terrain module operates as a **GenStateMachine** that continuously backfills missing elevation data for historical positions.

### Scanning log_positions

A background job periodically executes `get_positions_without_elevation/2`, querying the database for entries lacking altitude data. For each batch of positions, the state machine spawns short-lived Tasks that call `do_get_elevation/2` independently, enabling concurrent processing without blocking the main event loop.

### Handling Timeouts

Elevation requests exceeding the configured timeout are handled asynchronously through `:info` messages. This ensures the state machine remains responsive to new vehicle telemetry while elevation data populates in the background. Successfully retrieved elevations are persisted to the database via `Log.update_position/2`.

## Configuration and Usage Examples

### Basic Elevation Lookup

Fetch elevation for specific coordinates after ensuring the Terrain GenServer runs:

```elixir
{:ok, _pid} = TeslaMate.Terrain.start_link(name: MyTerrain)
elevation = TeslaMate.Terrain.get_elevation(MyTerrain, {37.7749, -122.4194})
IO.puts("Elevation: #{elevation || "unknown"} m")

```

### Manual Backfill Trigger

Force processing of the next pending batch (normally handled automatically):

```elixir
:gen_statem.cast(MyTerrain, :process)

```

### Cache Directory Setup

Ensure the cache directory exists with appropriate permissions:

```elixir
cache_dir = Application.fetch_env!(:teslamate, :srtm_cache)
File.mkdir_p!(cache_dir)

```

## Summary

- **The TeslaMate Terrain module** uses a GenStateMachine architecture to asynchronously enrich vehicle logs with elevation data from NASA's SRTM dataset.
- **Circuit-breaker protection** via the Fuse library prevents system overload during SRTM service outages, implementing automatic recovery with a 15-minute reset window after 2 failures.
- **Intelligent disk caching** stores downloaded DEM tiles locally using the configured `:srtm_cache` path, eliminating redundant network requests for coordinates within cached regions.
- **Background processing** continuously scans the `log_positions` table for entries lacking elevation, handling timeouts asynchronously to maintain system responsiveness.

## Frequently Asked Questions

### What is SRTM and why does TeslaMate use it?

**SRTM** stands for Shuttle Radar Topography Mission, a NASA dataset providing near-global elevation data. TeslaMate uses this public domain data to add altitude context to driving and charging logs without requiring proprietary mapping APIs or additional subscription costs.

### Where does TeslaMate store cached elevation tiles?

Cached tiles are stored in the directory specified by the `:srtm_cache` configuration value in [`config/config.exs`](https://github.com/teslamate-org/teslamate/blob/main/config/config.exs). The SRTM library automatically manages these files, organizing them by tile coordinates and validating checksums to ensure data integrity.

### What happens when elevation requests fail or timeout?

If the SRTM service is unavailable, the **Fuse circuit breaker** melts after 2 failures, returning `{:error, :unavailable}` for subsequent requests during the 15-minute cooldown. Individual request timeouts are handled asynchronously—the Task result arrives via a `:info` message later, allowing the state machine to continue processing other positions without blocking.

### Can I disable or configure the elevation fetching behavior?

While you cannot completely disable the Terrain module without modifying the supervision tree, you can configure the **cache location** via the `:srtm_cache` environment variable or adjust fuse parameters by modifying the standard policy defined in [`lib/teslamate/terrain.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/terrain.ex). The module gracefully handles missing elevation data, storing `NULL` values when the service is unavailable.