OBC OpenBikeComputer / docs

Terrain and elevation

OpenBikeComputer uses one terrain raster for all elevation functions. The source is Copernicus DEM GLO-30. The obc-dem tool converts this source to the OBCT format.

Terrain cells have a separate catalog revision from map cells. The assembler copies selected terrain cells into the terrain region of the .obcm file. The packer and device use the same no_std sampler. Thus, all consumers use the same elevation values.

Data flow

one surface, baked once — then read by everything ① bake — on a host, once per dataset release Copernicus GLO-30 float32 GeoTIFF, 1″ tiled · DEFLATE obc-dem resample · flip rows round half away from 0 terrain cells 2¹⁹ µdeg · 1024² samples one .obcd per square terrain region spliced into the map OBCM §1.3, at its tail own revision track — an OBCM bump republishes none of it ② sample — obc-elevation, the only implementation of OBCT §5 integer bilinear · half-open cell ownership, cross-cell fetch at a seam, clamp at the coverage edge 4 × 512 B tile cache · a NODATA corner voids the whole query — never a guessed height ③ three consumers pack time — obc-pack walks each edge's polyline, samples at most 50 m apart, integrates through the dead-band → 2 B per direction, OBCM §8.3 route emit — on device fills every OBCR point's height, densifying to 250 m so a crest between two vertices can't hide → profile · climbs · stats · GPX live — altimeter fusion map − barometer at each fix, low-passed over ~5 minutes: that difference is the offset → absolute Current Elevation a map with no terrain → all three answer “no height here”, and nothing else in the system changes
The bakery publishes OBCT cells. The assembler puts the selected cells in the map. One sampler supplies all elevation consumers.

Peak View surface data

The production terrain baker writes a geographic surface index for Peak View. Native heights remain unchanged and are stored once. Coarser height levels and conservative maximum-height bounds let the renderer skip hidden terrain. Baked height and gradient error bounds also let it draw large smooth patches without visiting each small source cell. The extra coarse levels keep those patches' corner reads close together in storage. The data contains no stored viewpoints or images.

Peak View uses the loaded map and the current GPS position. It reads nearby named summits from that map, projects their geographic coordinates, and checks their visibility against the terrain. Each bearing sector keeps its tallest summit and one strong height-and-distance candidate. This prevents nearby lower hills from using both slots before a distant landmark can be checked. Before a GPS fix it waits. The renderer prepares the current field of view first, then fills the rest of the circle in the background, extending a buffer on both sides of the view. Static dots show that background work remains. Turning toward an unfinished view gives that view priority. After the first view appears, completed terrain continues to follow the heading. A light hatch marks pending parts until they are ready. Completed views reuse the panorama in RAM. Movement above 20 m starts another panorama after the current job finishes. Back cancels generation and releases the arena. The vertical scale is chosen once per observer from the catalogue elevation angles. Shallow relief receives up to 3× vertical exaggeration; steep views keep 1.25×. Missing height metadata keeps the ordinary scale. Terrain and labels share the projection, while bearings, elevations, distances and visibility stay geographic. Turning changes neither the scale nor the horizon position. Lighting has a fixed northwest world direction, so turning does not change the shading.

The renderer requests terrain out to 100 km. Missing distant coverage is marked with dashed bearing segments. Missing terrain at the observer or a storage read failure makes the view unavailable. Absent geographic cells are skipped without traversing their individual samples.

At the standard posting and cell size, an indexed cell occupies 3,149,824 bytes instead of 2,097,152 bytes. The assembler enforces a 10% limit on growth of the complete map, including summit data and alignment. It rejects a map that exceeds this limit before writing it. Terrain itself grows about 50.2%, so terrain-heavy sparse selections can exceed the complete-map budget. The limit does not guarantee that every selection can include the surface index.

The normal map bakery writes indexed terrain and named summit records directly. Re-bake all published terrain and geometry cells before publication. Catalog generation rejects mixed native and indexed terrain blocks. Standalone DEM baking produces native terrain; its surface conversion command adds the index without changing native heights.

Distance bands follow the source posting, so a finer source uses its fine cells over a shorter range. Smooth open terrain can merge into large patches. Rough mountain faces require more individual cells. Performance must therefore be checked at varied observer positions on the device. See OBCT section 8 for the byte layout and complete-map size rule.

One sampling truth

The packer samples OBCT tiles to calculate navigation-edge ascent. It also uses them to trace contour lines. The device samples the embedded raster for route heights and altimeter fusion. Route profiles, climb detection, and elevation statistics use these route heights.

The shared obc-elevation crate implements all sampling. The packer does not decode GeoTIFF data. Only obc-dem decodes the source DEM.

OBCT section 5 specifies integer bilinear interpolation. The calculation uses 64-bit integers and half-away-from-zero rounding. Independent implementations must return the same whole-meter height.

The sampler uses half-open cell ownership at seams. It reads a sample from the cell that owns that sample. It clamps the last sample at the outer coverage edge.

Terrain artifacts and map assembly

Published terrain cells use this identity tuple:

  • dataset_version
  • posting_log2
  • cell_log2
  • terrain_revision

The tuple does not contain an OBCM schema revision. Thus, a map schema change does not require a new terrain bake. See OBCC section 13 for the catalog contract.

The assembler verifies each selected cell against the catalog. It copies the cells into one OBCT container. The assembler puts this container in the map terrain region. The device reads terrain only from this region.

obc-pack --terrain has a different function. It samples OBCT input for contours and navigation-edge ascent. It does not put that input in its output map.

Navigation-edge ascent depends on a terrain revision. The catalog records this revision as network_terrain_revision. The bake guard rejects a network that uses a different terrain revision. This check keeps routing costs and route profiles on the same terrain surface.

Raster layout and resource use

The current published raster uses these values:

ItemValue
Posting2^9 microdegrees
Terrain cell side2^19 microdegrees
Samples per cell1024 × 1024
Tile size16 × 16 samples, 512 bytes
Sample typeLittle-endian signed 16-bit meters
NODATA value-32768
Default tile cacheFour tiles, approximately 2.1 KiB

The posting and cell size are OBCT header fields. A change to either value requires a terrain bake, not an OBCT version change. See the OBCT specification for all limits and byte layouts.

Routing ascent

Each navigation adjacency stores directional ascent_m. The value is the accumulated ascent along the edge polyline. It is not the elevation difference between the endpoints.

The packer samples each edge at intervals of at most 50 m. It applies the shared 3 m elevation dead band. The reverse adjacency stores the ascent for the reverse direction.

The router uses this cost:

cost = weighted_distance + ascent_m × climb_weight

A descent does not reduce the cost. See Weighting the climb for profile behavior.

Missing terrain

The sampler returns None when terrain is unavailable. It also returns None if a required sample is NODATA. Consumers must not replace None with zero elevation. Zero meters is a valid height.

For packer edge-ascent integration, None pauses the shared 3 m dead band. The next valid sample starts a new segment. Thus, a coverage gap contributes no ascent. Valid samples on each side can still contribute ascent in their segments.

FunctionBehavior without terrain
Map renderingRendering continues. Baked contour geometry does not require the raster.
RoutingRouting continues with the graph's baked ascent values.
Imported GPX routeThe route keeps its supplied heights.
Device-planned routeBefore the first valid sample, the route uses zero heights. After that sample, it carries the last valid height across gaps.
DetourThe splice interpolates between the seam elevations.
Ride recordingThe recorder stores the barometer measurement.
Current elevationThe UI uses the raw barometric estimate.
Time estimateThe model uses zero remaining ascent.

Coverage and NODATA

A bilinear query needs four sample corners. If one corner is NODATA, the query returns None. The sampler does not estimate a missing corner.

Route elevation parity requires terrain coverage for the complete route. OBCR has no per-point unknown-height value. Points before coverage starts use zero height. The route integrator does not use these placeholder points as an ascent anchor. The packer applies the pause rule at coverage boundaries and NODATA gaps. Device route filling carries the last valid height after coverage starts. A resumed sample can add ascent from that carried height.

Attribution

The data requires this attribution:

produced using Copernicus WorldDEM-30 © DLR e.V. 2010-2014 and © Airbus Defence and Space GmbH 2014-2018 provided under COPERNICUS by the European Union and ESA; all rights reserved

The source code stores this text in COPERNICUS_ATTRIBUTION. The bakery copies it to the catalog terrain block. Consumers read the text from the catalog. A map with derived contour geometry also requires this attribution.

Map data remains © OpenStreetMap contributors.

Implementation