Click or drag to resize

Terrain

Terrain data can be used within DME Component Libraries to:

  • Find the height of the terrain at a particular longitude and latitude.

  • Take terrain obscuration into account when computing Access between two objects.

  • Compute an Azimuth-Elevation Mask, which describes the minimum elevation angle that is not obscured by terrain for each azimuth angle surrounding a stationary object. The mask can then be used to more quickly compute terrain-obscured Access when one of the objects involved in the computation is stationary.

Note Note

The functionality described in this topic requires a license for the Terrain Analysis Library.

Reading Terrain Data

DME Component Libraries provides classes to read terrain data from the following sources:

Class

Terrain Format

GeospatialContentServerTerrain

Geospatial Content Server (GCS) is a commercial AGI product which provides a comprehensive enterprise solution for hosting, processing, serving, and analyzing 3D data, including terrain data.

StkTerrainServer

STK Terrain Server is a commercial AGI product which processes and serves terrain data from varied sources, producing a global unified data set in quantized-mesh format. AGI hosts a server for customer use which can be accessed over the internet. Alternatively, you can connect to your own STK Terrain Server if you are in an offline or disconnected environment, or to incorporate your own terrain data into a best-available data set for your needs.

UsgsDigitalElevationModel

The Digital Elevation Model (DEM) format from the United States Geological Survey. Note that this is not the same as the SDTS DEM format.

NgaDigitalTerrainElevationData

NgaDigitalTerrainElevationDataCell

The Digital Terrain Elevation Data (DTED) format from the National Geospatial Intelligence Agency.

GTOPO30Terrain

The United States Geological Survey digital elevation model for the Earth at 30 arc second resolution.

EarthGravityModel96MeanSeaLevel

The EGM96 equipotential model using data from the worldwide 15 minute binary geoid height data file, WW15MGH.DAC.

AGIWorldTerrain

DEM-derived data for the entire Earth.

GeodasGriddedTerrain

GEODAS (GEOphysical DAta System) is an interactive database management system developed by the National Geophysical Data Center (NGDC) for use in the assimilation, storage and retrieval of geophysical data.

AGIProcessedDataTerrain

AGI Processed Data Terrain (PDTT) is a custom terrain format designed for high-performance 3D visualization within AGI tools. It can also be used for analysis within DME Component Libraries.

In addition, DME Component Libraries includes a sophisticated caching mechanism for terrain data. When a height is requested from a terrain provider, and it is not already in the cache, the information needed to provide the height is immediately read from the terrain file. In addition, a request is queued to the thread associated with the TerrainCacheGroup to load an entire chunk of terrain near the location requested. The load of the region then takes place in the background. When the load is complete, future calls to getHeight calls will complete much more quickly because they will not need to read from disk. This all happens in the background and is transparent to the user of the library. However, the user can configure the maximum size of the cache. When the cache is full, the least recently used region will be unloaded.

Mean Sea Level (MSL)

Some features of DME Component Libraries work with heights defined relative to a reference surface, often mean sea level. For example, some terrain providers are defined relative to MSL, routes in the Route Design Library can be defined relative to MSL, and the USStandardAtmosphere1976 model is defined with respect to MSL.

The MSL surface used in your application is configured by constructing an instance of EarthGravityModel96MeanSeaLevel, then storing that instance on EarthCentralBody.MeanSeaLevel (get / set), as seen in the example below:

Java
EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth();
EarthGravityModel96MeanSeaLevel msl = new EarthGravityModel96MeanSeaLevel();
earth.setMeanSeaLevel(msl);

The data which defines MSL comes from the Earth Gravity Model 1996 (EGM96) worldwide 15 minute binary geoid height data file, WW15MGH.DAC, obtained from NGA. This data is embedded within DME Component Libraries, so no external files are required.

Loading Terrain

Before a DTED or DEM terrain data file can be loaded, the MeanSeaLevel (get / set) (MSL) surface must be configured, as described above. This is because these two terrain formats generally contain heights relative to MSL.

Once an MSL surface has been configured, it will be used when the actual terrain data is loaded. The following example shows how to load terrain data from a DEM file:

Java
String demPath = new File(terrainDirectory, "Ft_Drum.dem").getPath();
UsgsDigitalElevationModel dem = new UsgsDigitalElevationModel(demPath);

The following example shows how to load terrain data from a DTED file by specifying the location of the "dmed" file that is part of a DTED terrain data set:

Java
String dmedPath = new File(terrainDirectory, "SouthWestUS_DTED_Level_One/dmed").getPath();
NgaDigitalTerrainElevationData dted = new NgaDigitalTerrainElevationData(dmedPath);
Obtaining Terrain Heights

Once a source of terrain data has been loaded, it can be queried for the height of the terrain at any location:

Java
double height1 = dem.getHeight(-1.32, 0.77);
// This height might be relative to MSL, WGS84, or anything else depending
// on the content of the terrain file.

Note that the location is specified in radians and that longitude is specified first. If terrain data is not available at the specified location, a void height (Double.NaN) will be returned. Otherwise, it returns the height of the terrain relative to its HeightReference. If the HeightReference (get / set) property is HeightReference.SHAPE, the height is relative to the ellipsoidal shape specified by the Shape (get / set) property. For example, the terrain might be relative to the WGS84 ellipsoid. If the HeightReference (get / set) property is HeightReference.REFERENCE_SURFACE, the height is relative to a reference surface specified by the ReferenceSurface (get / set) property. For example, the terrain might be relative to a mean sea level surface.

You can avoid a lot of this complexity by calling getHeightRelativeToShape instead of getHeight. This method will automatically take the reference surface, if any, into account and return the height relative to the ellipsoid surface.

Java
double height2 = dem.getHeightRelativeToShape(-1.32, 0.77);
// This height is always relative to the ellipsoidal shape specified by the
// Shape property.

Raster terrain data sources such as DEM and DTED are composed of heights arranged on a regular grid. The height of a point between grid points can be obtained by interpolating between the "posts" on the grid. The interpolation algorithm can be changed by setting the DefaultInterpolator (get / set) property. The default interpolation algorithm is BilinearTerrainInterpolator. Other algorithms that can be used include HighestPostTerrainInterpolator and NearestNeighborTerrainInterpolator. It is also possible to write a custom interpolation algorithm.

Computing Terrain-Obscured Access

DME Component Libraries allows you to take terrain into account when determining when one object has access to another. See the Access topic for more information about computing access.

To take terrain obscuration into account when determining inter-visibility, add a TerrainLineOfSightConstraint and set the ConstrainedLink (get / set) and ConstrainedLinkEnd (get / set) properties.

Java
TerrainLineOfSightConstraint constraint = new TerrainLineOfSightConstraint(dem);
constraint.setConstrainedLink(new LinkInstantaneous(getTransmitter(), getReceiver()));
constraint.setConstrainedLinkEnd(LinkRole.TRANSMITTER);

When terrain data is not available for a region between the two objects, the TerrainLineOfSightConstraint constraint assumes that the view is unobscured. This means that if you have two objects on opposite sides of the Earth, and there is no terrain data between them, then this constraint alone will claim that they can see each other. Often, you will want to use a mean sea level surface with this constraint anywhere that more detailed terrain data does not exist. To do that, create a CompositeTerrainProvider and configure the constraint to use that:

Java
CompositeTerrainProvider composite = new CompositeTerrainProvider();
composite.add(dem);
composite.add(CentralBodiesFacet.getFromContext().getEarth().getMeanSeaLevel());

TerrainLineOfSightConstraint constraint = new TerrainLineOfSightConstraint(composite);
constraint.setConstrainedLink(new LinkInstantaneous(getTransmitter(), getReceiver()));
constraint.setConstrainedLinkEnd(LinkRole.TRANSMITTER);

A CompositeTerrainProvider maintains an ordered list of terrain providers. When a height is requested of it, it requests the height from each provider, in turn, until it finds one that returns a non-void height.

Generating an Azimuth-Elevation Mask

If one of the objects involved in the access computation is stationary, better performance can usually be obtained by generating an Azimuth-Elevation Mask for the stationary object and using that to constrain access. An Azimuth-Elevation Mask describes how the horizon looks to a stationary object. It holds the maximum obscured elevation angle in each sampled direction from the stationary object. It can also keep track of the minimum elevation angle as a function of distance. For example, if the stationary object is in a canyon, objects also inside the canyon are visible at a very low elevation angle. However, objects past the canyon walls are only visible if they are at a much steeper elevation angle such that they are above the walls.

An Azimuth-Elevation Mask can be computed from any terrain provider using TerrainAzimuthElevationMask. There are two ways to do this. The mask can either be computed in its entirety using a multithreaded calculation by calling compute, or it can be created using createDelayedMask, which will delay calculation of the elements in the mask until they are requested. Unless performing visualization or some other analysis which is guaranteed to require the entire 360 degree mask, the delayed mask will usually be more efficient.

Java
NgaDigitalTerrainElevationData terrain = new NgaDigitalTerrainElevationData(terrainPath);

CompositeTerrainProvider composite = new CompositeTerrainProvider();
composite.add(terrain);
composite.add(CentralBodiesFacet.getFromContext().getEarth().getMeanSeaLevel());

double longitude = Trig.degreesToRadians(-118.38);
double latitude = Trig.degreesToRadians(42.6419);
double height = composite.getHeight(longitude, latitude);
Cartographic position = new Cartographic(longitude, latitude, height);

// Sample using 360 azimuth rays.
int numberOfAzimuthSteps = 360;

// Sample approximately three times per terrain grid cell.
double stepSize = terrain.getMinimumSampleSpacing() * 0.33;

// A max search angle of 0.025 radians is reasonable for realistic Earth terrains.
// The risk of missing a very tall and far away terrain feature can be reduced
// by increasing this number, at the cost of increased time to compute the mask.
double maxSearchAngle = 0.025;

// The following is useful when using an AzElMask directly when the entire horizon is needed.
// The calculation of the mask is threaded to take advantage of multi-core hardware.
AzimuthElevationMask mask = 
        TerrainAzimuthElevationMask.compute(composite, position, numberOfAzimuthSteps, stepSize, maxSearchAngle);

// The following is more efficient when computing geometry where only a small swath of the
// horizon will be analyzed during computation and will avoid computing swaths where
// no analysis is needed.
DelayedTerrainAzimuthElevationMask delayedMask =
        TerrainAzimuthElevationMask.createDelayedMask(composite, position, numberOfAzimuthSteps, stepSize, maxSearchAngle);

A CompositeTerrainProvider is used in the example above for the same reason that it is generally used with TerrainLineOfSightConstraint. That is, a lack of terrain data implies an unobscured view when computing the mask.

Once the mask has been computed, it can be attached to a Platform:

Java
AzimuthElevationMaskExtension extension = new AzimuthElevationMaskExtension(azElMask);
Platform platform = new Platform();
platform.getExtensions().add(extension);

Attaching it to a platform using the AzimuthElevationMaskExtension allows it to be discovered by the AzimuthElevationMaskConstraint. Apply the AzimuthElevationMaskConstraint to the same platform to which you attach the extension:

Java
AzimuthElevationMaskExtension extension = new AzimuthElevationMaskExtension(azElMask);
Platform platform = new Platform();
platform.getExtensions().add(extension);

AzimuthElevationMaskConstraint constraint = new AzimuthElevationMaskConstraint();
constraint.setConstrainedLink(new LinkInstantaneous(platform, getReceiver()));
constraint.setConstrainedLinkEnd(LinkRole.TRANSMITTER);

Computing access in this way is much faster than using TerrainLineOfSightConstraint, but it is only applicable when one of the objects involved in the computation is stationary.

Pre-loading Terrain into the Cache

In certain cases, it is useful to pre-load terrain data into the terrain cache and ensure that it is not automatically removed. This can be accomplished by calling the preloadExtent method on any TerrainProvider:

Java
dem.preloadExtent(dem.getBoundingExtent());

This method loads into the cache all terrain data necessary to satisfy getHeight calls within the specified extent. It ignores the MaximumSize (get / set) property; it fails only if it runs out of memory. In addition, terrain data loaded with preloadExtent will never be automatically ejected from the cache. To unload pre-loaded terrain data, call unloadExtent:

Java
dem.unloadExtent(dem.getBoundingExtent());

Both preloadExtent and unloadExtent do not return until the terrain data for the specified extent is completely loaded or unloaded, respectively. It is safe, however, to call these methods from any thread, so they can be executed in a background thread if necessary.