Skip to content

helsingin/terrain-server

Repository files navigation

terrain-server

Terrain data becomes hard to use well once it leaves a desktop GIS workflow. Operational systems usually need fast elevation lookups, line-of-sight checks, terrain clearance, slope-aware routing, coverage masks, and repeatable answers from the same source data.

Those questions often end up scattered across application code, batch scripts, notebooks, and one-off preprocessing jobs.

That is an infrastructure problem, not just a map-rendering problem.

terrain-server is a Go library and HTTP service for terrain-derived answers from Copernicus DEM elevation grids. It provides a terrain sampler, tile index, bilinear interpolation, line-of-sight checks, terrain profiles, elevation grids, surface analysis, viewsheds, terrain masks, cache controls, OpenAPI metadata, and an HTTP API that other systems can call without knowing the internal GeoTIFF layout.

The default local run reads a compact JSON tile set so the core behavior is easy to test, review, and embed. The production backend is intentionally scoped to Copernicus DEM Cloud Optimized GeoTIFFs with a prebuilt terrain index, S3 range reads, and decoded internal-tile caching.

It is designed for teams that need a small terrain service for geospatial analysis, RF planning, sensor placement, route screening, simulation, or mission tooling before investing in a larger geospatial platform.

Quick Start

Run the local fixture-backed server:

make run

Open:

http://127.0.0.1:8080/healthz

Query an elevation:

curl "http://127.0.0.1:8080/v1/elevation?lat=60.005&lon=24.005"

Check line of sight:

curl "http://127.0.0.1:8080/v1/los?from_lat=60.000&from_lon=24.000&from_alt_m=65&to_lat=60.010&to_lon=24.010&to_alt_m=70"

Run the example clients:

examples/elevation_batch.sh
examples/analysis_queries.sh
examples/cache_ops.sh

The example scripts default to http://127.0.0.1:8080.

Use a different local address:

ADDR=127.0.0.1:5082 make run

Then run a client against that address:

BASE_URL=http://127.0.0.1:5082 examples/analysis_queries.sh

Run the Go test suite:

make test

Build the server and CLI binaries:

make build

Build and smoke-test the container image:

make container-test

Run against the Copernicus object-store backend:

make run-s3

That command uses config/s3-terrain.json and expects credentials in config/credentials.env. The credentials file is ignored by git.

What It Does

terrain-server handles common terrain analysis workflows:

  • Loads fixture terrain from one JSON tile, a list of tiles, or a tile set.
  • Validates grid dimensions, sample counts, pixel sizes, and sample values.
  • Builds an in-memory tile index for point lookup.
  • Samples elevation with bilinear interpolation.
  • Reports whether a point is above ground.
  • Computes clearance above ground for an absolute-altitude position.
  • Checks line of sight between two absolute-altitude positions.
  • Reports the first blocking terrain point and minimum clearance along a path.
  • Samples elevation grids for map and analysis clients.
  • Samples terrain profiles along point-to-point paths.
  • Computes slope, aspect, and surface normal at a point.
  • Samples elevations in batch for route and planning workflows.
  • Computes simple viewsheds from an observer point.
  • Generates terrain masks for altitude-layered grids.
  • Reads cloud-hosted Copernicus DEM GeoTIFF terrain through indexed range requests.
  • Decodes zlib-compressed internal terrain blocks with float32 predictor data.
  • Applies effective-Earth-curvature and optional Fresnel-zone clearance in line-of-sight checks.
  • Exposes binary grid and mask outputs for compact clients.
  • Exposes GeoJSON profile and viewshed outputs for mapping clients.
  • Exposes health, readiness, metrics, cache stats, cache clear, and cache prewarm endpoints.
  • Emits structured JSON request logs.
  • Returns stable error envelopes with machine-readable error codes.
  • Applies configurable request, timeout, sample, mask, and prewarm limits.
  • Provides CLI tooling for index inspection, coverage checks, live sampling, and quick backend benchmarks.

The HTTP server stays behind a small Go interface. Backends can change from fixture data to object-store terrain without changing endpoint behavior.

Terrain Data and Geospatial Workflows

Most systems do not need a full GIS server for every terrain query. They need a small service that can answer a few operational questions consistently:

  • What is the elevation at this latitude and longitude?
  • Does this sensor have line of sight to that target?
  • How much terrain clearance does this route or link have?
  • What does the elevation profile along this route look like?
  • What areas are visible from this observer height?
  • Which altitude-layered grid cells are below terrain?
  • Which terrain tile covers this point?
  • Can this application use the same terrain logic in tests and production?

terrain-server treats those questions as service behavior. The public API deals in latitude, longitude, meters, JSON, GeoJSON, and compact binary responses. The Go package owns tile validation, index lookup, interpolation, path sampling, object-store range reads, and terrain-specific analysis.

The starter JSON data format is intentionally plain. It keeps the repository easy to audit and gives tests deterministic fixtures. The object-store backend is Copernicus-specific: it uses the terrain index, one range request per internal tile, and an in-memory LRU cache for decoded blocks.

Public background:

Typical Pattern

Application or analysis job
        |
        | HTTP JSON API or Go interface
        v
 terrain-server
        |
        +-- tile index
        +-- terrain sampler
        +-- interpolation
        +-- line-of-sight checker
        +-- analysis endpoints
        |
        v
 elevation grid tiles

For the default local run:

terrain-server process
  example JSON tile
  in-memory index
  HTTP API on 127.0.0.1:8080

For object-store terrain:

terrain-server process or container
  committed terrain index
  ignored S3 credentials file
  range reads into Copernicus DEM COG objects
  decoded internal-tile LRU cache
  HTTP API on 127.0.0.1:8080

Terrain Workflows

Elevation Sampling

Sample one point or a batch of points in decimal degrees. Batch responses can include terrain clearance when the caller provides absolute altitude.

Line-of-Sight Review

Check whether terrain blocks a path between two absolute-altitude points. The LOS checker can include required clearance, effective-Earth curvature, and first-Fresnel-zone clearance for RF planning workflows.

Terrain Profile

Sample a point-to-point path and return terrain elevation, optional ray altitude, and clearance values along the path. Profiles can be returned as JSON or GeoJSON.

Elevation Grid

Sample a rectangular area into a regular grid. Clients can request JSON or row-major little-endian float32 output for compact local tools.

Surface Analysis

Compute elevation, gradient, slope, aspect, and surface normal around a point using central differences.

Viewshed

Estimate visible cells around an observer point using repeated line-of-sight checks over a bounded sample grid.

Terrain Mask

Generate a solid/clear mask for an altitude-layered grid. This is useful for simulation, planning, and routing systems that need terrain occupancy rather than raw elevation samples.

Copernicus Backend

The production backend is scoped to Copernicus DEM 30m Cloud Optimized GeoTIFFs. It uses:

  • data/terrain-index.bin: committed prebuilt terrain index.
  • S3-compatible object storage.
  • Range requests for only the internal GeoTIFF tile needed by a query.
  • A decoded internal-tile LRU cache.
  • Singleflight fetch behavior for concurrent requests to the same terrain block.

The object-store config is:

config/s3-terrain.json

Credentials live outside git:

config/credentials.env

The credentials file uses the same names understood by common S3 tooling:

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

The container image includes the terrain index so the Copernicus backend can run without a separate index mount. Credentials are not included in the image.

Rebuild the index from the configured object-store bucket:

make build-index

Test the object-store backend:

make test-s3

Those tests validate live sampling, readiness, cache prewarm and clear, and terrain analysis against the configured bucket.

CLI

The terrain-tool binary exposes small inspection and backend checks from the terminal.

Inspect the committed terrain index:

make inspect-index

Verify basic index structure and a known covered tile:

make verify-index

Check whether a point is covered by the index:

go run ./cmd/terrain-tool coverage -lat 60.005 -lon 24.005

Sample the object-store backend directly:

make sample

Run a small backend benchmark:

make bench

Build a fresh index from the configured object store:

go run ./cmd/build-terrain-index -config config/s3-terrain.json -credentials config/credentials.env

HTTP API

The OpenAPI 3.0 specification is available at:

openapi.yaml

The server exposes terrain and operations routes:

GET   /healthz
GET   /readyz
GET   /metrics
GET   /v1/tiles
GET   /v1/cache
POST  /v1/cache/clear
POST  /v1/cache/prewarm
GET   /v1/elevation
POST  /v1/elevation/batch
GET   /v1/grid
GET   /v1/profile
GET   /v1/surface
GET   /v1/los
GET   /v1/viewshed
GET   /v1/mask

Health only says the process can respond:

curl http://127.0.0.1:8080/healthz

Readiness verifies that the configured terrain backend is usable. For the Copernicus object-store backend, readiness verifies that the terrain index is loaded and the configured bucket can be reached with the configured credentials. If the S3 connection is not alive, readiness returns HTTP 503.

curl http://127.0.0.1:8080/readyz

Prometheus-style metrics include HTTP traffic, request duration, object-store reads, bytes read, index size, cache entries, cache capacity, cache hits, cache misses, cache evictions, and estimated maximum cache memory.

curl http://127.0.0.1:8080/metrics

Elevation example:

curl "http://127.0.0.1:8080/v1/elevation?lat=60.005&lon=24.005"

Grid example:

curl "http://127.0.0.1:8080/v1/grid?min_lat=60&max_lat=60.01&min_lon=24&max_lon=24.01&resolution=16"

Profile example:

curl "http://127.0.0.1:8080/v1/profile?from_lat=60&from_lon=24&to_lat=60.01&to_lon=24.01&step_m=100"

Line-of-sight example:

curl "http://127.0.0.1:8080/v1/los?from_lat=60.000&from_lon=24.000&from_alt_m=65&to_lat=60.010&to_lon=24.010&to_alt_m=70&clearance_m=5"

The API is local-service oriented today. Production use would need deployment policy around authentication, authorization, TLS termination, audit logging, rate limits, and object-store credential handling.

Configuration

The server reads a tracked JSON config file and an ignored credentials file.

The default local config is:

config/terrain-server.json

The object-store config is:

config/s3-terrain.json

Run with a different config file:

CONFIG=/path/to/terrain-server.json make run

Operational limits are configured in the JSON config:

{
  "limits": {
    "max_request_body_bytes": 1048576,
    "default_timeout_ms": 10000,
    "expensive_timeout_ms": 30000,
    "readiness_timeout_ms": 3000,
    "max_timeout_ms": 120000,
    "max_batch_points": 262144,
    "max_grid_samples": 262144,
    "max_viewshed_samples": 262144,
    "max_mask_cells": 1048576,
    "max_prewarm_tiles": 4096
  }
}

The server rejects invalid numeric parameters instead of silently falling back to defaults. Latitude and longitude are range-checked, request bodies are bounded, and expensive terrain operations use bounded request timeouts.

Error responses use a stable envelope:

{
  "error": {
    "code": "invalid_parameter",
    "message": "query parameter is required: lat"
  }
}

Common codes include invalid_parameter, invalid_json, request_too_large, not_found, terrain_out_of_bounds, terrain_no_data, backend_unavailable, backend_error, and internal_error.

Mirroring Copernicus DEM to S3

The original bucket-to-bucket setup used rclone with one remote for the public Copernicus AWS Open Data bucket and one remote for the target S3-compatible bucket.

The checked-in example config is:

config/rclone.conf.example

The real config path is ignored by git:

config/rclone.conf

The example includes fake target credentials and the same remote names used by the mirror script:

copernicus-aws:
hetzner:

Preview what rclone would do:

make mirror-copernicus-dry-run

Mirror into the target bucket:

make mirror-copernicus

By default the script uses rclone copy, which is safe for a bucket that may already contain other objects.

To make the target an exact mirror:

MIRROR_MODE=sync make mirror-copernicus

That is equivalent to the original setup command:

rclone sync copernicus-aws:copernicus-dem-30m hetzner:copernicus-dem-30m --progress --transfers 8 --checkers 16

What You Can Build With It

Terrain Query Service

Run a small HTTP service that gives application teams stable elevation, clearance, profile, grid, and mask answers without embedding GeoTIFF parsing in each application.

RF Planning Tool

Use line-of-sight, curvature, and Fresnel-zone clearance checks to screen links before moving into deeper propagation modeling.

Sensor Placement Lab

Use elevation grids, surface analysis, and viewsheds to compare observer locations and target visibility.

Route Screening Job

Use batch elevation and profile calls to find terrain clearance along planned routes.

Simulation Terrain Adapter

Use masks and terrain sampling through the Go interface to feed planning, physics, or agent simulations with repeatable terrain behavior.

What an Integration Needs

For a fixture-backed integration, the deployment needs:

  1. A JSON tile file or generated tile set.
  2. A config file that points at that fixture data.
  3. A caller that understands latitude, longitude, and meters.

For a Copernicus object-store integration, the deployment needs:

  1. A Copernicus DEM bucket mirrored into an S3-compatible object store.
  2. A terrain index matching that bucket layout.
  3. Object-store credentials supplied outside git.
  4. Cache sizing that matches expected query locality and available memory.
  5. Readiness checks wired into deployment health policy.
  6. A strategy for request limits, timeout limits, and expensive query usage.

The service should own terrain layout and decoding. Application code should only need to ask terrain questions in geospatial units.

What It Is Not

terrain-server is not a full GIS platform, map tile renderer, raster catalog, spatial database, routing engine, or general-purpose GeoTIFF service.

That is intentional.

It is a focused terrain query and analysis service. It shows how Copernicus DEM data can be turned into repeatable operational answers without making every application understand DEM storage details.

Main Files

  • cmd/terrain-server/main.go: HTTP server entrypoint.
  • cmd/terrain-tool/main.go: CLI for index inspection, coverage, sampling, and benchmark checks.
  • cmd/build-terrain-index/main.go: terrain index builder for Copernicus object-store buckets.
  • server/: HTTP API, request limits, readiness, metrics, logging, and error responses.
  • settings/: config and credentials loading.
  • terrain/source.go: fixture terrain source and sampler.
  • terrain/object_source.go: Copernicus object-store terrain source.
  • terrain/object_index.go: binary terrain index loading and lookup.
  • terrain/object_cache.go: decoded internal-tile LRU cache.
  • terrain/los.go: line-of-sight and clearance calculations.
  • terrain/analysis.go: grid, profile, surface, viewshed, and mask analysis.
  • config/: tracked local, object-store, credentials, and rclone examples.
  • data/terrain-index.bin: committed Copernicus terrain index.
  • examples/: curl clients and fixture terrain tile.
  • scripts/mirror-copernicus-s3.sh: rclone bucket mirror helper.
  • openapi.yaml: machine-readable API specification.
  • Dockerfile: container image for fixture and object-store server runs.

Verification

Run the Go unit tests:

make test

Run the object-store integration tests:

make test-s3

Build the server and CLI binaries:

make build

Build and smoke-test the default container:

make container-test

The smoke test exercises:

  1. Container image build.
  2. Server startup.
  3. Health and readiness endpoints.
  4. Metrics endpoint.
  5. Batch elevation example client.
  6. Terrain analysis example client.
  7. Cache control example client.

Container

The Makefile uses docker when it is installed, otherwise podman.

Build the image:

make container-build

Run the fixture-backed server:

make container-up

Smoke-test a running container:

make container-smoke

Run the full build, start, smoke-test, and cleanup flow:

make container-test

Use a different host port:

PORT=5082 make container-test

Run it with Docker:

docker run --rm -p 8080:8080 terrain-server:dev

Run it with Podman:

podman run --rm -p 8080:8080 terrain-server:dev

Override the runtime:

CONTAINER_TOOL=podman make container-test

The Makefile defaults are:

CONTAINER_TOOL=docker when available, otherwise podman
IMAGE=terrain-server:dev
CONTAINER_NAME=terrain-server
PORT=8080
BASE_URL=http://127.0.0.1:8080

Object Store Mode

Run the Copernicus object-store backend locally:

make run-s3

Run the Copernicus object-store backend in a container:

make container-up-s3

That target mounts config/credentials.env read-only into the container. The tracked config and terrain index are already present in the image.

Stop the container:

make container-down

The object-store backend is ready only when the S3 connection is alive and verified. A failed S3 readiness check returns HTTP 503 from /readyz.

Tested Environments

terrain-server is expected to run on standard Go-supported Unix-like systems. The local demo path is intended for:

  • macOS with Docker or Podman.
  • Linux with Docker or Podman.
  • S3-compatible object storage that supports range reads.
  • Copernicus DEM 30m Cloud Optimized GeoTIFF bucket layouts.

The service is written in Go and has no frontend build step.

Security

This repository is intended for transparent examples, repeatable local tests, and terrain integration work.

Do not commit real object-store credentials, private bucket details, production endpoints, API tokens, deployment-specific policy, or sensitive mission data.

For production use, add authentication, authorization, TLS, audit logging, rate limits, object-store credential rotation, operational monitoring, backup and recovery procedures, and deployment-specific access controls.

See SECURITY.md for vulnerability reporting and credential handling guidance.

License

Apache License 2.0. See LICENSE.

Summary

terrain-server makes terrain queries concrete.

It gives developers and integrators one place to sample Copernicus DEM elevations, check terrain line of sight, generate profiles and masks, run object-store terrain from an indexed bucket, and expose those answers through a small HTTP API or Go interface.

About

Go HTTP service and library for Copernicus DEM terrain queries, line-of-sight checks, terrain profiles, masks, and S3-backed elevation sampling.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors