# How the TeslaMate Terrain Module Handles Elevation Data

> Discover how the TeslaMate Terrain module enriches GPS data with elevation using SRTM, circuit breakers, disk caching, and batched updates for efficient processing.

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

---

**The TeslaMate Terrain module is a GenStateMachine that asynchronously enriches GPS positions with elevation data from the Shuttle Radar Topography Mission (SRTM), using circuit-breaker protection, disk caching, and batched updates every six hours.**

The TeslaMate Terrain module handles elevation data by integrating with NASA's SRTM dataset to augment vehicle location logs with accurate altitude measurements. Implemented as a robust GenStateMachine in the `teslamate-org/teslamate` repository, this background process ensures reliable elevation lookups without blocking the main application workflow.

## Architecture and State Machine Design

The `TeslaMate.Terrain` module operates as a **GenStateMachine** that cycles through distinct states to manage the elevation enrichment pipeline. Located in [`lib/teslamate/terrain.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/terrain.ex), the process begins in the `:ready` state and coordinates with the **Log** module to identify and update position records lacking altitude information.

The module uses **dependency injection** for its SRTM client, defaulting to the `SRTM` module but allowing test mocks via the `:deps_srtm` option. This design enables isolated testing while maintaining production reliability through **circuit-breaker patterns** and **asynchronous task processing**.

## Step-by-Step Elevation Processing Workflow

### Initialization and Scheduling

Upon startup, the `init/1` function transitions the state machine to `:ready` and immediately triggers an internal event to fetch positions. The system schedules periodic re-fetch operations using `schedule_fetch/0`, which sets a six-hour timeout to continuously process new location data.

### Batched Position Retrieval

The state machine queries the database through `Log.get_positions_without_elevation/2`, retrieving up to **1,000 positions** per batch. If no elevation-less records exist, the process idles until the next scheduled fetch. This batching strategy prevents memory overload while ensuring comprehensive coverage of historical GPS data.

### Asynchronous Elevation Lookup

For each retrieved position, the module spawns a supervised `Task` to execute `do_get_elevation/2`. These tasks operate with a strict **100-millisecond timeout** to prevent blocking the main process. The lookup function checks a **Fuse circuit-breaker** before forwarding requests to the SRTM dependency via `SRTM.get_elevation/3`.

### Circuit-Breaker Protection

The module implements fault tolerance using the Fuse library with a **2-failure threshold** and **3-minute lockout period**. When the breaker is closed, requests proceed to the external service; when blown, `do_get_elevation/2` immediately returns `{:error, :unavailable}`, protecting the SRTM service from overload during outages.

### Disk Caching Strategy

Elevation requests include a `disk_cache_path` parameter extracted from the application environment (`:srtm_cache`). The SRTM library stores raw `.hgt` tiles locally, eliminating redundant network calls for previously accessed geographic regions. This caching mechanism significantly improves performance for repeated routes.

### Database Updates and Error Handling

Successful elevation lookups trigger `Log.update_position/2` to persist altitude data to the database. When tasks exceed the timeout threshold, the state machine transitions to a `{:waiting, task.ref}` state, eventually processing delayed replies through `:info` messages without data loss. Failures are logged at `warning` level, while informational messages track delayed queries.

## Integration with the SRTM Library

The Terrain module delegates actual elevation calculations to the **SRTM** library through a well-defined interface. The `SRTM.get_elevation/3` function accepts latitude, longitude, and options including the disk cache path, returning `{:ok, meters}` or `{:error, reason}`.

For testing, the repository includes [`test/support/mocks/srtm.ex`](https://github.com/teslamate-org/teslamate/blob/main/test/support/mocks/srtm.ex), which implements a lightweight mock returning fixed elevation values:

```elixir
defmodule SRTMMock do
  def get_elevation(lat, lng, _opts) do
    send(state.pid, {SRTM, {:get_elevation, lat, lng, []}})
    {:ok, 123}
  end
end

```

This mock demonstrates the expected contract: accepting coordinates and options, then returning a tuple with the elevation in meters.

## Practical Code Examples

### Querying Elevation Directly

You can request elevation data for specific coordinates through the public API:

```elixir
{:ok, elevation} = TeslaMate.Terrain.get_elevation({52.5200, 13.4050})
IO.puts("Elevation: #{elevation} m")

```

This call blocks for up to the configured timeout (default 2000ms) and returns `nil` if the service is unavailable.

### Custom SRTM Implementation

During testing or when using alternative elevation sources, inject a custom module:

```elixir
{:ok, pid} = TeslaMate.Terrain.start_link(
  deps_srtm: MyApp.CustomSRTM,
  timeout: 500
)

{:ok, elevation} = TeslaMate.Terrain.get_elevation({40.7128, -74.0060})

```

### Inspecting Process State

Monitor the background enrichment process using Erlang's system functions:

```elixir
terrain_pid = Process.whereis(TeslaMate.Terrain)
{:state, state, data} = :sys.get_state(terrain_pid)
IO.inspect(state)  # => :ready, :waiting, etc.

```

## Summary

- The **TeslaMate.Terrain** module is a GenStateMachine in [`lib/teslamate/terrain.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/terrain.ex) that manages elevation enrichment as a background process.
- It retrieves up to **1,000 positions** per batch from the Log module, processing them asynchronously with **100ms timeouts**.
- **Circuit-breaker protection** using Fuse prevents cascade failures with a 2-failure threshold and 3-minute recovery window.
- **Disk caching** via the `:srtm_cache` configuration stores SRTM tiles locally to minimize network requests.
- The module updates elevation data through `Log.update_position/2` and operates on a **6-hour polling cycle** for continuous data enrichment.

## Frequently Asked Questions

### How does the Terrain module handle SRTM service outages?

The module implements a **circuit-breaker pattern** using the Fuse library. After two consecutive failures, the breaker opens for three minutes, immediately returning `{:error, :unavailable}` for subsequent requests rather than overwhelming the external service. This protects both the TeslaMate application and the SRTM data source during network interruptions.

### What happens when elevation lookups take too long?

When an asynchronous task exceeds the **100-millisecond timeout**, the state machine transitions to a `{:waiting, task.ref}` state and returns `nil` to the caller. The process later handles the delayed result via `:info` messages, ensuring the position remains available for future processing cycles without blocking the pipeline.

### Can I use a different elevation data source instead of SRTM?

Yes. The module supports **dependency injection** through the `:deps_srtm` option. You can provide any module implementing the `get_elevation/3` callback that accepts latitude, longitude, and options, then returns `{:ok, elevation}` or `{:error, reason}`. The test suite demonstrates this pattern using `SRTMMock` in [`test/support/mocks/srtm.ex`](https://github.com/teslamate-org/teslamate/blob/main/test/support/mocks/srtm.ex).

### Where is the SRTM tile cache stored?

The cache location is configured via the `:srtm_cache` application environment variable, typically set in [`config/config.exs`](https://github.com/teslamate-org/teslamate/blob/main/config/config.exs). The Terrain module passes this path as `disk_cache_path` to `SRTM.get_elevation/3`, allowing the underlying library to persist `.hgt` tiles locally and avoid redundant downloads for previously accessed coordinates.