Elevation data

From Maria GDK Wiki
Revision as of 09:59, 7 November 2024 by Ths (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Elevation data and other single channel data can be used for various analysis and visualization purposes throughout the Maria system. This page shows an overview of the elevation data programming API's

The two main interfaces for elevation data are IElevationData (implemented by the NativeElevationData class) and IElevationDataManager (implemented by ElevationDataManager).

IElevationData

This interface contains methods for querying elevation data from a set of data layers.

Data layers

An elevation data set consists of one or more elevation data map entries stored on a Maria raster data service. The set of available map entries can be queried from the catalog service and any MapEntry with MapContentType = ElevationData can be used.

The elevation data layers are usually managed by the ElevationDataManager, but you can also specify them explicitly through a MapTemplate with the SetTemplate method

        /// <summary>
        /// Setup elevation data layers from the given template.
        /// This method will clear the current elevation data set.
        /// </summary>
        void SetTemplate(MapTemplate template);

        /// <summary>
        /// Get the currently active elevation data template.
        /// </summary>
        /// <returns></returns>
        MapTemplate GetTemplate();

The format of this elevation data template is described here: Elevation layers in templates

Elevation queries

The main purpose of the IElevationData interface is to calculate elevation values in a certain geographical point or area. The following methods are supported for this:

        /// <summary>
        /// Generalized API for elevation queries.
        /// </summary>
        /// <param name="lat">Latitude of the query point</param>
        /// <param name="lon">Longitude of the query point</param>
        /// <param name="queryParams">Parameters for the elevation query.</param>
        /// <returns>An ElevationResult containing the elevation and resolution of the source data</returns>
        ElevationResult CalcElevation(double lat, double lon, ElevationQueryParams queryParams);

        /// <summary>
        /// Calculate multiple elevations for a given point.
        /// </summary>
        /// <param name="lat">Latitude of the query point</param>
        /// <param name="lon">Longitude of the query point</param>
        /// <param name="queryParams">Parameters for the elevation query.</param>
        /// <returns>
        /// A list of ElevationResults containing the elevation and resolution of each layer in the source data. 
        /// To get further info on each of the layers, you can look up the matching ElevationDataSource entry in 
        /// IElevationDataManager.
        /// </returns>
        List<ElevationResult> CalcElevations(double lat, double lon, ElevationQueryParams queryParams);

        /// <summary>
        /// Synchronously calculate an elevation map covering the given raster projector.
        /// </summary>
        /// <param name="rp">Raster projector defining the area and projection of the elevation map.</param>
        /// <param name="queryParams">Parameters for the elevation query.</param>
        /// <returns>A bitmap of floating point elevation samples.</returns>
        BitmapFloatData CalcElevationMap(IRasterProjector rp, ElevationQueryParams queryParams);

        /// <summary>
        /// Calculate a normal map covering the given raster projector. The normal vectors are encoded as 
        /// XYZ -> RGB values. The alpha channel is not used.
        /// </summary>
        /// <param name="rp">Raster projector defining the area and projection of the elevation map.</param>
        /// <param name="interpolationMethod">Method of resampling/interpolation. Nearest is usually sufficient here.</param>
        /// <param name="timeoutMs">Timeout in ms. If 0, block indefinitely.</param>
        /// <returns></returns>
        Bitmap32Data CalcNormalMap(IRasterProjector rp, InterpolationMethod interpolationMethod = InterpolationMethod.Nearest, int timeoutMs = 0);

As we can see all these methods require an ElevationQueryParams object as input. This query object contains all the parameters for the elevation query, such as what data elevation data types to look for, what resolution we require, interpolation method etc. The elevation data query is actually a specialization of the more general raster data query:

    public class RasterQueryParams
    {
        /// <summary>
        /// Method of resampling/interpolation (Nearest or Bilinear).
        /// </summary>
        public InterpolationMethod interpolationMethod = InterpolationMethod.Bilinear;

        /// <summary>
        /// Only include data from data sets with any of the given tags
        /// </summary>
        public string Tags { get; set; }

        /// <summary>
        /// Minimum resolution (in m/pixel) of the data we want to base the calculation on.
        /// </summary>
        public double resolution = 0.0;

        /// <summary>
        /// Timeout in ms when waiting for data. If 0, wait indefinitely.
        /// </summary>
        public int timeoutMs = 0;
    };

    /// <summary>
    /// Parameter class for elevation data queries.
    /// </summary>
    public class ElevationQueryParams : RasterQueryParams
    {
        /// <summary>
        /// Query data type, only return samples of this data type. Ignored if set to Undefined.
        /// </summary>
        public ElevationDataType dataType = ElevationDataType.Undefined;
    };

The single point elevation data queries return an ElevationResult which looks like this:

    public class ElevationResult
    {
        public enum StatusCode
        {
            Undefined = 0,
            /// <summary>
            /// The query finished successfully
            /// </summary>
            OK,
            /// <summary>
            /// No data could be found at the given position.
            /// </summary>
            Missing,
            /// <summary>
            /// The query took longer than the given timeout values
            /// </summary>
            Timeout,
            /// <summary>
            /// Some unspecified error caused the query to fail.
            /// </summary>
            Error
        };

        /// <summary>
        /// The result status of the query. 
        /// </summary>
        public StatusCode Status { get; set; }

        /// <summary>
        /// The actual elevation value, if status == OK
        /// </summary>
        public double Value { get; set; }

        /// <summary>
        /// Estimated data resolution (in m/pixel) of the dataset from which the result was taken.
        /// </summary>
        public double Resolution { get; set; }

        /// <summary>
        /// Identifier for the elevation data layer that was used to produce this sample.
        /// This value can be used to look up further info about the data source by resolving
        /// it in ElevationDataManager.DataSources
        /// </summary>
        public int LayerId { get; set; }

        /// <summary>
        /// Type of elevation data (TerrainModel, Bathymetry, etc).
        /// </summary>
        public ElevationDataType DataType { get; set; }
    }

As we can see the result contains information on the sample we found in the given input position. In order to get more information on the underlying data layer, you need an ElevationDataManager handling the layers.

Intersection queries

IElevationData also contains methods to calculate the intersection between a ray and the ground surface. The ray can be defined either by two 3D points in space, or as a single point and direction angles vertically and horizontally.

        /// <summary>
        /// Calculate intersection with the terrain from a given observer point and direction.
        /// This method searches forwards along a ray along the direction of observation until it hits the ground. 
        /// The method will then refine the intersection point until within the hTolerance horizontal distance.
        /// The step parameter indicates the distance of each initial search step. Longer steps means faster convergence,
        /// but you may also miss terrain features that are smaller than the search step distance.
        /// </summary>
        /// <param name="pos">Geographical position of observer</param>
        /// <param name="altitude">Altitude of observer (ASL)</param>
        /// <param name="azimuth">Heading of observer (compass direction in degrees).</param>
        /// <param name="elevation">Vertical tilt of observer. Negative angles point downwards.</param>
        /// <param name="step">Search step distance.</param>
        /// <param name="interpolationMethod">Method of resampling/interpolation</param>
        /// <param name="hTolerance">horizontal error tolerance for the intersection point (meters).</param>
        /// <param name="vTolerance">Minimum resolution of the terrain data to use for the calculations. 0 means use best available.</param>
        /// <param name="timeout_ms">Timeout for elevation calculations (in ms).</param>
        /// <returns>3D point of intersection between the view direction ray and the terrain.</returns>
	    GeoPos3D CalcSurfaceIntersection(GeoPos pos, double altitude,
	        double azimuth, double elevation,
	        double step = 50, InterpolationMethod interpolationMethod = InterpolationMethod.Bilinear,
	        double hTolerance = 2.5, double vTolerance = 0.0, int timeout_ms = 0);

        /// <summary>
        /// Same as above, but using two points in 3D space to calculate the intersection ray.
        /// </summary>
        /// <param name="pos0">Start point of ray</param>
        /// <param name="pos1">End point of ray</param>
        /// <param name="step">Search step distance.</param>
        /// <param name="interpolationMethod">Method of resampling/interpolation</param>
        /// <param name="hTolerance">horizontal error tolerance for the intersection point (meters).</param>
        /// <param name="vTolerance">Minimum resolution of the terrain data to use for the calculations. 0 means use best available.</param>
        /// <param name="timeout_ms">Timeout for elevation calculations (in ms).</param>
        /// <returns>3D point of intersection between the view direction ray and the terrain.</returns>
        GeoPos3D CalcSurfaceIntersection(GeoPos3D pos0, GeoPos3D pos1,
            double step = 50, InterpolationMethod interpolationMethod = InterpolationMethod.Bilinear,
            double hTolerance = 2.5, double vTolerance = 0.0, int timeout_ms = 0);

Note on async methods

The IElevationData interface currently contains a set of methods with Async in their names which return Task objects. We don't really recommend using these, and they may be obsoleted in a future version. Instead, wrap any time consuming elevation methods in your own async caller method to have better control over these operations.

IElevationDataManager

The ElevationDataManager uses the catalog service to find all available elevation data sets and provides an interface for sorting and filtering these layers according to certain critieria, and building an ElevationData template which is used by the IElevationData interface as described above.

ElevationDataClient

The ElevationDataClient class is a utility class for setting up an ElevationDataManager with corresponding tile cache and service clients, and can be found in the TPG.GeoFramework.MapLayer assembly.

It can either be set up completely standalone where it creates its own tile cache and service clients (requires a MapCatalogService config in the app.config), or with an existing catalog service client. In these cases you need to dispose the ElevationDataClient after use, in order to clean up the internal tile cache and service client structures.

Alternatively you can also set up the ElevationClient with an existing NativeTileCacheManager. This tile cache must be set up with the correct MapDataProviders for reading elevation data tiles. In this case, no disposal is necessary as long as you dispose the NativeTileCacheManager somewhere else.

Example:

var elevationDataClient = new ElevationDataClient();
var elevationDataManager = elevationDataClient.ElevationDataManager;
if(elevationDataManager != null)
{
    var elevationDataQuery = new ElevationDataSortByResolution();

    // Sort elevation layers on resolution and generate a template 
    elevationDataManager.UpdateDataSources();
    elevationDataManager.GenerateLayers(elevationDataQuery);
    var elevationData = elevationDataManager.ElevationData;
    var elevResult = elevationData.CalcElevationEx(60.0, 10.0, InterpolationMethod.Bilinear, 0.0);
    if (elevResult.Status == ElevationResult.StatusCode.OK)
    {
        ElevationDataSource elevSource = null;
        elevationDataManager.DataSources.TryGetValue(elevResult.LayerId, out elevSource);
        var mapSignature = elevSource != null ? elevSource.MapEntry.MapSignature : "(Unknown)";
        Console.WriteLine(@"Elevation = {0}, resolution = {1}, map signature = {2}", elevResult.Value, elevResult.Resolution, mapSignature);
    }
    else
        Console.WriteLine(@"Elevation calculation failed, status = {0}", elevResult.Status);

    // Clean up
    elevationDataClient.Dispose();
}


Exports to DTED

DTED, although dating from the mid 90'ties, is still a commonly applied interchange format for digital elevation data. This section concerns exports to this format from MariaGDK. The data source being the client elevation data, that is, the data sets currently available on the GDK client, subject to the same selection of templates, sorting and filtering mechanisms as described above. The connection being established by letting the export utility hook onto the IElevationData interface by construction:

public DtedExport(IElevationData elevationData

Properties

public Progress { get; }

Optionally used to get current progress value upon notification of progress change - or otherwise.

Methods

public bool ExportElevations(string dtedRoot, GeoRect gr, uint levelMask, DtedFillOptions dfo, DtedProductInfo dpi = null);

ExportElevations initiates an export process, using the supplied parameter dtedRoot as target root directory. The DTED format will not be discussed here in any length. Suffice it to say that output typically is a set of folders, each associated to a particular precision level (among the possible DTED levels) containing files each covering an area measuring 1 arc degree in both latitude and longitude - aligned to integer values of these coordinates. Supported levels are DTED0 - DTED5, the last three being the 'unofficial' ones. Parameters are as follows:

Name Type Description
dtedRoot string Output directory.
gr GeoRect Definition of the source area. Output may be extended to meet the DTED 1 degree cell size requirement. See also DtedFillOptions below
levelMask uint Bitwise selection of requested DTED levels. (0x1 : DTED0 only, 0x2 : DTED1 only, 0x3 : DTED0 and DTED1, 0x4 : DTED2 only - and so on)
dfo DtedFillOptions Optionally allow alternative fills in output cells.
dpi DtedProductInfo Optionally pass on metadata)
public void Cancel();

Cancels an ongoing export process.

DtedFillOptions

Alternative fill options may be handy when the source area is small (like just a fraction of a DTED cell) and the precision requirement (DTED level) is high.

Value Description
AllComplete Full cells will be made, even if the source area is considerable smaller than a single cell.
AllowPartial Allow partially filled cells. Originally, and according to the DTED standard, this option is available for the tape medium only, whereas on CD-ROM cells were supposed to be filled. However, this standard did not consider levels beyond 2.
UseNullFills Apply invalid values outside requested source area.

DtedProductInfo

This is a set of metadata optionally passed on along with the export. Properties are as follows:

Name Type Description
UhlReference string Unique reference tag, defined by producer (truncated after 12 letters or digits). Default is "0".
UhlSecurity char Security tag: S - Secret, C - Confidential, U - Unclassified, R - Restricted. Default is 'U'.
UhlAccuracy uint Vertical accuracy in meters within 90% confidentiality. Default is 1.
DsiSecurity char Data set security tag: S - Secret, C - Confidential, U - Unclassified, R - Restricted. Default is 'U'.
DsiReferenceNumber uint Unique reference number (max 15 digits). Default is 0.
DsiEditionNUmber uint Edition number 00-99. Default is 1.
DsiMerge char Merge version (A-Z) Default is 'A'.
DsiProducer string Producer id. Default is "MariaGDK".
DsiProdSpecAmend uint Product Specification Amendment Number (00-99). Default is 0.
DsiProdDate DateTime Production date. Default is UtcNow
DsiCompDate DateTime Compilation date. Default is UTcNow.

Events

The following events are available to monitor the export process. Handlers should be aware they all run on the export thread.

Name Description
ProgressChanged Raised on significant progress change - percentage of estimated workload. Get the Progress property for actual value.
ExportComplete Raised on completion.
ExportFailed Raised on failure or cancellation.

Code Example

using TPG.GeoFRamework.Map.Core;
//...

var gr = new GeoRect(new GeoPos(61, 7), new GeoPos(62, 8));  //Some region of interest
uint mask = 0x3;  //DTed0 & dted1
var de = new DtedExport(_elevationData);

de.ProgressChanged += DtedExportProgressChanged;
de.ExportComplete += DtedExportComplete;
de.ExportFailed += DtedExportFailed;

de.ExportElevations(@"c:\my_dted_root", gr, mask, DtedFillOptions.AllComplete, null);