2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
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
.*;
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.
41 * This class represents a single tile in the data set and contains the information that needs to be cached.
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
);
74 * @throws IllegalArgumentException if <code>levels</code> is null or invalid
76 public BasicElevationModel(LevelSet levels
, double minElevation
, double maxElevation
)
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
);
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()
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())
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
))
183 Tile tile
= this.elevationModel
.createTile(this.tileKey
);
184 final java
.net
.URL url
= WorldWind
.getDataFileCache().findFile(tile
.getPath(), false);
187 if (this.elevationModel
.loadElevations(tile
, url
))
189 this.elevationModel
.levels
.unmarkResourceAbsent(tile
);
190 this.elevationModel
.firePropertyChange(AVKey
.ELEVATION_MODEL
, null, this);
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
)
210 if (o
== null || getClass() != o
.getClass())
213 final RequestTask that
= (RequestTask
) o
;
215 //noinspection RedundantIfStatement
216 if (this.tileKey
!= null ?
!this.tileKey
.equals(that
.tileKey
) : that
.tileKey
!= null)
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)
240 if (this.numExpectedValues
> 0 && elevations
.capacity() != this.numExpectedValues
)
241 return false; // corrupt file
243 tile
.elevations
= elevations
;
244 this.addTileToCache(tile
, elevations
);
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
);
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
);
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
)
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());
295 private void downloadElevations(final Tile tile
)
297 if (!WorldWind
.getRetrievalService().isAvailable())
300 java
.net
.URL url
= null;
303 url
= tile
.getResourceURL();
304 if (WorldWind
.getNetworkStatus().isHostUnavailable(url
))
307 catch (java
.net
.MalformedURLException e
)
309 Logging
.logger().log(java
.util
.logging
.Level
.SEVERE
,
310 Logging
.getMessage("TiledElevationModel.ExceptionCreatingElevationsUrl", url
), e
);
314 URLRetriever retriever
= new HTTPRetriever(url
, new DownloadPostProcessor(tile
, this));
315 if (WorldWind
.getRetrievalService().contains(retriever
))
318 WorldWind
.getRetrievalService().runRetriever(retriever
, 0d
);
326 * @throws IllegalArgumentException if <code>dc</code> is null, <code>sector</code> is null or <code>density is
329 public final int getTargetResolution(DrawContext dc
, Sector sector
, int density
)
336 String msg
= Logging
.getMessage("nullValue.DrawContextIsNull");
337 Logging
.logger().severe(msg
);
338 throw new IllegalArgumentException(msg
);
342 String msg
= Logging
.getMessage("nullValue.SectorIsNull");
343 Logging
.logger().severe(msg
);
344 throw new IllegalArgumentException(msg
);
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
)
374 String msg
= Logging
.getMessage("nullValue.GlobeIsNull");
375 Logging
.logger().severe(msg
);
376 throw new IllegalArgumentException(msg
);
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
401 private BasicElevationModel elevationModel
;
403 public DownloadPostProcessor(Tile tile
, BasicElevationModel elevationModel
)
405 // don't validate - constructor is only available to classes with private access.
407 this.elevationModel
= elevationModel
;
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
))
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
);
440 URLRetriever r
= (URLRetriever
) retriever
;
441 ByteBuffer buffer
= r
.getBuffer();
443 final File outFile
= WorldWind
.getDataFileCache().newFile(tile
.getPath());
447 if (outFile
.exists())
452 synchronized (elevationModel
.fileLock
)
454 WWIO
.saveBuffer(buffer
, outFile
);
459 catch (java
.io
.IOException e
)
461 Logging
.logger().log(java
.util
.logging
.Level
.SEVERE
,
462 Logging
.getMessage("TiledElevationModel.ExceptionSavingRetrievedElevationFile", tile
.getPath()), e
);
466 this.elevationModel
.firePropertyChange(AVKey
.ELEVATION_MODEL
, null, this);
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()
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)
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
);
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
);
529 public short[] getExtremes()
531 if (this.extremes
!= null)
532 return this.extremes
;
534 if (this.tiles
== 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())
547 while (tile
.elevations
.hasRemaining())
549 short h
= tile
.elevations
.get();
557 return this.extremes
= new short[] {min
, max
};
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())
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
);
585 int fallbackRow
= tileKey
.getRow();
586 int fallbackCol
= tileKey
.getColumn();
587 for (int fallbackLevelNum
= tileKey
.getLevelNumber() - 1; fallbackLevelNum
>= 0; fallbackLevelNum
--)
591 TileKey fallbackKey
= new TileKey(fallbackLevelNum
, fallbackRow
, fallbackCol
,
592 this.levels
.getLevel(fallbackLevelNum
).getCacheName());
593 tile
= this.getTileFromMemory(fallbackKey
);
601 final TileKey zeroKey
= new TileKey(latitude
, longitude
, this.levels
.getFirstLevel());
602 this.requestTile(zeroKey
);
607 return this.lookupElevation(latitude
.radians
, longitude
.radians
, tile
);
610 public Double
getBestElevation(Angle latitude
, Angle longitude
)
612 if (!this.isEnabled())
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
);
627 return this.lookupElevation(latitude
.radians
, longitude
.radians
, tile
);
631 this.requestTile(tileKey
);
636 public Double
getElevationAtResolution(Angle latitude
, Angle longitude
, int resolution
)
638 if (!this.isEnabled())
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
);
656 return this.lookupElevation(latitude
.radians
, longitude
.radians
, tile
);
660 this.requestTile(tileKey
);
665 public final int getTileCount(Sector sector
, int resolution
)
669 String msg
= Logging
.getMessage("nullValue.SectorIsNull");
670 Logging
.logger().severe(msg
);
671 throw new IllegalArgumentException(msg
);
674 if (!this.isEnabled())
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
));
694 * @throws IllegalArgumentException if <code>sector</code> is null
696 public final Elevations
getElevations(Sector sector
, int resolution
)
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
);
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
--)
750 fallbackKey
= new TileKey(fallbackLevelNum
, fallbackRow
, fallbackCol
, this.levels
.getLevel(
751 fallbackLevelNum
).getCacheName());
752 tile
= this.getTileFromMemory(fallbackKey
);
755 if (!tiles
.contains(tile
))
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);
798 elevations
= new BasicElevations(sector
, resolution
, this);
801 elevations
.tiles
= tiles
;
806 public final int getTileCountAtResolution(Sector sector
, int resolution
)
808 int targetResolution
= this.getLevels().getLastLevel(sector
).getLevelNumber();
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();
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);
910 return new double[] {(double) min
, (double) max
};
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
);
936 // Look directly in the file system
937 File file
= new File(extremesFileName
);
939 is
= new FileInputStream(file
);
941 Logging
.logger().log(java
.util
.logging
.Level
.WARNING
, "BasicElevationModel.UnavailableExtremesFile",
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",
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;
985 catch (IOException e
)
987 Logging
.logger().log(java
.util
.logging
.Level
.WARNING
,
988 Logging
.getMessage("generic.ExceptionClosingStream", extremesFileName
), e
);