Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

ElevationTileLayer

Brian Kircher edited this page Jul 13, 2020 · 22 revisions

The elevation data used to render the map can be configured via the ElevationTileLayer system. The MapRenderer calls into an ElevationTileLayer to request data relevant to the current view. The layer is responsible for retrieving and providing elevation data on a per-tile basis.

Hillshaded terrain of the Deschutes River valley from the DefaultElevationTileLayer. Using the ElevationTileLayer to provide higher detail data based on LIDAR collection, sourced from DOGAMI.

The DefaultElevationTileLayer uses Bing Maps elevation data. This layer has global coverage with varying degrees of resolution.

As shown in the image above, a custom data set can provide higher detail.

Anatomy of an ElevationTile

Similar to a TextureTile, an ElevationTile covers the extents of a specified Web Mercator tile. However, instead of the tile representing colors, values represent elevations. In this sense, the elevation tile is simply a heightmap.

An ElevationTile visualized as a grid of elevation values. Each elevation value can be mapped to a vertex in a mesh.

For rendering, the elevation data is used to offset the height axis of a grid mesh. This offset is performed in the vertex shader by sampling the elevation tile as a texture and applying a corresponding shift to the vertical axis of each vertex.

ElevationTile requirements

  • Origin (0, 0) is in northwest corner (top-left) of the tile.
  • Elevation values are provided in meters.
  • Uses EPSG:3857 projection with individual tile data covering the extents as described here.
  • (Recommended) Pixels along the tile edge should share the same values as corresponding pixels in neighboring tile edges. This will reduce seams between tiles.

Constructing ElevationTiles

ElevationTiles can be created in one of two ways.

  • ElevationTile.FromDataInMeters: Provide elevation values in meters as 32-bit floats.
  • ElevationTile.FromNormalizedData: Provide elevation values which have been normalized into 16-bit ushorts. A min and max elevation for the tile must also be provided.

Implementing an ElevationTileLayer

While the DefaultElevationTileLayer provides global elevation coverage with varying amounts of detail, there may be areas where higher detail data is available from a different source like LIDAR scans, drone capture, etc.

A custom ElevationTileLayer implementation can be used to provide this sort of data to the map, and it can be served from a variety of sources:

  • Remote servers
  • Local disk
  • Textures generated procedurally

Exactly how the data is requested and processed into ElevationTiles is up to the implementation, which must provide HasElevationTile and GetElevationTile methods.

public class FooElevationTileLayer : ElevationTileLayer
{
    public override async Task<bool> HasElevationTile(TileId tileId, CancellationToken cancellationToken = default)
    {
        // This method will be called first, before GetElevationTile, to determine if the specified TileId is present.

        // If working with many tiles, it is recommended to create a lookup table in order to quickly determine which
        // tiles are present. If no lookup data structure is available, the ElevationTile data could be requested here
        // instead, and put in a pool that can be consumed when GetElevationTile is called. Regardless of the approach,
        // the map will cache the result of this method, so HasElevationTile is called just once per tile in a layer.
    }

    public override async Task<ElevationTile> GetElevationTile(TileId tileId, CancellationToken cancellationToken = default)
    {
        // Request and decode elevation data asynchronously...

        // Possible options to construct an ElevationTile...
        return ElevationTile.FromDataInMeters(...);
        return ElevationTile.FromNormalizedData(...);
    }
}

A note on performance

  • HasElevationTile and GetElevationTile are called asynchronously-- not on the Unity thread. Web requests or file IO can be performed in these methods without directly impacting framerate. If it is required to interop with Unity APIs, the UnityTaskFactory can be used to run tasks on Unity's main thread. Avoid long running work on the main thread.
  • Serving large elevation data sets with thousands of tiles can be resource intensive both for network/file IO and CPU processing. Host or store the elevation data in a format that is easily decodable to the 16-bit normalized input as this format has a smaller memory footprint than the unnormalized 32-bit float alternative. In general, aim to minimize size of data and decrease time needed to transcode the data on the client to a format usable by the ElevationTile.

Using multiple ElevationTileLayers

Like TextureTileLayers, ElevationTileLayers are presented as a reorderable list in the MapRenderer's editor UI and are stacked in priority order.

Layers are prioritized in this UI from the bottom up. If no data is available from the Custom Elevation Tile Layer, the map will fall back to rendering the next highest priority layer, e.g. the Default Elevation Tile Layer.

Unlike TextureTileLayers, there is no concept of blending between different layers. Using the above image as an example, if any elevation data for a given tile is present from the higher priority custom layer, it will be used regardless of what data is available in the default layer, even if this default layer contains tile data at higher levels of detail, e.g. the custom layer may only cover a region up to tile level of detail (LOD) 16, but even when the default layer provides data in that region up to LOD 18, it will not be used since the custom layer has a higher priority in the list.

Generally speaking, a custom elevation layer need not provide global data. The DefaultElevationTileLayer can be used to provide a base level of elevation detail, and then a custom layer can be used to bring in higher resolution data for specific regions.


Clone this wiki locally