---
operation_id: get_terrain_elevation
method: GET
path: /v1/terrain/elevation
summary: Geländehöhe an einem WGS84-Punkt aus DGM1
tags: [terrain]
stability: stable
since_version: 0.1.0
auth: none
data_product: terrain
snapshot_aware: true
attribution_block: false
rate_limit_tier: public
related:
  - /v1/terrain/datasets
  - /v1/terrain/profile
---
# `GET /v1/terrain/elevation` — Punktabfrage Geländehöhe

Liefert die Geländehöhe (DHHN2016, m über NHN) an einem WGS84-Punkt. Die API routet die Anfrage über einen PostGIS-Spatial-Index auf den zuständigen DGM1-Tile und sampelt das Pixel via GDAL/rasterio mit HTTP-Range-Request auf dem Cloud-Optimized GeoTIFF. **Kein Bundesland ist hartcodiert** — Punkte überall in Deutschland funktionieren, sofern Lodapi den DGM1-Layer für die zuständige Region ingested hat.

## Wann verwenden

- Höhenkote für einen Adresspunkt holen (z.B. für PV-Konfigurator, Statik-Vorabprüfung).
- Karten-UI mit Hover-Tooltip „Höhe an Cursor-Position".
- Geo-Pipeline: LoD2-Gebäude auf Geländehöhe normieren (Z-Differenz Gebäude-First-Floor – Terrain).

Wenn du **viele Punkte entlang einer Linie** sampeln willst → `/v1/terrain/profile`. Wenn du **eine Fläche** als Mesh brauchst → `/v1/terrain-mesh/datasets` (3D-Tiles-Mesh-Tilesets).

## Examples

### curl — Brandenburger Tor

```bash
curl -s 'https://api.lodapi.de/v1/terrain/elevation?lat=52.5163&lon=13.3777' | jq
```

```json
{
  "elevation_m": 34.95,
  "datum": "DHHN2016",
  "source_bl": "bb",
  "snapshot": "2025-12-18",
  "license": "dl-de-zero-2.0",
  "attribution": "© SenStadt Berlin (DL-DE/Zero 2.0)",
  "tile_id": "bb_33_388_5818"
}
```

### Python — Adresse → Höhe

```python
import httpx

def elevation_at(lat: float, lon: float) -> float | None:
    r = httpx.get(
        "https://api.lodapi.de/v1/terrain/elevation",
        params={"lat": lat, "lon": lon},
    )
    if r.status_code == 404:
        return None  # außerhalb der Coverage oder NoData (Wasserfläche)
    r.raise_for_status()
    return r.json()["elevation_m"]

print(elevation_at(52.5163, 13.3777))  # → 34.95
```

### TypeScript — Cesium-Integration

```ts
async function clampToGround(lon: number, lat: number) {
  const url = new URL("https://api.lodapi.de/v1/terrain/elevation");
  url.searchParams.set("lat", String(lat));
  url.searchParams.set("lon", String(lon));
  const r = await fetch(url);
  if (r.status === 404) return null;
  const { elevation_m } = await r.json();
  return Cesium.Cartesian3.fromDegrees(lon, lat, elevation_m);
}
```

## Parameters

| Parameter | In | Type | Required | Default | Range | Beschreibung |
|---|---|---|---|---|---|---|
| `lat` | query | float | yes | — | −90..90 | WGS84-Breite (Grad) |
| `lon` | query | float | yes | — | −180..180 | WGS84-Länge (Grad) |
| `srs` | query | string | no | `EPSG:4326` | — | Räumlicher Bezug. Aktuell nur EPSG:4326. |

## Response

`200 OK · application/json` — Schema-Quelle [`openapi.json`](../openapi/openapi.json) (`#/components/schemas/ElevationResponse`).

Felder:

| Feld | Beschreibung |
|---|---|
| `elevation_m` | Höhe in Metern, DHHN2016, gerundet auf 3 Nachkommastellen |
| `datum` | Vertikales Datum (immer `DHHN2016` in Phase 1) |
| `source_bl` | 2-Buchstaben-BL-Code des zuständigen Tile |
| `snapshot` | Snapshot-Datum des DGM1-Quelldatensatzes (ISO 8601) |
| `license` | Lizenz-ID (`dl-de-zero-2.0`, `cc-by-4.0`, `dl-de-by-2.0`) |
| `attribution` | Pflicht-Attribution-String für UI-Anzeige |
| `tile_id` | Eindeutige Tile-Kennung (Cache-Key oder Audit-Pfad) |

## Fehler

| Status | Bedeutung | Bedingung |
|---|---|---|
| `422` | Unprocessable Entity | `lat`/`lon` außerhalb Range, `srs` nicht EPSG:4326 (FastAPI/Pydantic-Validation) |
| `404` | Not Found, kein Tile | Punkt außerhalb aller ingested Terrain-Coverages |
| `404` | Not Found, NoData | Tile gefunden, aber Pixel ist NoData (Wasser, Tile-Rand) |
| `502` | Bad Gateway | COG-Quelle nicht erreichbar (Pass-Through-Modus auf Behörden-Server) |
| `503` | Service Unavailable | rasterio nicht installiert (deploy issue) |

Alle Fehler sind im **RFC-7807-Format** (`application/problem+json`).

## Stolperdrähte

- **Wasserflächen** liefern `404` mit Hinweis-Text, nicht `0.0`. Mancher PV-Konfigurator interpretiert `0` als „Meereshöhe" — der bewusste 404 verhindert das.
- **Tile-Rand-Pixel** werden 0.6 m einwärts gesampelt (siehe ADR-0009 §Edge-Cases) — sonst gibt es zwischen zwei benachbarten Tiles minimale Sprünge.
- **CRS-Wechsel** für östliche BL (BB/SN/MV/BE) erfolgt automatisch (UTM33N statt UTM32N). Aus Konsumentensicht: WGS84 rein, NHN raus.
- **`source_bl` ist die BL-Zuordnung des Datensatzes**, nicht zwingend des Standorts. An Bundesländer-Grenzen kann ein Punkt im BB-Tile liegen, obwohl er administrativ noch zu BE gehört.

## Live-Coverage

Stand 2026-06: alle 16 Bundesländer (16/16) via `/v1/terrain/elevation`. Aktuelles Inventar via `/v1/terrain/datasets`.

## Verwandte Endpoints

- [`GET /v1/terrain/datasets`](./list-terrain-datasets.md) — Coverage + Snapshot-Stand.
- [`GET /v1/terrain/profile`](./get-terrain-profile.md) — Linien-Sampling für mehrere Punkte.