این صفحه هنوز ترجمه نشده است؛ نسخهٔ انگلیسی نمایش داده میشود.
Cesium terrain
Reader support for the two Cesium terrain tile formats (.terrain files) used for 3D terrain
visualization in web mapping: quantized-mesh-1.0 (adaptive triangle mesh, compressed via
quantization and delta encoding) and heightmap-1.0 (regular elevation grid). This module is
read-only — it parses tiles, answers height queries across tile sets, and converts terrain data
to raster DEMs; it does not write .terrain files. Ships in the
IRI.Maptor.Core.Spatial package.
Supported capabilities
| Capability | Supported | Implemented in |
|---|---|---|
| Read quantized-mesh-1.0 | Yes | QuantizedMeshReader.Read (path/stream/reader) |
| Read heightmap-1.0 | Yes | HeightmapReader.Read, IsHeightmapFormat |
| Auto-detect format | Yes | TerrainReader.ReadAuto, DetectFormat |
| Height queries (single tile / tile set) | Yes | TerrainReader.GetHeightAt/GetHeightAtPixel, TerrainHeightQuery |
Terrain → raster DEM (RasterGeoTiff) |
Yes | HeightmapRasterConverter.ToRasterGeoTiff, QuantizedMeshRasterConverter.ToRasterGeoTiff |
| Raster DEM → in-memory terrain data | Yes | HeightmapRasterConverter.FromRasterGeoTiff, QuantizedMeshRasterConverter.FromRasterGeoTiff |
Write .terrain files (either format) |
No | — |
Usage
Auto-detection and reading
using IRI.Maptor.Core.Spatial.IO.CesiumTerrain;
var (format, data) = TerrainReader.ReadAuto("path/to/tile/15/12345/67890.terrain");
if (format == TerrainFormat.QuantizedMesh)
{
var meshData = (QuantizedMeshData)data;
Console.WriteLine($"Mesh: {meshData.VertexCount} vertices");
}
else if (format == TerrainFormat.Heightmap)
{
var heightmapData = (HeightmapData)data;
Console.WriteLine($"Grid: {heightmapData.GridSize}x{heightmapData.GridSize}");
}
Height queries
// By file path and normalized tile coordinates (0-1)
float height = TerrainReader.GetHeightAt("tile.terrain", u: 0.5, v: 0.5);
// By z/x/y tile coordinates and pixel position
float pixelHeight = TerrainReader.GetHeightAtPixel(
terrainBasePath: @"C:\terrain",
zoom: 13, tileX: 4096, tileY: 2048,
pixelX: 128, pixelY: 128); // pixel in a 256x256 tile
TerrainHeightQuery handles multi-tile queries against a tile directory: GetHeightsForBoundary
returns a Matrix of elevations for a WGS84 bounding box at a zoom level (loading and merging all
intersecting tiles), GetHeightsForBoundaryWithSize/GetHeightsForDisplay resample to exact
output dimensions, and GetElevationProfile samples elevations along a list of lon/lat waypoints.
var (heights, actualBounds) = TerrainHeightQuery.GetHeightsForBoundary(
boundingBox, zoomLevel: 6, terrainBasePath: @"C:\terrain");
Reading quantized-mesh tiles
var terrainData = QuantizedMeshReader.Read("path/to/tile/15/12345/67890.terrain");
Console.WriteLine($"Height range: {terrainData.Header.MinimumHeight}m to {terrainData.Header.MaximumHeight}m");
Console.WriteLine($"Vertices: {terrainData.VertexCount}, Triangles: {terrainData.TriangleCount}");
// Vertex data (dequantized on access)
for (int i = 0; i < terrainData.VertexCount; i++)
{
double u = terrainData.GetNormalizedU(i); // [0, 1]
double v = terrainData.GetNormalizedV(i); // [0, 1]
double height = terrainData.GetHeight(i); // meters
}
// Triangle indices
for (int i = 0; i < terrainData.Indices.Length; i += 3)
{
uint v0 = terrainData.Indices[i];
uint v1 = terrainData.Indices[i + 1];
uint v2 = terrainData.Indices[i + 2];
}
Edge vertex indices (WestIndices, SouthIndices, EastIndices, NorthIndices) are exposed for
tile stitching.
Reading heightmap tiles
var heightmapData = HeightmapReader.Read("tile.terrain");
Console.WriteLine($"Grid size: {heightmapData.GridSize}x{heightmapData.GridSize}");
Console.WriteLine($"Height range: {heightmapData.MinHeight}m to {heightmapData.MaxHeight}m");
float heightAt = heightmapData.GetHeight(row: 128, col: 128);
float interpolated = heightmapData.GetInterpolatedHeight(u: 0.5, v: 0.5);
Tile coordinates
var tileCoord = new TerrainTileCoordinate(level: 15, x: 12345, y: 67890);
string fileName = tileCoord.GetFileName(); // "15/12345/67890.terrain"
var (west, south, east, north) = tileCoord.GetBoundingBox(); // WGS84 degrees
var parent = tileCoord.GetParent();
var children = tileCoord.GetChildren();
var coord = TerrainTileCoordinate.FromPath("data/terrain/15/12345/67890.terrain");
var fromGeo = TerrainTileCoordinate.FromGeographic(longitude: 51.4, latitude: 35.7, zoom: 12);
To convert vertex positions to geographic coordinates, combine the tile's bounding box with the
normalized vertex coordinates: longitude = west + u * (east - west),
latitude = south + v * (north - south).
Quantized-mesh extensions
var terrainData = QuantizedMeshReader.Read("tile.terrain");
if (terrainData.Extensions != null)
{
if (terrainData.Extensions.HasWaterMask)
{
byte[] waterMask = terrainData.Extensions.WaterMask; // 0 = land, 255 = water
}
if (terrainData.Extensions.HasVertexNormals)
{
byte[] normals = terrainData.Extensions.VertexNormals; // oct-encoded, 2 bytes/vertex
var (nx, ny, nz) = QuantizedMeshReader.DecodeOctNormal(normals[0], normals[1]);
}
if (terrainData.Extensions.HasMetadata)
{
string metadata = terrainData.Extensions.Metadata; // JSON string
}
}
Converting terrain to raster DEM
Both formats convert to RasterGeoTiff (see ../GeoTiff):
// Quantized-mesh -> raster (barycentric interpolation over the mesh)
var terrainData = QuantizedMeshReader.Read("15/12345/67890.terrain");
var tileCoord = new TerrainTileCoordinate(15, 12345, 67890);
var raster = QuantizedMeshRasterConverter.ToRasterGeoTiff(
terrainData, tileCoord, outputWidth: 512, outputHeight: 512);
Console.WriteLine($"Bounds: {raster.GeodeticWgs84BoundingBox}");
double elevation = raster.Data[256, 256];
// Heightmap -> raster
var heightmapData = HeightmapReader.Read("tile.terrain");
var raster2 = HeightmapRasterConverter.ToRasterGeoTiff(heightmapData, new TerrainTileCoordinate(5, 39, 20));
Converting raster DEM to in-memory terrain data
The reverse converters build in-memory HeightmapData/QuantizedMeshData objects from a raster
DEM. Because there is no .terrain writer, the result can be queried or converted back but
cannot be saved as a terrain tile.
var raster = TiffReader.ReadGeoTiff32bitDEM("dem.tif"); // IRI.Maptor.Core.Spatial.IO
var heightmap = HeightmapRasterConverter.FromRasterGeoTiff(raster, targetGridSize: 257);
var resampled = HeightmapRasterConverter.Resample(heightmap, targetGridSize: 65);
var mesh = QuantizedMeshRasterConverter.FromRasterGeoTiff(raster, new TerrainTileCoordinate(15, 12345, 67890));
Quantized-mesh binary layout
Header (88 bytes)
Vertex count uint32
U coordinates zigzag-encoded deltas
V coordinates zigzag-encoded deltas
Height values zigzag-encoded deltas
Triangle count uint32
Indices high-water-mark encoding
West/South/East/North edge vertex indices (for tile stitching)
Extensions optional (normals, water mask, metadata)
Header fields (all little-endian): tile center in ECEF (CenterX/Y/Z, 3 doubles),
MinimumHeight/MaximumHeight (2 floats), bounding sphere center and radius (4 doubles), and
horizon occlusion point (3 doubles).
U, V, and height values are quantized to 16-bit integers in [0, 32767];
actual = min + (quantized / 32767.0) * (max - min). Heightmap-1.0 files are a plain row-major
grid of elevation samples; the grid size is detected from the file length.
Tile pyramid
Terrain tiles live in a level/x/y pyramid addressed as {level}/{x}/{y}.terrain, with each level
doubling the resolution of the previous one. TerrainTileCoordinate implements the coordinate
arithmetic: file naming, parent/children navigation, geographic bounds, and lookup by geographic
position (see the tiling-scheme caveat under Limitations).
Format comparison
| Feature | heightmap-1.0 | quantized-mesh-1.0 |
|---|---|---|
| Structure | Regular grid | Triangle mesh |
| Sampling | Uniform | Adaptive |
| File size | Fixed (grid-based) | Variable |
| Height lookup | Direct grid access | Barycentric interpolation |
Conversion methodology
- Mesh → raster: for each output pixel, find the containing triangle, compute barycentric weights, and interpolate the height from the triangle's vertices.
- Raster → mesh: each valid raster cell becomes a vertex; each grid cell is split into two triangles; coordinates and heights are quantized to 16-bit values; edge vertices are extracted for stitching and ECEF header values computed.
- Raster → heightmap: bilinear resampling to a
2^n + 1grid.
Limitations
- Writing is not implemented for either format: there is no serializer that produces
.terrainfiles, soFromRasterGeoTiffresults exist in memory only. QuantizedMeshRasterConverter.FromRasterGeoTiffaccepts asimplificationToleranceparameter, but mesh simplification is not implemented — the full grid triangulation is always produced.- Heightmap grid sizes must be
2^n + 1(65, 129, 257, 513, ...); other sizes are rejected. TerrainTileCoordinateassumes a simplified geographic tiling with a single 2level x 2level grid per level — not the standard Cesium layout with two root tiles (2^(n+1) x 2n at level n).FromGeographicmaps latitude over 180/2level degrees per tile whileGetBoundingBoxuses 360/2^level on both axes, so providers using the standard scheme need their own coordinate mapping.- Quantized-mesh water mask, vertex normals, and metadata extensions are parsed but not interpreted beyond raw access.
- No tile caching or streaming; each query loads tiles from disk.
References
- Cesium quantized-mesh specification
- CesiumJS
- USAGE_EXAMPLES.md — additional code examples