Update to Worldwind release 0.4.1
[worldwind-tracker.git] / gov / nasa / worldwind / globes / BasicElevationModel.java
blobe3407da13a6d1335c7dfb530f24ca7dd8227cf24
1 /*
2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
5 All Rights Reserved.
6 */
7 package gov.nasa.worldwind.globes;
9 import gov.nasa.worldwind.*;
10 import gov.nasa.worldwind.avlist.AVKey;
11 import gov.nasa.worldwind.cache.*;
12 import gov.nasa.worldwind.geom.*;
13 import gov.nasa.worldwind.render.DrawContext;
14 import gov.nasa.worldwind.retrieve.*;
15 import gov.nasa.worldwind.util.*;
17 import java.io.*;
18 import java.net.*;
19 import java.nio.*;
21 // Implementation notes, not for API doc:
23 // Implements an elevation model based on a quad tree of elevation tiles. Meant to be subclassed by very specific
24 // classes, e.g. Earth/SRTM. A Descriptor passed in at construction gives the configuration parameters. Eventually
25 // Descriptor will be replaced by an XML configuration document.
27 // A "tile" corresponds to one tile of the data set, which has a corresponding unique row/column address in the data
28 // set. An inner class implements Tile. An inner class also implements TileKey, which is used to address the
29 // corresponding Tile in the memory cache.
31 // Clients of this class get elevations from it by first getting an Elevations object for a specific Sector, then
32 // querying that object for the elevation at individual lat/lon positions. The Elevations object captures information
33 // that is used to compute elevations. See in-line comments for a description.
35 // 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
36 // 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
37 // the task. The disk is never accessed during a call to getElevations(sector, resolution) because that method is
38 // likely being called when a frame is being rendered. The details of all this are in-line below.
40 /**
41 * This class represents a single tile in the data set and contains the information that needs to be cached.
43 * @author Tom Gaskins
44 * @version $Id: BasicElevationModel.java 3558 2007-11-17 08:36:45Z tgaskins $
46 public class BasicElevationModel extends WWObjectImpl implements ElevationModel
48 private boolean isEnabled = true;
49 private final LevelSet levels;
50 private final double minElevation;
51 private final double maxElevation;
52 private long numExpectedValues = 0;
53 private final Object fileLock = new Object();
54 private java.util.concurrent.ConcurrentHashMap<TileKey, Tile> levelZeroTiles =
55 new java.util.concurrent.ConcurrentHashMap<TileKey, Tile>();
56 private MemoryCache memoryCache = new BasicMemoryCache(4000000, 5000000);
57 private int extremesLevel = -1;
58 private ShortBuffer extremes = null;
60 private static final class Tile extends gov.nasa.worldwind.util.Tile implements Cacheable
62 private java.nio.ShortBuffer elevations; // the elevations themselves
64 private Tile(Sector sector, Level level, int row, int col)
66 super(sector, level, row, col);
70 /**
71 * @param levels
72 * @param minElevation
73 * @param maxElevation
74 * @throws IllegalArgumentException if <code>levels</code> is null or invalid
76 public BasicElevationModel(LevelSet levels, double minElevation, double maxElevation)
78 if (levels == null)
80 String message = Logging.getMessage("nullValue.LevelSetIsNull");
81 Logging.logger().severe(message);
82 throw new IllegalArgumentException(message);
85 String cacheName = Tile.class.getName();
86 if (WorldWind.getMemoryCacheSet().containsCache(cacheName))
88 this.memoryCache = WorldWind.getMemoryCache(cacheName);
90 else
92 long size = Configuration.getLongValue(AVKey.ELEVATION_TILE_CACHE_SIZE, 5000000L);
93 this.memoryCache = new BasicMemoryCache((long) (0.85 * size), size);
94 this.memoryCache.setName("Elevation Tiles");
95 WorldWind.getMemoryCacheSet().addCache(cacheName, this.memoryCache);
98 this.levels = new LevelSet(levels); // the caller's levelSet may change internally, so we copy it.
99 this.minElevation = minElevation;
100 this.maxElevation = maxElevation;
103 public boolean isEnabled()
105 return this.isEnabled;
108 public void setEnabled(boolean enabled)
110 this.isEnabled = enabled;
113 public LevelSet getLevels()
115 return this.levels;
118 public final double getMaxElevation()
120 return this.maxElevation;
123 public final double getMinElevation()
125 return this.minElevation;
128 public long getNumExpectedValuesPerTile()
130 return numExpectedValues;
133 public void setNumExpectedValuesPerTile(long numExpectedValues)
135 this.numExpectedValues = numExpectedValues;
138 // Create the tile corresponding to a specified key.
139 private Tile createTile(TileKey key)
141 Level level = this.levels.getLevel(key.getLevelNumber());
143 // Compute the tile's SW lat/lon based on its row/col in the level's data set.
144 Angle dLat = level.getTileDelta().getLatitude();
145 Angle dLon = level.getTileDelta().getLongitude();
147 Angle minLatitude = Tile.computeRowLatitude(key.getRow(), dLat);
148 Angle minLongitude = Tile.computeColumnLongitude(key.getColumn(), dLon);
150 Sector tileSector = new Sector(minLatitude, minLatitude.add(dLat), minLongitude, minLongitude.add(dLon));
152 return new Tile(tileSector, level, key.getRow(), key.getColumn());
155 // Thread off a task to determine whether the file is local or remote and then retrieve it either from the file
156 // cache or a remote server.
157 private void requestTile(TileKey key)
159 if (WorldWind.getTaskService().isFull())
160 return;
162 RequestTask request = new RequestTask(key, this);
163 WorldWind.getTaskService().addTask(request);
166 private static class RequestTask implements Runnable
168 private final BasicElevationModel elevationModel;
169 private final TileKey tileKey;
171 private RequestTask(TileKey tileKey, BasicElevationModel elevationModel)
173 this.elevationModel = elevationModel;
174 this.tileKey = tileKey;
177 public final void run()
179 // check to ensure load is still needed
180 if (this.elevationModel.areElevationsInMemory(this.tileKey))
181 return;
183 Tile tile = this.elevationModel.createTile(this.tileKey);
184 final java.net.URL url = WorldWind.getDataFileCache().findFile(tile.getPath(), false);
185 if (url != null)
187 if (this.elevationModel.loadElevations(tile, url))
189 this.elevationModel.levels.unmarkResourceAbsent(tile);
190 this.elevationModel.firePropertyChange(AVKey.ELEVATION_MODEL, null, this);
191 return;
193 else
195 // Assume that something's wrong with the file and delete it.
196 gov.nasa.worldwind.WorldWind.getDataFileCache().removeFile(url);
197 this.elevationModel.levels.markResourceAbsent(tile);
198 String message = Logging.getMessage("generic.DeletedCorruptDataFile", url);
199 Logging.logger().info(message);
203 this.elevationModel.downloadElevations(tile);
206 public final boolean equals(Object o)
208 if (this == o)
209 return true;
210 if (o == null || getClass() != o.getClass())
211 return false;
213 final RequestTask that = (RequestTask) o;
215 //noinspection RedundantIfStatement
216 if (this.tileKey != null ? !this.tileKey.equals(that.tileKey) : that.tileKey != null)
217 return false;
219 return true;
222 public final int hashCode()
224 return (this.tileKey != null ? this.tileKey.hashCode() : 0);
227 public final String toString()
229 return this.tileKey.toString();
233 // Reads a tile's elevations from the file cache and adds the tile to the memory cache.
234 private boolean loadElevations(Tile tile, java.net.URL url)
236 java.nio.ShortBuffer elevations = this.readElevations(url);
237 if (elevations == null)
238 return false;
240 if (this.numExpectedValues > 0 && elevations.capacity() != this.numExpectedValues)
241 return false; // corrupt file
243 tile.elevations = elevations;
244 this.addTileToCache(tile, elevations);
246 return true;
249 private void addTileToCache(Tile tile, java.nio.ShortBuffer elevations)
251 // Level 0 tiles are held in the model itself; other levels are placed in the memory cache.
252 if (tile.getLevelNumber() == 0)
253 this.levelZeroTiles.putIfAbsent(tile.getTileKey(), tile);
254 else
255 this.memoryCache.add(tile.getTileKey(), tile, elevations.limit() * 2);
258 private boolean areElevationsInMemory(TileKey key)
260 Tile tile = this.getTileFromMemory(key);
261 return (tile != null && tile.elevations != null);
264 private Tile getTileFromMemory(TileKey tileKey)
266 if (tileKey.getLevelNumber() == 0)
267 return this.levelZeroTiles.get(tileKey);
268 else
269 return (Tile) this.memoryCache.getObject(tileKey);
272 // Read elevations from the file cache. Don't be confused by the use of a URL here: it's used so that files can
273 // be read using System.getResource(URL), which will draw the data from a jar file in the classpath.
274 // TODO: Look into possibly moving the mapping to a URL into WWIO.
275 private java.nio.ShortBuffer readElevations(URL url)
279 ByteBuffer buffer;
280 synchronized (this.fileLock)
282 buffer = WWIO.readURLContentToBuffer(url);
284 buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN); // TODO: byte order is format dependent
285 return buffer.asShortBuffer();
287 catch (java.io.IOException e)
289 Logging.logger().log(java.util.logging.Level.SEVERE,
290 "TiledElevationModel.ExceptionAttemptingToReadTextureFile", url.toString());
291 return null;
295 private void downloadElevations(final Tile tile)
297 if (!WorldWind.getRetrievalService().isAvailable())
298 return;
300 java.net.URL url = null;
303 url = tile.getResourceURL();
304 if (WorldWind.getNetworkStatus().isHostUnavailable(url))
305 return;
307 catch (java.net.MalformedURLException e)
309 Logging.logger().log(java.util.logging.Level.SEVERE,
310 Logging.getMessage("TiledElevationModel.ExceptionCreatingElevationsUrl", url), e);
311 return;
314 URLRetriever retriever = new HTTPRetriever(url, new DownloadPostProcessor(tile, this));
315 if (WorldWind.getRetrievalService().contains(retriever))
316 return;
318 WorldWind.getRetrievalService().runRetriever(retriever, 0d);
322 * @param dc
323 * @param sector
324 * @param density
325 * @return
326 * @throws IllegalArgumentException if <code>dc</code> is null, <code>sector</code> is null or <code>density is
327 * negative
329 public final int getTargetResolution(DrawContext dc, Sector sector, int density)
331 if (!this.isEnabled)
332 return 0;
334 if (dc == null)
336 String msg = Logging.getMessage("nullValue.DrawContextIsNull");
337 Logging.logger().severe(msg);
338 throw new IllegalArgumentException(msg);
340 if (sector == null)
342 String msg = Logging.getMessage("nullValue.SectorIsNull");
343 Logging.logger().severe(msg);
344 throw new IllegalArgumentException(msg);
346 if (density < 0)
348 Logging.logger().severe("BasicElevationModel.DensityBelowZero");
351 LatLon c = this.levels.getSector().getCentroid();
352 double radius = dc.getGlobe().getRadiusAt(c.getLatitude(), c.getLongitude());
353 double sectorWidth = sector.getDeltaLatRadians() * radius;
354 double targetSize = 0.8 * sectorWidth / (density); // TODO: make scale of density configurable
356 for (Level level : this.levels.getLevels())
358 if (level.getTexelSize(radius) < targetSize)
360 return level.getLevelNumber();
364 return this.levels.getNumLevels(); // finest resolution available
367 public final int getTargetResolution(Globe globe, double size)
369 if (!this.isEnabled)
370 return 0;
372 if (globe == null)
374 String msg = Logging.getMessage("nullValue.GlobeIsNull");
375 Logging.logger().severe(msg);
376 throw new IllegalArgumentException(msg);
379 if (size < 0)
381 Logging.logger().severe("BasicElevationModel.DensityBelowZero");
384 LatLon c = this.levels.getSector().getCentroid();
385 double radius = globe.getRadiusAt(c.getLatitude(), c.getLongitude());
387 for (Level level : this.levels.getLevels())
389 if (level.getTexelSize(radius) < size)
391 return level.getLevelNumber();
395 return this.levels.getNumLevels(); // finest resolution available
398 private static class DownloadPostProcessor implements RetrievalPostProcessor
400 private Tile tile;
401 private BasicElevationModel elevationModel;
403 public DownloadPostProcessor(Tile tile, BasicElevationModel elevationModel)
405 // don't validate - constructor is only available to classes with private access.
406 this.tile = tile;
407 this.elevationModel = elevationModel;
411 * @param retriever
412 * @return
413 * @throws IllegalArgumentException if <code>retriever</code> is null
415 public ByteBuffer run(Retriever retriever)
417 if (retriever == null)
419 String msg = Logging.getMessage("nullValue.RetrieverIsNull");
420 Logging.logger().severe(msg);
421 throw new IllegalArgumentException(msg);
426 if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
427 return null;
429 if (retriever instanceof HTTPRetriever)
431 HTTPRetriever htr = (HTTPRetriever) retriever;
432 if (htr.getResponseCode() != HttpURLConnection.HTTP_OK)
434 // Mark tile as missing so avoid excessive attempts
435 this.elevationModel.levels.markResourceAbsent(this.tile);
436 return null;
440 URLRetriever r = (URLRetriever) retriever;
441 ByteBuffer buffer = r.getBuffer();
443 final File outFile = WorldWind.getDataFileCache().newFile(tile.getPath());
444 if (outFile == null)
445 return null;
447 if (outFile.exists())
448 return buffer;
450 if (buffer != null)
452 synchronized (elevationModel.fileLock)
454 WWIO.saveBuffer(buffer, outFile);
456 return buffer;
459 catch (java.io.IOException e)
461 Logging.logger().log(java.util.logging.Level.SEVERE,
462 Logging.getMessage("TiledElevationModel.ExceptionSavingRetrievedElevationFile", tile.getPath()), e);
464 finally
466 this.elevationModel.firePropertyChange(AVKey.ELEVATION_MODEL, null, this);
468 return null;
472 private static class BasicElevations implements ElevationModel.Elevations
474 private final int resolution;
475 private final Sector sector;
476 private final BasicElevationModel elevationModel;
477 private java.util.Set<Tile> tiles;
478 private short extremes[] = null;
480 private BasicElevations(Sector sector, int resolution, BasicElevationModel elevationModel)
482 this.sector = sector;
483 this.resolution = resolution;
484 this.elevationModel = elevationModel;
487 public int getResolution()
489 return this.resolution;
492 public Sector getSector()
494 return this.sector;
497 public boolean hasElevations()
499 return this.tiles != null && this.tiles.size() > 0;
502 public double getElevation(double latRadians, double lonRadians)
504 if (this.tiles == null)
505 return 0;
509 // TODO: Tiles are sorted by level/row/column. Use that to find containing sector faster.
510 for (BasicElevationModel.Tile tile : this.tiles)
512 if (tile.getSector().containsRadians(latRadians, lonRadians))
513 return this.elevationModel.lookupElevation(latRadians, lonRadians, tile);
516 return 0;
518 catch (Exception e)
520 // Throwing an exception within what's likely to be the caller's geometry creation loop
521 // would be hard to recover from, and a reasonable response to the exception can be done here.
522 Logging.logger().log(java.util.logging.Level.SEVERE,
523 Logging.getMessage("BasicElevationModel.ExceptionComputingElevation", latRadians, lonRadians), e);
525 return 0;
529 public short[] getExtremes()
531 if (this.extremes != null)
532 return this.extremes;
534 if (this.tiles == null)
535 return null;
537 short min = Short.MAX_VALUE;
538 short max = Short.MIN_VALUE;
540 for (BasicElevationModel.Tile tile : this.tiles)
542 tile.elevations.rewind();
544 if (!tile.elevations.hasRemaining())
545 return null;
547 while (tile.elevations.hasRemaining())
549 short h = tile.elevations.get();
550 if (h > max)
551 max = h;
552 if (h < min)
553 min = h;
557 return this.extremes = new short[] {min, max};
562 * @param latitude
563 * @param longitude
564 * @return
565 * @throws IllegalArgumentException if <code>latitude</code> or <code>longitude</code> is null
567 public final double getElevation(Angle latitude, Angle longitude)
569 if (!this.isEnabled())
570 return 0;
572 if (latitude == null || longitude == null)
574 String msg = Logging.getMessage("nullValue.AngleIsNull");
575 Logging.logger().severe(msg);
576 throw new IllegalArgumentException(msg);
579 // TODO: Make level to draw elevations from configurable
580 final TileKey tileKey = new TileKey(latitude, longitude, this.levels.getLastLevel(latitude, longitude));
581 Tile tile = this.getTileFromMemory(tileKey);
583 if (tile == null)
585 int fallbackRow = tileKey.getRow();
586 int fallbackCol = tileKey.getColumn();
587 for (int fallbackLevelNum = tileKey.getLevelNumber() - 1; fallbackLevelNum >= 0; fallbackLevelNum--)
589 fallbackRow /= 2;
590 fallbackCol /= 2;
591 TileKey fallbackKey = new TileKey(fallbackLevelNum, fallbackRow, fallbackCol,
592 this.levels.getLevel(fallbackLevelNum).getCacheName());
593 tile = this.getTileFromMemory(fallbackKey);
594 if (tile != null)
595 break;
599 if (tile == null)
601 final TileKey zeroKey = new TileKey(latitude, longitude, this.levels.getFirstLevel());
602 this.requestTile(zeroKey);
604 return 0;
607 return this.lookupElevation(latitude.radians, longitude.radians, tile);
610 public Double getBestElevation(Angle latitude, Angle longitude)
612 if (!this.isEnabled())
613 return null;
615 if (latitude == null || longitude == null)
617 String msg = Logging.getMessage("nullValue.AngleIsNull");
618 Logging.logger().severe(msg);
619 throw new IllegalArgumentException(msg);
622 final TileKey tileKey = new TileKey(latitude, longitude, this.levels.getLastLevel(latitude, longitude));
623 Tile tile = this.getTileFromMemory(tileKey);
625 if (tile != null)
627 return this.lookupElevation(latitude.radians, longitude.radians, tile);
629 else
631 this.requestTile(tileKey);
632 return null;
636 public Double getElevationAtResolution(Angle latitude, Angle longitude, int resolution)
638 if (!this.isEnabled())
639 return null;
641 if (latitude == null || longitude == null)
643 String msg = Logging.getMessage("nullValue.AngleIsNull");
644 Logging.logger().severe(msg);
645 throw new IllegalArgumentException(msg);
648 if (resolution < 0 || resolution > this.getLevels().getLastLevel(longitude, latitude).getLevelNumber())
649 return this.getBestElevation(latitude, longitude);
651 final TileKey tileKey = new TileKey(latitude, longitude, this.levels.getLevel(resolution));
652 Tile tile = this.getTileFromMemory(tileKey);
654 if (tile != null)
656 return this.lookupElevation(latitude.radians, longitude.radians, tile);
658 else
660 this.requestTile(tileKey);
661 return null;
665 public final int getTileCount(Sector sector, int resolution)
667 if (sector == null)
669 String msg = Logging.getMessage("nullValue.SectorIsNull");
670 Logging.logger().severe(msg);
671 throw new IllegalArgumentException(msg);
674 if (!this.isEnabled())
675 return 0;
677 // Collect all the elevation tiles intersecting the input sector. If a desired tile is not curently
678 // available, choose its next lowest resolution parent that is available.
679 final Level targetLevel = this.levels.getLevel(resolution);
681 LatLon delta = this.levels.getLevel(resolution).getTileDelta();
682 final int nwRow = Tile.computeRow(delta.getLatitude(), sector.getMaxLatitude());
683 final int nwCol = Tile.computeColumn(delta.getLongitude(), sector.getMinLongitude());
684 final int seRow = Tile.computeRow(delta.getLatitude(), sector.getMinLatitude());
685 final int seCol = Tile.computeColumn(delta.getLongitude(), sector.getMaxLongitude());
687 return (1 + (nwRow - seRow) * (1 + seCol - nwCol));
691 * @param sector
692 * @param resolution
693 * @return
694 * @throws IllegalArgumentException if <code>sector</code> is null
696 public final Elevations getElevations(Sector sector, int resolution)
698 if (sector == null)
700 String msg = Logging.getMessage("nullValue.SectorIsNull");
701 Logging.logger().severe(msg);
702 throw new IllegalArgumentException(msg);
705 if (!this.isEnabled())
706 return new BasicElevations(sector, Integer.MIN_VALUE, this);
708 // Collect all the elevation tiles intersecting the input sector. If a desired tile is not curently
709 // available, choose its next lowest resolution parent that is available.
710 final Level targetLevel = this.levels.getLevel(resolution);
712 LatLon delta = this.levels.getLevel(resolution).getTileDelta();
713 final int nwRow = Tile.computeRow(delta.getLatitude(), sector.getMaxLatitude());
714 final int nwCol = Tile.computeColumn(delta.getLongitude(), sector.getMinLongitude());
715 final int seRow = Tile.computeRow(delta.getLatitude(), sector.getMinLatitude());
716 final int seCol = Tile.computeColumn(delta.getLongitude(), sector.getMaxLongitude());
718 java.util.TreeSet<Tile> tiles = new java.util.TreeSet<Tile>();
719 java.util.ArrayList<TileKey> requested = new java.util.ArrayList<TileKey>();
721 boolean missingTargetTiles = false;
722 boolean missingLevelZeroTiles = false;
723 for (int row = seRow; row <= nwRow; row++)
725 for (int col = nwCol; col <= seCol; col++)
727 TileKey key = new TileKey(resolution, row, col, targetLevel.getCacheName());
728 Tile tile = this.getTileFromMemory(key);
729 if (tile != null)
731 tiles.add(tile);
732 continue;
735 missingTargetTiles = true;
736 this.requestTile(key);
738 // Determine the fallback to use. Simultaneously determine a fallback to request that is
739 // the next resolution higher than the fallback chosen, if any. This will progressively
740 // refine the display until the desired resolution tile arrives.
741 TileKey fallbackToRequest = null;
742 TileKey fallbackKey = null;
744 int fallbackRow = row;
745 int fallbackCol = col;
746 for (int fallbackLevelNum = key.getLevelNumber() - 1; fallbackLevelNum >= 0; fallbackLevelNum--)
748 fallbackRow /= 2;
749 fallbackCol /= 2;
750 fallbackKey = new TileKey(fallbackLevelNum, fallbackRow, fallbackCol, this.levels.getLevel(
751 fallbackLevelNum).getCacheName());
752 tile = this.getTileFromMemory(fallbackKey);
753 if (tile != null)
755 if (!tiles.contains(tile))
756 tiles.add(tile);
757 break;
759 else
761 if (fallbackLevelNum == 0)
762 missingLevelZeroTiles = true;
763 fallbackToRequest = fallbackKey; // keep track of lowest level to request
767 if (fallbackToRequest != null)
769 if (!requested.contains(fallbackKey))
771 this.requestTile(fallbackKey);
772 requested.add(fallbackKey); // keep track to avoid overhead of duplicte requests
778 BasicElevations elevations;
780 // int lev = tiles.size() > 0 ? tiles.first().getLevelNumber() : 0;
781 // System.out.printf("%d tiles, target = %d (%d, %d), level %d, target = %d\n", tiles.size(),
782 // (1 + nwRow - seRow) * (1 + seCol - nwCol), nwRow - seRow, seCol - nwCol,
783 // lev, targetLevel.getLevelNumber());
785 if (missingLevelZeroTiles || tiles.isEmpty())
787 // Integer.MIN_VALUE is a signal for no in-memory tile for a given region of the sector.
788 elevations = new BasicElevations(sector, Integer.MIN_VALUE, this);
790 else if (missingTargetTiles)
792 // Use the level of the the lowest resolution found as the resolution for this elevation set.
793 // The list of tiles is sorted first by level, so use the level of the list's first entry.
794 elevations = new BasicElevations(sector, tiles.first().getLevelNumber(), this);
796 else
798 elevations = new BasicElevations(sector, resolution, this);
801 elevations.tiles = tiles;
803 return elevations;
806 public final int getTileCountAtResolution(Sector sector, int resolution)
808 int targetResolution = this.getLevels().getLastLevel(sector).getLevelNumber();
809 if (resolution >= 0)
810 targetResolution = Math.min(resolution, this.getLevels().getLastLevel(sector).getLevelNumber());
811 return this.getTileCount(sector, targetResolution);
814 public final Elevations getElevationsAtResolution(Sector sector, int resolution)
816 int targetResolution = this.getLevels().getLastLevel(sector).getLevelNumber();
817 if (resolution >= 0)
818 targetResolution = Math.min(resolution, this.getLevels().getLastLevel(sector).getLevelNumber());
819 Elevations elevs = this.getElevations(sector, targetResolution);
820 return elevs.getResolution() == targetResolution ? elevs : null;
823 public final Elevations getBestElevations(Sector sector)
825 return this.getElevationsAtResolution(sector, this.getLevels().getLastLevel(sector).getLevelNumber());
828 private double lookupElevation(final double latRadians, final double lonRadians, final Tile tile)
830 Sector sector = tile.getSector();
831 final int tileHeight = tile.getLevel().getTileHeight();
832 final int tileWidth = tile.getLevel().getTileWidth();
833 final double sectorDeltaLat = sector.getDeltaLat().radians;
834 final double sectorDeltaLon = sector.getDeltaLon().radians;
835 final double dLat = sector.getMaxLatitude().radians - latRadians;
836 final double dLon = lonRadians - sector.getMinLongitude().radians;
837 final double sLat = dLat / sectorDeltaLat;
838 final double sLon = dLon / sectorDeltaLon;
840 int j = (int) ((tileHeight - 1) * sLat);
841 int i = (int) ((tileWidth - 1) * sLon);
842 int k = j * tileWidth + i;
844 double eLeft = tile.elevations.get(k);
845 double eRight = i < (tileWidth - 1) ? tile.elevations.get(k + 1) : eLeft;
847 double dw = sectorDeltaLon / (tileWidth - 1);
848 double dh = sectorDeltaLat / (tileHeight - 1);
849 double ssLon = (dLon - i * dw) / dw;
850 double ssLat = (dLat - j * dh) / dh;
852 double eTop = eLeft + ssLon * (eRight - eLeft);
854 if (j < tileHeight - 1 && i < tileWidth - 1)
856 eLeft = tile.elevations.get(k + tileWidth);
857 eRight = tile.elevations.get(k + tileWidth + 1);
860 double eBot = eLeft + ssLon * (eRight - eLeft);
861 return eTop + ssLat * (eBot - eTop);
864 public double getMinElevation(Sector sector)
866 return this.getMinAndMaxElevations(sector)[0];
869 public double getMaxElevation(Sector sector)
871 return this.getMinAndMaxElevations(sector)[1];
874 public double[] getMinAndMaxElevations(Sector sector)
876 if (this.extremesLevel < 0 || this.extremes == null)
877 return new double[] {this.getMinElevation(), this.getMaxElevation()};
881 LatLon delta = this.levels.getLevel(this.extremesLevel).getTileDelta();
882 final int nwRow = Tile.computeRow(delta.getLatitude(), sector.getMaxLatitude());
883 final int nwCol = Tile.computeColumn(delta.getLongitude(), sector.getMinLongitude());
884 final int seRow = Tile.computeRow(delta.getLatitude(), sector.getMinLatitude());
885 final int seCol = Tile.computeColumn(delta.getLongitude(), sector.getMaxLongitude());
887 final int nCols = Tile.computeColumn(delta.getLongitude(), Angle.POS180) + 1;
889 short min = Short.MAX_VALUE;
890 short max = Short.MIN_VALUE;
892 for (int row = seRow; row <= nwRow; row++)
894 for (int col = nwCol; col <= seCol; col++)
896 int index = 2 * (row * nCols + col);
897 short a = this.extremes.get(index);
898 short b = this.extremes.get(index + 1);
899 if (a > max)
900 max = a;
901 if (a < min)
902 min = a;
903 if (b > max)
904 max = b;
905 if (b < min)
906 min = b;
910 return new double[] {(double) min, (double) max};
912 catch (Exception e)
914 String message = Logging.getMessage("BasicElevationModel.ExceptionDeterminingExtremes", sector);
915 Logging.logger().log(java.util.logging.Level.WARNING, message, e);
917 return new double[] {this.getMinElevation(), this.getMaxElevation()};
921 protected void loadExtremeElevations(String extremesFileName)
923 if (extremesFileName == null)
925 String message = Logging.getMessage("nullValue.ExtremeElevationsFileName");
926 Logging.logger().severe(message);
927 throw new IllegalArgumentException(message);
930 InputStream is = null;
933 is = this.getClass().getResourceAsStream("/" + extremesFileName);
934 if (is == null)
936 // Look directly in the file system
937 File file = new File(extremesFileName);
938 if (file.exists())
939 is = new FileInputStream(file);
940 else
941 Logging.logger().log(java.util.logging.Level.WARNING, "BasicElevationModel.UnavailableExtremesFile",
942 extremesFileName);
945 if (is == null)
946 return;
948 // The level the extremes were taken from is encoded as the last element in the file name
949 String[] tokens = extremesFileName.substring(0, extremesFileName.lastIndexOf(".")).split("_");
950 this.extremesLevel = Integer.parseInt(tokens[tokens.length - 1]);
951 if (this.extremesLevel < 0)
953 this.extremes = null;
954 Logging.logger().log(java.util.logging.Level.WARNING, "BasicElevationModel.UnavailableExtremesLevel",
955 extremesFileName);
956 return;
959 ByteBuffer bb = WWIO.readStreamToBuffer(is);
960 this.extremes = bb.asShortBuffer();
961 this.extremes.rewind();
964 catch (FileNotFoundException e)
966 Logging.logger().log(java.util.logging.Level.WARNING,
967 Logging.getMessage("BasicElevationModel.ExceptionReadingExtremeElevations", extremesFileName), e);
968 this.extremes = null;
969 this.extremesLevel = -1;
971 catch (IOException e)
973 Logging.logger().log(java.util.logging.Level.WARNING,
974 Logging.getMessage("BasicElevationModel.ExceptionReadingExtremeElevations", extremesFileName), e);
975 this.extremes = null;
976 this.extremesLevel = -1;
978 finally
980 if (is != null)
983 is.close();
985 catch (IOException e)
987 Logging.logger().log(java.util.logging.Level.WARNING,
988 Logging.getMessage("generic.ExceptionClosingStream", extremesFileName), e);