Terrain data can be used within STK Components 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.
The functionality described in this topic requires a license for the Terrain Analysis Library.
STK Components provides classes to read terrain data from the following sources:
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.
The Digital Elevation Model (DEM) format from the United States Geological Survey. Note that this is not the same as the SDTS DEM format.
The Digital Terrain Elevation Data (DTED) format from the National Geospatial Intelligence Agency.
The United States Geological Survey digital elevation model for the Earth at 30 arc second resolution.
The EGM96 equipotential model as read from WW15MGH.DAC, the worldwide 15 minute binary geoid height data file.
DEM-derived data for the entire Earth.
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.
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 STK Components.
In addition, STK Components 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.
Before a DTED or DEM terrain data file can be loaded, the EarthCentralBody in the calculation context must be configured with a MeanSeaLevel (get / set) (MSL) surface. This is because these two terrain formats generally contain heights relative to MSL.
To load the EGM96 mean sea level surface, first obtain the freely-available WW15MGH.DAC data file from NGA. It can be downloaded from: http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/binary/binarygeoid.html.
// The EGM96 worldwide 15 minute binary geoid height data file, WW15MGH.DAC, can // be obtained from http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/binary/binarygeoid.html EarthCentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); EarthGravityModel96MeanSeaLevel msl = new EarthGravityModel96MeanSeaLevel(new File(dataPath, "WW15MGH.DAC").getPath()); earth.setMeanSeaLevel(msl);
Once an MSL surface has been specified, it will be used when the actual terrain data is loaded. The following example shows how to load terrain data from a DEM file:
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:
String dmedPath = new File(terrainDirectory, "SouthWestUS_DTED_Level_One/dmed").getPath(); NgaDigitalTerrainElevationData dted = new NgaDigitalTerrainElevationData(dmedPath);
Once a source of terrain data has been loaded, it can be queried for the height of the terrain at any location:
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.
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.
STK Components 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.
TerrainLineOfSightConstraint constraint = new TerrainLineOfSightConstraint(dem); constraint.setConstrainedLink(new LinkInstantaneous(getAnyOldTransmitter(), getAnyOldReceiver())); 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:
CompositeTerrainProvider composite = new CompositeTerrainProvider(); composite.add(dem); composite.add(CentralBodiesFacet.getFromContext().getEarth().getMeanSeaLevel()); TerrainLineOfSightConstraint constraint = new TerrainLineOfSightConstraint(composite); constraint.setConstrainedLink(new LinkInstantaneous(getAnyOldTransmitter(), getAnyOldReceiver())); 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.
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.
String terrainPath = new File(terrainDirectory, "SouthWestUS_DTED_Level_One/dmed").getPath(); 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 stationaryObjectLocation = new Cartographic(longitude, latitude, height); // Sample using 360 azimuth rays. int numberOfAzimuthSteps = 360; // Sample around three times per terrain grid cell. double res = Trig.degreesToRadians(3.0 / 3600); double stepSize = res * 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 azElMask = TerrainAzimuthElevationMask.compute(composite, stationaryObjectLocation, 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 efficientAzElMask = TerrainAzimuthElevationMask.createDelayedMask(composite, stationaryObjectLocation, 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:
AzimuthElevationMaskExtension extension = new AzimuthElevationMaskExtension(azElMask); Platform platform = getAnyOldPlatform(); 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:
AzimuthElevationMaskExtension extension = new AzimuthElevationMaskExtension(azElMask); Platform platform = getAnyOldPlatform(); platform.getExtensions().add(extension); AzimuthElevationMaskConstraint constraint = new AzimuthElevationMaskConstraint(); constraint.setConstrainedLink(new LinkInstantaneous(platform, getAnyOldReceiver())); 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.
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:
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:
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.