/*
Copyright (C) 2001, 2006 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.globes;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.cache.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.retrieve.*;
import gov.nasa.worldwind.util.*;
import java.io.*;
import java.net.*;
import java.nio.*;
import java.util.*;
// Implementation notes, not for API doc:
//
// Implements an elevation model based on a quad tree of elevation tiles. Meant to be subclassed by very specific
// classes, e.g. Earth/SRTM. A Descriptor passed in at construction gives the configuration parameters. Eventually
// Descriptor will be replaced by an XML configuration document.
//
// A "tile" corresponds to one tile of the data set, which has a corresponding unique row/column address in the data
// set. An inner class implements Tile. An inner class also implements TileKey, which is used to address the
// corresponding Tile in the memory cache.
// Clients of this class get elevations from it by first getting an Elevations object for a specific Sector, then
// querying that object for the elevation at individual lat/lon positions. The Elevations object captures information
// that is used to compute elevations. See in-line comments for a description.
//
// When an elevation tile is needed but is not in memory, a task is threaded off to find it. If it's in the file cache
// then it's loaded by the task into the memory cache. If it's not in the file cache then a retrieval is initiated by
// the task. The disk is never accessed during a call to getElevations(sector, resolution) because that method is
// likely being called when a frame is being rendered. The details of all this are in-line below.
/**
* This class represents a single tile in the data set and contains the information that needs to be cached.
*
* @author Tom Gaskins
* @version $Id: BasicElevationModel.java 5164 2008-04-24 20:57:37Z dcollins $
*/
public class BasicElevationModel extends WWObjectImpl implements ElevationModel
{
private boolean isEnabled = true;
private final LevelSet levels;
private final double minElevation;
private final double maxElevation;
private long numExpectedValues = 0;
private final Object fileLock = new Object();
private java.util.concurrent.ConcurrentHashMap<TileKey, Tile> levelZeroTiles =
new java.util.concurrent.ConcurrentHashMap<TileKey, Tile>();
private MemoryCache memoryCache = new BasicMemoryCache(4000000, 5000000);
private int extremesLevel = -1;
private ShortBuffer extremes = null;
private static final class Tile extends gov.nasa.worldwind.util.Tile implements Cacheable
{
private java.nio.ShortBuffer elevations; // the elevations themselves
private Tile(Sector sector, Level level, int row, int col)
{
super(sector, level, row, col);
}
}
/**
* @param levels
* @param minElevation
* @param maxElevation
* @throws IllegalArgumentException if <code>levels</code> is null or invalid
*/
public BasicElevationModel(LevelSet levels, double minElevation, double maxElevation)
{
if (levels == null)
{
String message = Logging.getMessage("nullValue.LevelSetIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
String cacheName = Tile.class.getName();
if (WorldWind.getMemoryCacheSet().containsCache(cacheName))
{
this.memoryCache = WorldWind.getMemoryCache(cacheName);
}
else
{
long size = Configuration.getLongValue(AVKey.ELEVATION_TILE_CACHE_SIZE, 5000000L);
this.memoryCache = new BasicMemoryCache((long) (0.85 * size), size);
this.memoryCache.setName("Elevation Tiles");
WorldWind.getMemoryCacheSet().addCache(cacheName, this.memoryCache);
}
this.levels = new LevelSet(levels); // the caller's levelSet may change internally, so we copy it.
this.minElevation = minElevation;
this.maxElevation = maxElevation;
}
public boolean isEnabled()
{
return this.isEnabled;
}
public void setEnabled(boolean enabled)
{
this.isEnabled = enabled;
}
public LevelSet getLevels()
{
return this.levels;
}
public final double getMaxElevation()
{
return this.maxElevation;
}
public final double getMinElevation()
{
return this.minElevation;
}
public long getNumExpectedValuesPerTile()
{
return numExpectedValues;
}
public void setNumExpectedValuesPerTile(long numExpectedValues)
{
this.numExpectedValues = numExpectedValues;
}
// Create the tile corresponding to a specified key.
private Tile createTile(TileKey key)
{
Level level = this.levels.getLevel(key.getLevelNumber());
// Compute the tile's SW lat/lon based on its row/col in the level's data set.
Angle dLat = level.getTileDelta().getLatitude();
Angle dLon = level.getTileDelta().getLongitude();
Angle minLatitude = Tile.computeRowLatitude(key.getRow(), dLat);
Angle minLongitude = Tile.computeColumnLongitude(key.getColumn(), dLon);
Sector tileSector = new Sector(minLatitude, minLatitude.add(dLat), minLongitude, minLongitude.add(dLon));
return new Tile(tileSector, level, key.getRow(), key.getColumn());
}
// Thread off a task to determine whether the file is local or remote and then retrieve it either from the file
// cache or a remote server.
private void requestTile(TileKey key)
{
if (WorldWind.getTaskService().isFull())
return;
RequestTask request = new RequestTask(key, this);
WorldWind.getTaskService().addTask(request);
}
private static class RequestTask implements Runnable
{
private final BasicElevationModel elevationModel;
private final TileKey tileKey;
private RequestTask(TileKey tileKey, BasicElevationModel elevationModel)
{
this.elevationModel = elevationModel;
this.tileKey = tileKey;
}
public final void run()
{
// check to ensure load is still needed
if (this.elevationModel.areElevationsInMemory(this.tileKey))
return;
Tile tile = this.elevationModel.createTile(this.tileKey);
final java.net.URL url = WorldWind.getDataFileCache().findFile(tile.getPath(), false);
if (url != null)
{
if (this.elevationModel.loadElevations(tile, url))
{
this.elevationModel.levels.unmarkResourceAbsent(tile);
this.elevationModel.firePropertyChange(AVKey.ELEVATION_MODEL, null, this);
return;
}
else
{
// Assume that something's wrong with the file and delete it.
gov.nasa.worldwind.WorldWind.getDataFileCache().removeFile(url);
this.elevationModel.levels.markResourceAbsent(tile);
String message = Logging.getMessage("generic.DeletedCorruptDataFile", url);
Logging.logger().info(message);
}
}
this.elevationModel.downloadElevations(tile);
}
public final boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final RequestTask that = (RequestTask) o;
//noinspection RedundantIfStatement
if (this.tileKey != null ? !this.tileKey.equals(that.tileKey) : that.tileKey != null)
return false;
return true;
}
public final int hashCode()
{
return (this.tileKey != null ? this.tileKey.hashCode() : 0);
}
public final String toString()
{
return this.tileKey.toString();
}
}
// Reads a tile's elevations from the file cache and adds the tile to the memory cache.
private boolean loadElevations(Tile tile, java.net.URL url)
{
java.nio.ShortBuffer elevations = this.readElevations(url);
if (elevations == null)
return false;
if (this.numExpectedValues > 0 && elevations.capacity() != this.numExpectedValues)
return false; // corrupt file
tile.elevations = elevations;
this.addTileToCache(tile, elevations);
return true;
}
private void addTileToCache(Tile tile, java.nio.ShortBuffer elevations)
{
// Level 0 tiles are held in the model itself; other levels are placed in the memory cache.
if (tile.getLevelNumber() == 0)
this.levelZeroTiles.putIfAbsent(tile.getTileKey(), tile);
else
this.memoryCache.add(tile.getTileKey(), tile, elevations.limit() * 2);
}
private boolean areElevationsInMemory(TileKey key)
{
Tile tile = this.getTileFromMemory(key);
return (tile != null && tile.elevations != null);
}
private Tile getTileFromMemory(TileKey tileKey)
{
if (tileKey.getLevelNumber() == 0)
return this.levelZeroTiles.get(tileKey);
else
return (Tile) this.memoryCache.getObject(tileKey);
}
// Read elevations from the file cache. Don't be confused by the use of a URL here: it's used so that files can
// be read using System.getResource(URL), which will draw the data from a jar file in the classpath.
// TODO: Look into possibly moving the mapping to a URL into WWIO.
private java.nio.ShortBuffer readElevations(URL url)
{
try
{
ByteBuffer buffer;
synchronized (this.fileLock)
{
buffer = WWIO.readURLContentToBuffer(url);
}
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN); // TODO: byte order is format dependent
return buffer.asShortBuffer();
}
catch (java.io.IOException e)
{
Logging.logger().log(java.util.logging.Level.SEVERE,
"TiledElevationModel.ExceptionAttemptingToReadTextureFile", url.toString());
return null;
}
}
private void downloadElevations(final Tile tile)
{
if (!WorldWind.getRetrievalService().isAvailable())
return;
java.net.URL url = null;
try
{
url = tile.getResourceURL();
if (WorldWind.getNetworkStatus().isHostUnavailable(url))
return;
}
catch (java.net.MalformedURLException e)
{
Logging.logger().log(java.util.logging.Level.SEVERE,
Logging.getMessage("TiledElevationModel.ExceptionCreatingElevationsUrl", url), e);
return;
}
URLRetriever retriever = new HTTPRetriever(url, new DownloadPostProcessor(tile, this));
if (WorldWind.getRetrievalService().contains(retriever))
return;
WorldWind.getRetrievalService().runRetriever(retriever, 0d);
}
/**
* @param dc
* @param sector
* @param density
* @return
* @throws IllegalArgumentException if <code>dc</code> is null, <code>sector</code> is null or <code>density is
* negative
*/
public final int getTargetResolution(DrawContext dc, Sector sector, int density)
{
if (!this.isEnabled)
return 0;
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (sector == null)
{
String msg = Logging.getMessage("nullValue.SectorIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (density < 0)
{
Logging.logger().severe("BasicElevationModel.DensityBelowZero");
}
LatLon c = this.levels.getSector().getCentroid();
double radius = dc.getGlobe().getRadiusAt(c.getLatitude(), c.getLongitude());
double sectorWidth = sector.getDeltaLatRadians() * radius;
double targetSize = 0.8 * sectorWidth / (density); // TODO: make scale of density configurable
for (Level level : this.levels.getLevels())
{
if (level.getTexelSize(radius) < targetSize)
{
return level.getLevelNumber();
}
}
return this.levels.getNumLevels(); // finest resolution available
}
public final int getTargetResolution(Globe globe, double size)
{
if (!this.isEnabled)
return 0;
if (globe == null)
{
String msg = Logging.getMessage("nullValue.GlobeIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (size < 0)
{
Logging.logger().severe("BasicElevationModel.DensityBelowZero");
}
LatLon c = this.levels.getSector().getCentroid();
double radius = globe.getRadiusAt(c.getLatitude(), c.getLongitude());
for (Level level : this.levels.getLevels())
{
if (level.getTexelSize(radius) < size)
{
return level.getLevelNumber();
}
}
return this.levels.getNumLevels() - 1; // finest resolution available
}
private static class DownloadPostProcessor implements RetrievalPostProcessor
{
private Tile tile;
private BasicElevationModel elevationModel;
public DownloadPostProcessor(Tile tile, BasicElevationModel elevationModel)
{
// don't validate - constructor is only available to classes with private access.
this.tile = tile;
this.elevationModel = elevationModel;
}
/**
* @param retriever
* @return
* @throws IllegalArgumentException if <code>retriever</code> is null
*/
public ByteBuffer run(Retriever retriever)
{
if (retriever == null)
{
String msg = Logging.getMessage("nullValue.RetrieverIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
try
{
if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
return null;
if (retriever instanceof HTTPRetriever)
{
HTTPRetriever htr = (HTTPRetriever) retriever;
if (htr.getResponseCode() != HttpURLConnection.HTTP_OK)
{
// Mark tile as missing so avoid excessive attempts
this.elevationModel.levels.markResourceAbsent(this.tile);
return null;
}
}
URLRetriever r = (URLRetriever) retriever;
ByteBuffer buffer = r.getBuffer();
final File outFile = WorldWind.getDataFileCache().newFile(tile.getPath());
if (outFile == null)
return null;
if (outFile.exists())
return buffer;
if (buffer != null)
{
synchronized (elevationModel.fileLock)
{
WWIO.saveBuffer(buffer, outFile);
}
return buffer;
}
}
catch (java.io.IOException e)
{
Logging.logger().log(java.util.logging.Level.SEVERE,
Logging.getMessage("TiledElevationModel.ExceptionSavingRetrievedElevationFile", tile.getPath()), e);
}
finally
{
this.elevationModel.firePropertyChange(AVKey.ELEVATION_MODEL, null, this);
}
return null;
}
}
private static class BasicElevations implements ElevationModel.Elevations
{
private final int resolution;
private final Sector sector;
private final BasicElevationModel elevationModel;
private java.util.Set<Tile> tiles;
private short extremes[] = null;
private BasicElevations(Sector sector, int resolution, BasicElevationModel elevationModel)
{
this.sector = sector;
this.resolution = resolution;
this.elevationModel = elevationModel;
}
public int getResolution()
{
return this.resolution;
}
public Sector getSector()
{
return this.sector;
}
public boolean hasElevations()
{
return this.tiles != null && this.tiles.size() > 0;
}
public double getElevation(double latRadians, double lonRadians)
{
if (this.tiles == null)
return 0;
try
{
for (BasicElevationModel.Tile tile : this.tiles)
{
if (tile.getSector().containsRadians(latRadians, lonRadians))
return this.elevationModel.lookupElevation(latRadians, lonRadians, tile);
}
return 0;
}
catch (Exception e)
{
// Throwing an exception within what's likely to be the caller's geometry creation loop
// would be hard to recover from, and a reasonable response to the exception can be done here.
Logging.logger().log(java.util.logging.Level.SEVERE,
Logging.getMessage("BasicElevationModel.ExceptionComputingElevation", latRadians, lonRadians), e);
return 0;
}
}
public short[] getExtremes()
{
if (this.extremes != null)
return this.extremes;
if (this.tiles == null)
return null;
short min = Short.MAX_VALUE;
short max = Short.MIN_VALUE;
for (BasicElevationModel.Tile tile : this.tiles)
{
tile.elevations.rewind();
if (!tile.elevations.hasRemaining())
return null;
while (tile.elevations.hasRemaining())
{
short h = tile.elevations.get();
if (h > max)
max = h;
if (h < min)
min = h;
}
}
return this.extremes = new short[] {min, max};
}
}
/**
* @param latitude
* @param longitude
* @return
* @throws IllegalArgumentException if <code>latitude</code> or <code>longitude</code> is null
*/
public final double getElevation(Angle latitude, Angle longitude)
{
if (!this.isEnabled())
return 0;
if (latitude == null || longitude == null)
{
String msg = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
// TODO: Make level to draw elevations from configurable
final TileKey tileKey = new TileKey(latitude, longitude, this.levels.getLastLevel(latitude, longitude));
Tile tile = this.getTileFromMemory(tileKey);
if (tile == null)
{
int fallbackRow = tileKey.getRow();
int fallbackCol = tileKey.getColumn();
for (int fallbackLevelNum = tileKey.getLevelNumber() - 1; fallbackLevelNum >= 0; fallbackLevelNum--)
{
fallbackRow /= 2;
fallbackCol /= 2;
TileKey fallbackKey = new TileKey(fallbackLevelNum, fallbackRow, fallbackCol,
this.levels.getLevel(fallbackLevelNum).getCacheName());
tile = this.getTileFromMemory(fallbackKey);
if (tile != null)
break;
}
}
if (tile == null)
{
final TileKey zeroKey = new TileKey(latitude, longitude, this.levels.getFirstLevel());
this.requestTile(zeroKey);
return 0;
}
return this.lookupElevation(latitude.radians, longitude.radians, tile);
}
public Double getBestElevation(Angle latitude, Angle longitude)
{
if (!this.isEnabled())
return null;
if (latitude == null || longitude == null)
{
String msg = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
final TileKey tileKey = new TileKey(latitude, longitude, this.levels.getLastLevel(latitude, longitude));
Tile tile = this.getTileFromMemory(tileKey);
if (tile != null)
{
return this.lookupElevation(latitude.radians, longitude.radians, tile);
}
else
{
this.requestTile(tileKey);
return null;
}
}
public Double getElevationAtResolution(Angle latitude, Angle longitude, int resolution)
{
if (!this.isEnabled())
return null;
if (latitude == null || longitude == null)
{
String msg = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (resolution < 0 || resolution > this.getLevels().getLastLevel(longitude, latitude).getLevelNumber())
return this.getBestElevation(latitude, longitude);
final TileKey tileKey = new TileKey(latitude, longitude, this.levels.getLevel(resolution));
Tile tile = this.getTileFromMemory(tileKey);
if (tile != null)
{
return this.lookupElevation(latitude.radians, longitude.radians, tile);
}
else
{
this.requestTile(tileKey);
return null;
}
}
public final int getTileCount(Sector sector, int resolution)
{
if (sector == null)
{
String msg = Logging.getMessage("nullValue.SectorIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (!this.isEnabled())
return 0;
// Collect all the elevation tiles intersecting the input sector. If a desired tile is not curently
// available, choose its next lowest resolution parent that is available.
final Level targetLevel = this.levels.getLevel(resolution);
LatLon delta = this.levels.getLevel(resolution).getTileDelta();
final int nwRow = Tile.computeRow(delta.getLatitude(), sector.getMaxLatitude());
final int nwCol = Tile.computeColumn(delta.getLongitude(), sector.getMinLongitude());
final int seRow = Tile.computeRow(delta.getLatitude(), sector.getMinLatitude());
final int seCol = Tile.computeColumn(delta.getLongitude(), sector.getMaxLongitude());
return (1 + (nwRow - seRow) * (1 + seCol - nwCol));
}
/**
* @param sector
* @param resolution
* @return
* @throws IllegalArgumentException if <code>sector</code> is null
*/
public final Elevations getElevations(Sector sector, int resolution)
{
if (sector == null)
{
String msg = Logging.getMessage("nullValue.SectorIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (!this.isEnabled())
return new BasicElevations(sector, Integer.MIN_VALUE, this);
// Collect all the elevation tiles intersecting the input sector. If a desired tile is not curently
// available, choose its next lowest resolution parent that is available.
final Level targetLevel = this.levels.getLevel(resolution);
LatLon delta = this.levels.getLevel(resolution).getTileDelta();
final int nwRow = Tile.computeRow(delta.getLatitude(), sector.getMaxLatitude());
final int nwCol = Tile.computeColumn(delta.getLongitude(), sector.getMinLongitude());
final int seRow = Tile.computeRow(delta.getLatitude(), sector.getMinLatitude());
final int seCol = Tile.computeColumn(delta.getLongitude(), sector.getMaxLongitude());
java.util.TreeSet<Tile> tiles = new java.util.TreeSet<Tile>(new Comparator<Tile>()
{
public int compare(Tile t1, Tile t2)
{
if (t2.getLevelNumber() == t1.getLevelNumber()
&& t2.getRow() == t1.getRow() && t2.getColumn() == t1.getColumn())
return 0;
// Higher-res levels compare lower than lower-res
return t1.getLevelNumber() > t2.getLevelNumber() ? -1 : 1;
}
});
java.util.ArrayList<TileKey> requested = new java.util.ArrayList<TileKey>();
boolean missingTargetTiles = false;
boolean missingLevelZeroTiles = false;
for (int row = seRow; row <= nwRow; row++)
{
for (int col = nwCol; col <= seCol; col++)
{
TileKey key = new TileKey(resolution, row, col, targetLevel.getCacheName());
Tile tile = this.getTileFromMemory(key);
if (tile != null)
{
tiles.add(tile);
continue;
}
missingTargetTiles = true;
this.requestTile(key);
// Determine the fallback to use. Simultaneously determine a fallback to request that is
// the next resolution higher than the fallback chosen, if any. This will progressively
// refine the display until the desired resolution tile arrives.
TileKey fallbackToRequest = null;
TileKey fallbackKey = null;
int fallbackRow = row;
int fallbackCol = col;
for (int fallbackLevelNum = key.getLevelNumber() - 1; fallbackLevelNum >= 0; fallbackLevelNum--)
{
fallbackRow /= 2;
fallbackCol /= 2;
fallbackKey = new TileKey(fallbackLevelNum, fallbackRow, fallbackCol, this.levels.getLevel(
fallbackLevelNum).getCacheName());
tile = this.getTileFromMemory(fallbackKey);
if (tile != null)
{
if (!tiles.contains(tile))
tiles.add(tile);
break;
}
else
{
if (fallbackLevelNum == 0)
missingLevelZeroTiles = true;
fallbackToRequest = fallbackKey; // keep track of lowest level to request
}
}
if (fallbackToRequest != null)
{
if (!requested.contains(fallbackToRequest))
{
this.requestTile(fallbackToRequest);
requested.add(fallbackToRequest); // keep track to avoid overhead of duplicte requests
}
}
}
}
BasicElevations elevations;
// int lev = tiles.size() > 0 ? tiles.first().getLevelNumber() : 0;
// System.out.printf("%d tiles, target = %d (%d, %d), level %d, target = %d\n", tiles.size(),
// (1 + nwRow - seRow) * (1 + seCol - nwCol), nwRow - seRow, seCol - nwCol,
// lev, targetLevel.getLevelNumber());
if (missingLevelZeroTiles || tiles.isEmpty())
{
// Integer.MIN_VALUE is a signal for no in-memory tile for a given region of the sector.
elevations = new BasicElevations(sector, Integer.MIN_VALUE, this);
}
else if (missingTargetTiles)
{
// Use the level of the the lowest resolution found to denote the resolution of this elevation set.
// The list of tiles is sorted first by level, so use the level of the list's last entry.
elevations = new BasicElevations(sector, tiles.last().getLevelNumber(), this);
}
else
{
elevations = new BasicElevations(sector, resolution, this);
}
elevations.tiles = tiles;
return elevations;
}
public final int getTileCountAtResolution(Sector sector, int resolution)
{
int targetResolution = this.getLevels().getLastLevel(sector).getLevelNumber();
if (resolution >= 0)
targetResolution = Math.min(resolution, this.getLevels().getLastLevel(sector).getLevelNumber());
return this.getTileCount(sector, targetResolution);
}
public final Elevations getElevationsAtResolution(Sector sector, int resolution)
{
int targetResolution = this.getLevels().getLastLevel(sector).getLevelNumber();
if (resolution >= 0)
targetResolution = Math.min(resolution, this.getLevels().getLastLevel(sector).getLevelNumber());
Elevations elevs = this.getElevations(sector, targetResolution);
return elevs.getResolution() == targetResolution ? elevs : null;
}
public final Elevations getBestElevations(Sector sector)
{
return this.getElevationsAtResolution(sector, this.getLevels().getLastLevel(sector).getLevelNumber());
}
private double lookupElevation(final double latRadians, final double lonRadians, final Tile tile)
{
Sector sector = tile.getSector();
final int tileHeight = tile.getLevel().getTileHeight();
final int tileWidth = tile.getLevel().getTileWidth();
final double sectorDeltaLat = sector.getDeltaLat().radians;
final double sectorDeltaLon = sector.getDeltaLon().radians;
final double dLat = sector.getMaxLatitude().radians - latRadians;
final double dLon = lonRadians - sector.getMinLongitude().radians;
final double sLat = dLat / sectorDeltaLat;
final double sLon = dLon / sectorDeltaLon;
int j = (int) ((tileHeight - 1) * sLat);
int i = (int) ((tileWidth - 1) * sLon);
int k = j * tileWidth + i;
double eLeft = tile.elevations.get(k);
double eRight = i < (tileWidth - 1) ? tile.elevations.get(k + 1) : eLeft;
double dw = sectorDeltaLon / (tileWidth - 1);
double dh = sectorDeltaLat / (tileHeight - 1);
double ssLon = (dLon - i * dw) / dw;
double ssLat = (dLat - j * dh) / dh;
double eTop = eLeft + ssLon * (eRight - eLeft);
if (j < tileHeight - 1 && i < tileWidth - 1)
{
eLeft = tile.elevations.get(k + tileWidth);
eRight = tile.elevations.get(k + tileWidth + 1);
}
double eBot = eLeft + ssLon * (eRight - eLeft);
return eTop + ssLat * (eBot - eTop);
}
public double[] getMinAndMaxElevations(Sector sector)
{
if (sector == null)
{
String message = Logging.getMessage("nullValue.SectorIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (this.extremesLevel < 0 || this.extremes == null)
return new double[] {this.getMinElevation(), this.getMaxElevation()};
try
{
LatLon delta = this.levels.getLevel(this.extremesLevel).getTileDelta();
final int nwRow = Tile.computeRow(delta.getLatitude(), sector.getMaxLatitude());
final int nwCol = Tile.computeColumn(delta.getLongitude(), sector.getMinLongitude());
final int seRow = Tile.computeRow(delta.getLatitude(), sector.getMinLatitude());
final int seCol = Tile.computeColumn(delta.getLongitude(), sector.getMaxLongitude());
final int nCols = Tile.computeColumn(delta.getLongitude(), Angle.POS180) + 1;
short min = Short.MAX_VALUE;
short max = Short.MIN_VALUE;
for (int row = seRow; row <= nwRow; row++)
{
for (int col = nwCol; col <= seCol; col++)
{
int index = 2 * (row * nCols + col);
short a = this.extremes.get(index);
short b = this.extremes.get(index + 1);
if (a > max)
max = a;
if (a < min)
min = a;
if (b > max)
max = b;
if (b < min)
min = b;
}
}
return new double[] {(double) min, (double) max};
}
catch (Exception e)
{
String message = Logging.getMessage("BasicElevationModel.ExceptionDeterminingExtremes", sector);
Logging.logger().log(java.util.logging.Level.WARNING, message, e);
return new double[] {this.getMinElevation(), this.getMaxElevation()};
}
}
protected void loadExtremeElevations(String extremesFileName)
{
if (extremesFileName == null)
{
String message = Logging.getMessage("nullValue.ExtremeElevationsFileName");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
InputStream is = null;
try
{
is = this.getClass().getResourceAsStream("/" + extremesFileName);
if (is == null)
{
// Look directly in the file system
File file = new File(extremesFileName);
if (file.exists())
is = new FileInputStream(file);
else
Logging.logger().log(java.util.logging.Level.WARNING, "BasicElevationModel.UnavailableExtremesFile",
extremesFileName);
}
if (is == null)
return;
// The level the extremes were taken from is encoded as the last element in the file name
String[] tokens = extremesFileName.substring(0, extremesFileName.lastIndexOf(".")).split("_");
this.extremesLevel = Integer.parseInt(tokens[tokens.length - 1]);
if (this.extremesLevel < 0)
{
this.extremes = null;
Logging.logger().log(java.util.logging.Level.WARNING, "BasicElevationModel.UnavailableExtremesLevel",
extremesFileName);
return;
}
ByteBuffer bb = WWIO.readStreamToBuffer(is);
this.extremes = bb.asShortBuffer();
this.extremes.rewind();
}
catch (FileNotFoundException e)
{
Logging.logger().log(java.util.logging.Level.WARNING,
Logging.getMessage("BasicElevationModel.ExceptionReadingExtremeElevations", extremesFileName), e);
this.extremes = null;
this.extremesLevel = -1;
}
catch (IOException e)
{
Logging.logger().log(java.util.logging.Level.WARNING,
Logging.getMessage("BasicElevationModel.ExceptionReadingExtremeElevations", extremesFileName), e);
this.extremes = null;
this.extremesLevel = -1;
}
finally
{
if (is != null)
try
{
is.close();
}
catch (IOException e)
{
Logging.logger().log(java.util.logging.Level.WARNING,
Logging.getMessage("generic.ExceptionClosingStream", extremesFileName), e);
}
}
}
}