src.gov.nasa.worldwind.layers.TiledImageLayer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind.layers;
import com.jogamp.opengl.util.awt.TextRenderer;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.geom.Box;
import gov.nasa.worldwind.globes.Earth;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.retrieve.*;
import gov.nasa.worldwind.util.*;
import org.w3c.dom.*;
import javax.imageio.ImageIO;
import javax.media.opengl.*;
import javax.xml.xpath.XPath;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.PriorityBlockingQueue;
/**
* @author tag
* @version $Id: TiledImageLayer.java 1824 2014-01-22 22:41:10Z dcollins $
*/
public abstract class TiledImageLayer extends AbstractLayer
{
// Infrastructure
protected static final LevelComparer levelComparer = new LevelComparer();
protected final LevelSet levels;
protected ArrayList topLevels;
protected boolean forceLevelZeroLoads = false;
protected boolean levelZeroLoaded = false;
protected boolean retainLevelZeroTiles = false;
protected String tileCountName;
protected double detailHintOrigin = 2.8;
protected double detailHint = 0;
protected boolean useMipMaps = true;
protected boolean useTransparentTextures = false;
protected ArrayList supportedImageFormats = new ArrayList();
protected String textureFormat;
// Diagnostic flags
protected boolean drawTileBoundaries = false;
private boolean drawTileIDs = false;
protected boolean drawBoundingVolumes = false;
// Stuff computed each frame
protected ArrayList currentTiles = new ArrayList();
protected TextureTile currentResourceTile;
protected boolean atMaxResolution = false;
protected PriorityBlockingQueue requestQ = new PriorityBlockingQueue(200);
abstract protected void requestTexture(DrawContext dc, TextureTile tile);
abstract protected void forceTextureLoad(TextureTile tile);
public TiledImageLayer(LevelSet levelSet)
{
if (levelSet == null)
{
String message = Logging.getMessage("nullValue.LevelSetIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.levels = new LevelSet(levelSet); // the caller's levelSet may change internally, so we copy it.
this.setValue(AVKey.SECTOR, this.levels.getSector());
this.setPickEnabled(false); // textures are assumed to be terrain unless specifically indicated otherwise.
this.tileCountName = this.getName() + " Tiles";
}
@Override
public Object setValue(String key, Object value)
{
// Offer it to the level set
if (this.getLevels() != null)
this.getLevels().setValue(key, value);
return super.setValue(key, value);
}
@Override
public Object getValue(String key)
{
Object value = super.getValue(key);
return value != null ? value : this.getLevels().getValue(key); // see if the level set has it
}
@Override
public void setName(String name)
{
super.setName(name);
this.tileCountName = this.getName() + " Tiles";
}
public boolean isForceLevelZeroLoads()
{
return this.forceLevelZeroLoads;
}
public void setForceLevelZeroLoads(boolean forceLevelZeroLoads)
{
this.forceLevelZeroLoads = forceLevelZeroLoads;
}
public boolean isRetainLevelZeroTiles()
{
return retainLevelZeroTiles;
}
public void setRetainLevelZeroTiles(boolean retainLevelZeroTiles)
{
this.retainLevelZeroTiles = retainLevelZeroTiles;
}
public boolean isDrawTileIDs()
{
return drawTileIDs;
}
public void setDrawTileIDs(boolean drawTileIDs)
{
this.drawTileIDs = drawTileIDs;
}
public boolean isDrawTileBoundaries()
{
return drawTileBoundaries;
}
public void setDrawTileBoundaries(boolean drawTileBoundaries)
{
this.drawTileBoundaries = drawTileBoundaries;
}
public boolean isDrawBoundingVolumes()
{
return drawBoundingVolumes;
}
public void setDrawBoundingVolumes(boolean drawBoundingVolumes)
{
this.drawBoundingVolumes = drawBoundingVolumes;
}
/**
* Indicates the layer's detail hint, which is described in {@link #setDetailHint(double)}.
*
* @return the detail hint
*
* @see #setDetailHint(double)
*/
public double getDetailHint()
{
return this.detailHint;
}
/**
* Modifies the default relationship of image resolution to screen resolution as the viewing altitude changes.
* Values greater than 0 cause imagery to appear at higher resolution at greater altitudes than normal, but at an
* increased performance cost. Values less than 0 decrease the default resolution at any given altitude. The default
* value is 0. Values typically range between -0.5 and 0.5.
*
* Note: The resolution-to-height relationship is defined by a scale factor that specifies the approximate size of
* discernible lengths in the image relative to eye distance. The scale is specified as a power of 10. A value of 3,
* for example, specifies that 1 meter on the surface should be distinguishable from an altitude of 10^3 meters
* (1000 meters). The default scale is 1/10^2.8, (1 over 10 raised to the power 2.8). The detail hint specifies
* deviations from that default. A detail hint of 0.2 specifies a scale of 1/1000, i.e., 1/10^(2.8 + .2) = 1/10^3.
* Scales much larger than 3 typically cause the applied resolution to be higher than discernible for the altitude.
* Such scales significantly decrease performance.
*
* @param detailHint the degree to modify the default relationship of image resolution to screen resolution with
* changing view altitudes. Values greater than 1 increase the resolution. Values less than zero
* decrease the resolution. The default value is 0.
*/
public void setDetailHint(double detailHint)
{
this.detailHint = detailHint;
}
public LevelSet getLevels()
{
return levels;
}
protected PriorityBlockingQueue getRequestQ()
{
return requestQ;
}
@Override
public boolean isMultiResolution()
{
return this.getLevels() != null && this.getLevels().getNumLevels() > 1;
}
@Override
public boolean isAtMaxResolution()
{
return this.atMaxResolution;
}
/**
* Returns the format used to store images in texture memory, or null if images are stored in their native format.
*
* @return the texture image format; null if images are stored in their native format.
*
* @see #setTextureFormat(String)
*/
public String getTextureFormat()
{
return this.textureFormat;
}
/**
* Specifies the format used to store images in texture memory, or null to store images in their native format.
* Supported texture formats are as follows: image/dds
- Stores images in the compressed DDS
* format. If the image is already in DDS format it's stored as-is.
*
* @param textureFormat the texture image format; null to store images in their native format.
*/
public void setTextureFormat(String textureFormat)
{
this.textureFormat = textureFormat;
}
public boolean isUseMipMaps()
{
return useMipMaps;
}
public void setUseMipMaps(boolean useMipMaps)
{
this.useMipMaps = useMipMaps;
}
public boolean isUseTransparentTextures()
{
return this.useTransparentTextures;
}
public void setUseTransparentTextures(boolean useTransparentTextures)
{
this.useTransparentTextures = useTransparentTextures;
}
/**
* Specifies the time of the layer's most recent dataset update, beyond which cached data is invalid. If greater
* than zero, the layer ignores and eliminates any in-memory or on-disk cached data older than the time specified,
* and requests new information from the data source. If zero, the default, the layer applies any expiry times
* associated with its individual levels, but only for on-disk cached data. In-memory cached data is expired only
* when the expiry time is specified with this method and is greater than zero. This method also overwrites the
* expiry times of the layer's individual levels if the value specified to the method is greater than zero.
*
* @param expiryTime the expiry time of any cached data, expressed as a number of milliseconds beyond the epoch. The
* default expiry time is zero.
*
* @see System#currentTimeMillis() for a description of milliseconds beyond the epoch.
*/
public void setExpiryTime(long expiryTime) // Override this method to use intrinsic level-specific expiry times
{
super.setExpiryTime(expiryTime);
if (expiryTime > 0)
this.levels.setExpiryTime(expiryTime); // remove this in sub-class to use level-specific expiry times
}
public List getTopLevels()
{
if (this.topLevels == null)
this.createTopLevelTiles();
return topLevels;
}
protected void createTopLevelTiles()
{
Sector sector = this.levels.getSector();
Level level = levels.getFirstLevel();
Angle dLat = level.getTileDelta().getLatitude();
Angle dLon = level.getTileDelta().getLongitude();
Angle latOrigin = this.levels.getTileOrigin().getLatitude();
Angle lonOrigin = this.levels.getTileOrigin().getLongitude();
// Determine the row and column offset from the common World Wind global tiling origin.
int firstRow = Tile.computeRow(dLat, sector.getMinLatitude(), latOrigin);
int firstCol = Tile.computeColumn(dLon, sector.getMinLongitude(), lonOrigin);
int lastRow = Tile.computeRow(dLat, sector.getMaxLatitude(), latOrigin);
int lastCol = Tile.computeColumn(dLon, sector.getMaxLongitude(), lonOrigin);
int nLatTiles = lastRow - firstRow + 1;
int nLonTiles = lastCol - firstCol + 1;
this.topLevels = new ArrayList(nLatTiles * nLonTiles);
Angle p1 = Tile.computeRowLatitude(firstRow, dLat, latOrigin);
for (int row = firstRow; row <= lastRow; row++)
{
Angle p2;
p2 = p1.add(dLat);
Angle t1 = Tile.computeColumnLongitude(firstCol, dLon, lonOrigin);
for (int col = firstCol; col <= lastCol; col++)
{
Angle t2;
t2 = t1.add(dLon);
this.topLevels.add(new TextureTile(new Sector(p1, p2, t1, t2), level, row, col));
t1 = t2;
}
p1 = p2;
}
}
protected void loadAllTopLevelTextures(DrawContext dc)
{
for (TextureTile tile : this.getTopLevels())
{
if (!tile.isTextureInMemory(dc.getTextureCache()))
this.forceTextureLoad(tile);
}
this.levelZeroLoaded = true;
}
// ============== Tile Assembly ======================= //
// ============== Tile Assembly ======================= //
// ============== Tile Assembly ======================= //
protected void assembleTiles(DrawContext dc)
{
this.currentTiles.clear();
for (TextureTile tile : this.getTopLevels())
{
if (this.isTileVisible(dc, tile))
{
this.currentResourceTile = null;
this.addTileOrDescendants(dc, tile);
}
}
}
protected void addTileOrDescendants(DrawContext dc, TextureTile tile)
{
if (this.meetsRenderCriteria(dc, tile))
{
this.addTile(dc, tile);
return;
}
// The incoming tile does not meet the rendering criteria, so it must be subdivided and those
// subdivisions tested against the criteria.
// All tiles that meet the selection criteria are drawn, but some of those tiles will not have
// textures associated with them either because their texture isn't loaded yet or because they
// are finer grain than the layer has textures for. In these cases the tiles use the texture of
// the closest ancestor that has a texture loaded. This ancestor is called the currentResourceTile.
// A texture transform is applied during rendering to align the sector's texture coordinates with the
// appropriate region of the ancestor's texture.
TextureTile ancestorResource = null;
try
{
// TODO: Revise this to reflect that the parent layer is only requested while the algorithm continues
// to search for the layer matching the criteria.
// At this point the tile does not meet the render criteria but it may have its texture in memory.
// If so, register this tile as the resource tile. If not, then this tile will be the next level
// below a tile with texture in memory. So to provide progressive resolution increase, add this tile
// to the draw list. That will cause the tile to be drawn using its parent tile's texture, and it will
// cause it's texture to be requested. At some future call to this method the tile's texture will be in
// memory, it will not meet the render criteria, but will serve as the parent to a tile that goes
// through this same process as this method recurses. The result of all this is that a tile isn't rendered
// with its own texture unless all its parents have their textures loaded. In addition to causing
// progressive resolution increase, this ensures that the parents are available as the user zooms out, and
// therefore the layer remains visible until the user is zoomed out to the point the layer is no longer
// active.
if (tile.isTextureInMemory(dc.getTextureCache()) || tile.getLevelNumber() == 0)
{
ancestorResource = this.currentResourceTile;
this.currentResourceTile = tile;
}
else if (!tile.getLevel().isEmpty())
{
// this.addTile(dc, tile);
// return;
// Issue a request for the parent before descending to the children.
// if (tile.getLevelNumber() < this.levels.getNumLevels())
// {
// // Request only tiles with data associated at this level
// if (!this.levels.isResourceAbsent(tile))
// this.requestTexture(dc, tile);
// }
}
TextureTile[] subTiles = tile.createSubTiles(this.levels.getLevel(tile.getLevelNumber() + 1));
for (TextureTile child : subTiles)
{
if (this.getLevels().getSector().intersects(child.getSector()) && this.isTileVisible(dc, child))
this.addTileOrDescendants(dc, child);
}
}
finally
{
if (ancestorResource != null) // Pop this tile as the currentResource ancestor
this.currentResourceTile = ancestorResource;
}
}
protected void addTile(DrawContext dc, TextureTile tile)
{
tile.setFallbackTile(null);
if (tile.isTextureInMemory(dc.getTextureCache()))
{
this.addTileToCurrent(tile);
return;
}
// Level 0 loads may be forced
if (tile.getLevelNumber() == 0 && this.forceLevelZeroLoads && !tile.isTextureInMemory(dc.getTextureCache()))
{
this.forceTextureLoad(tile);
if (tile.isTextureInMemory(dc.getTextureCache()))
{
this.addTileToCurrent(tile);
return;
}
}
// Tile's texture isn't available, so request it
if (tile.getLevelNumber() < this.levels.getNumLevels())
{
// Request only tiles with data associated at this level
if (!this.levels.isResourceAbsent(tile))
this.requestTexture(dc, tile);
}
// Set up to use the currentResource tile's texture
if (this.currentResourceTile != null)
{
if (this.currentResourceTile.getLevelNumber() == 0 && this.forceLevelZeroLoads &&
!this.currentResourceTile.isTextureInMemory(dc.getTextureCache()) &&
!this.currentResourceTile.isTextureInMemory(dc.getTextureCache()))
this.forceTextureLoad(this.currentResourceTile);
if (this.currentResourceTile.isTextureInMemory(dc.getTextureCache()))
{
tile.setFallbackTile(currentResourceTile);
this.addTileToCurrent(tile);
}
}
}
protected void addTileToCurrent(TextureTile tile)
{
this.currentTiles.add(tile);
}
protected boolean isTileVisible(DrawContext dc, TextureTile tile)
{
return tile.getExtent(dc).intersects(dc.getView().getFrustumInModelCoordinates()) &&
(dc.getVisibleSector() == null || dc.getVisibleSector().intersects(tile.getSector()));
}
protected boolean meetsRenderCriteria(DrawContext dc, TextureTile tile)
{
return this.levels.isFinalLevel(tile.getLevelNumber()) || !needToSplit(dc, tile.getSector(), tile.getLevel());
}
protected double getDetailFactor()
{
return this.detailHintOrigin + this.getDetailHint();
}
protected boolean needToSplit(DrawContext dc, Sector sector, Level level)
{
// Compute the height in meters of a texel from the specified level. Take care to convert from the radians to
// meters by multiplying by the globe's radius, not the length of a Cartesian point. Using the length of a
// Cartesian point is incorrect when the globe is flat.
double texelSizeRadians = level.getTexelSize();
double texelSizeMeters = dc.getGlobe().getRadius() * texelSizeRadians;
// Compute the level of detail scale and the field of view scale. These scales are multiplied by the eye
// distance to derive a scaled distance that is then compared to the texel size. The level of detail scale is
// specified as a power of 10. For example, a detail factor of 3 means split when the cell size becomes more
// than one thousandth of the eye distance. The field of view scale is specified as a ratio between the current
// field of view and a the default field of view. In a perspective projection, decreasing the field of view by
// 50% has the same effect on object size as decreasing the distance between the eye and the object by 50%.
double detailScale = Math.pow(10, -this.getDetailFactor());
double fieldOfViewScale = dc.getView().getFieldOfView().tanHalfAngle() / Angle.fromDegrees(45).tanHalfAngle();
fieldOfViewScale = WWMath.clamp(fieldOfViewScale, 0, 1);
// Compute the distance between the eye point and the sector in meters, and compute a fraction of that distance
// by multiplying the actual distance by the level of detail scale and the field of view scale.
double eyeDistanceMeters = sector.distanceTo(dc, dc.getView().getEyePoint());
double scaledEyeDistanceMeters = eyeDistanceMeters * detailScale * fieldOfViewScale;
// Split when the texel size in meters becomes greater than the specified fraction of the eye distance, also in
// meters. Another way to say it is, use the current tile if its texel size is less than the specified fraction
// of the eye distance.
//
// NOTE: It's tempting to instead compare a screen pixel size to the texel size, but that calculation is
// window-size dependent and results in selecting an excessive number of tiles when the window is large.
return texelSizeMeters > scaledEyeDistanceMeters;
}
public Double getMinEffectiveAltitude(Double radius)
{
if (radius == null)
radius = Earth.WGS84_EQUATORIAL_RADIUS;
// Get the texel size in meters for the highest-resolution level.
double texelSizeRadians = this.getLevels().getLastLevel().getTexelSize();
double texelSizeMeters = radius * texelSizeRadians;
// Compute altitude associated with the texel size at which it would switch if it had higher-res levels.
return texelSizeMeters * Math.pow(10, this.getDetailFactor());
}
public Double getMaxEffectiveAltitude(Double radius)
{
if (radius == null)
radius = Earth.WGS84_EQUATORIAL_RADIUS;
// Find first non-empty level. Compute altitude at which it comes into effect.
for (int i = 0; i < this.getLevels().getLastLevel().getLevelNumber(); i++)
{
if (this.levels.isLevelEmpty(i))
continue;
// Compute altitude associated with the texel size at which it would switch if it had a lower-res level.
// That texel size is twice that of the current lowest-res level.
double texelSizeRadians = this.levels.getLevel(i).getTexelSize();
double texelSizeMeters = 2 * radius * texelSizeRadians;
return texelSizeMeters * Math.pow(10, this.getDetailFactor());
}
return null;
}
protected boolean atMaxLevel(DrawContext dc)
{
Position vpc = dc.getViewportCenterPosition();
if (dc.getView() == null || this.getLevels() == null || vpc == null)
return false;
if (!this.getLevels().getSector().contains(vpc.getLatitude(), vpc.getLongitude()))
return true;
Level nextToLast = this.getLevels().getNextToLastLevel();
if (nextToLast == null)
return true;
Sector centerSector = nextToLast.computeSectorForPosition(vpc.getLatitude(), vpc.getLongitude(),
this.levels.getTileOrigin());
return this.needToSplit(dc, centerSector, nextToLast);
}
// ============== Rendering ======================= //
// ============== Rendering ======================= //
// ============== Rendering ======================= //
@Override
public void render(DrawContext dc)
{
this.atMaxResolution = this.atMaxLevel(dc);
super.render(dc);
}
@Override
protected final void doRender(DrawContext dc)
{
if (this.forceLevelZeroLoads && !this.levelZeroLoaded)
this.loadAllTopLevelTextures(dc);
if (dc.getSurfaceGeometry() == null || dc.getSurfaceGeometry().size() < 1)
return;
dc.getGeographicSurfaceTileRenderer().setShowImageTileOutlines(this.isDrawTileBoundaries());
draw(dc);
}
protected void draw(DrawContext dc)
{
this.assembleTiles(dc); // Determine the tiles to draw.
if (this.currentTiles.size() >= 1)
{
// Indicate that this layer rendered something this frame.
this.setValue(AVKey.FRAME_TIMESTAMP, dc.getFrameTimeStamp());
if (this.getScreenCredit() != null)
{
dc.addScreenCredit(this.getScreenCredit());
}
TextureTile[] sortedTiles = new TextureTile[this.currentTiles.size()];
sortedTiles = this.currentTiles.toArray(sortedTiles);
Arrays.sort(sortedTiles, levelComparer);
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
if (this.isUseTransparentTextures() || this.getOpacity() < 1)
{
gl.glPushAttrib(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_POLYGON_BIT | GL2.GL_CURRENT_BIT);
this.setBlendingFunction(dc);
}
else
{
gl.glPushAttrib(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_POLYGON_BIT);
}
gl.glPolygonMode(GL2.GL_FRONT, GL2.GL_FILL);
gl.glEnable(GL.GL_CULL_FACE);
gl.glCullFace(GL.GL_BACK);
dc.setPerFrameStatistic(PerformanceStatistic.IMAGE_TILE_COUNT, this.tileCountName,
this.currentTiles.size());
dc.getGeographicSurfaceTileRenderer().renderTiles(dc, this.currentTiles);
gl.glPopAttrib();
if (this.drawTileIDs)
this.drawTileIDs(dc, this.currentTiles);
if (this.drawBoundingVolumes)
this.drawBoundingVolumes(dc, this.currentTiles);
// Check texture expiration. Memory-cached textures are checked for expiration only when an explicit,
// non-zero expiry time has been set for the layer. If none has been set, the expiry times of the layer's
// individual levels are used, but only for images in the local file cache, not textures in memory. This is
// to avoid incurring the overhead of checking expiration of in-memory textures, a very rarely used feature.
if (this.getExpiryTime() > 0 && this.getExpiryTime() <= System.currentTimeMillis())
this.checkTextureExpiration(dc, this.currentTiles);
this.currentTiles.clear();
}
this.sendRequests();
this.requestQ.clear();
}
protected void checkTextureExpiration(DrawContext dc, List tiles)
{
for (TextureTile tile : tiles)
{
if (tile.isTextureExpired())
this.requestTexture(dc, tile);
}
}
protected void setBlendingFunction(DrawContext dc)
{
// Set up a premultiplied-alpha blending function. Any texture read by JOGL will have alpha-premultiplied color
// components, as will any DDS file created by World Wind or the World Wind WMS. We'll also set up the base
// color as a premultiplied color, so that any incoming premultiplied color will be properly combined with the
// base color.
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
double alpha = this.getOpacity();
gl.glColor4d(alpha, alpha, alpha, alpha);
gl.glEnable(GL.GL_BLEND);
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
}
protected void sendRequests()
{
Runnable task = this.requestQ.poll();
while (task != null)
{
if (!WorldWind.getTaskService().isFull())
{
WorldWind.getTaskService().addTask(task);
}
task = this.requestQ.poll();
}
}
public boolean isLayerInView(DrawContext dc)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
if (dc.getView() == null)
{
String message = Logging.getMessage("layers.AbstractLayer.NoViewSpecifiedInDrawingContext");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
return !(dc.getVisibleSector() != null && !this.levels.getSector().intersects(dc.getVisibleSector()));
}
protected Vec4 computeReferencePoint(DrawContext dc)
{
if (dc.getViewportCenterPosition() != null)
return dc.getGlobe().computePointFromPosition(dc.getViewportCenterPosition());
java.awt.geom.Rectangle2D viewport = dc.getView().getViewport();
int x = (int) viewport.getWidth() / 2;
for (int y = (int) (0.5 * viewport.getHeight()); y >= 0; y--)
{
Position pos = dc.getView().computePositionFromScreenPoint(x, y);
if (pos == null)
continue;
return dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), 0d);
}
return null;
}
protected Vec4 getReferencePoint(DrawContext dc)
{
return this.computeReferencePoint(dc);
}
protected static class LevelComparer implements Comparator
{
public int compare(TextureTile ta, TextureTile tb)
{
int la = ta.getFallbackTile() == null ? ta.getLevelNumber() : ta.getFallbackTile().getLevelNumber();
int lb = tb.getFallbackTile() == null ? tb.getLevelNumber() : tb.getFallbackTile().getLevelNumber();
return la < lb ? -1 : la == lb ? 0 : 1;
}
}
protected void drawTileIDs(DrawContext dc, ArrayList tiles)
{
java.awt.Rectangle viewport = dc.getView().getViewport();
TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(),
java.awt.Font.decode("Arial-Plain-13"));
GL gl = dc.getGL();
gl.glDisable(GL.GL_DEPTH_TEST);
gl.glDisable(GL.GL_BLEND);
gl.glDisable(GL.GL_TEXTURE_2D);
textRenderer.beginRendering(viewport.width, viewport.height);
textRenderer.setColor(java.awt.Color.YELLOW);
for (TextureTile tile : tiles)
{
String tileLabel = tile.getLabel();
if (tile.getFallbackTile() != null)
tileLabel += "/" + tile.getFallbackTile().getLabel();
LatLon ll = tile.getSector().getCentroid();
Vec4 pt = dc.getGlobe().computePointFromPosition(ll.getLatitude(), ll.getLongitude(),
dc.getGlobe().getElevation(ll.getLatitude(), ll.getLongitude()));
pt = dc.getView().project(pt);
textRenderer.draw(tileLabel, (int) pt.x, (int) pt.y);
}
textRenderer.setColor(java.awt.Color.WHITE);
textRenderer.endRendering();
}
protected void drawBoundingVolumes(DrawContext dc, ArrayList tiles)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
float[] previousColor = new float[4];
gl.glGetFloatv(GL2.GL_CURRENT_COLOR, previousColor, 0);
gl.glColor3d(0, 1, 0);
for (TextureTile tile : tiles)
{
if (tile.getExtent(dc) instanceof Renderable)
((Renderable) tile.getExtent(dc)).render(dc);
}
Box c = Sector.computeBoundingBox(dc.getGlobe(), dc.getVerticalExaggeration(), this.levels.getSector());
gl.glColor3d(1, 1, 0);
c.render(dc);
gl.glColor4fv(previousColor, 0);
}
//**************************************************************//
//******************** Configuration *************************//
//**************************************************************//
/**
* Creates a configuration document for a TiledImageLayer described by the specified params. The returned document
* may be used as a construction parameter to {@link gov.nasa.worldwind.layers.BasicTiledImageLayer}.
*
* @param params parameters describing the TiledImageLayer.
*
* @return a configuration document for the TiledImageLayer.
*/
public static Document createTiledImageLayerConfigDocument(AVList params)
{
Document doc = WWXML.createDocumentBuilder(true).newDocument();
Element root = WWXML.setDocumentElement(doc, "Layer");
WWXML.setIntegerAttribute(root, "version", 1);
WWXML.setTextAttribute(root, "layerType", "TiledImageLayer");
createTiledImageLayerConfigElements(params, root);
return doc;
}
/**
* Appends TiledImageLayer configuration parameters as elements to the specified context. This appends elements for
* the following parameters: Parameter Element Path Type {@link
* AVKey#SERVICE_NAME} Service/@serviceName String {@link
* AVKey#IMAGE_FORMAT} ImageFormat String {@link
* AVKey#AVAILABLE_IMAGE_FORMATS} AvailableImageFormats/ImageFormat String array
* {@link AVKey#FORCE_LEVEL_ZERO_LOADS} ForceLevelZeroLoads Boolean {@link
* AVKey#RETAIN_LEVEL_ZERO_TILES} RetainLevelZeroTiles Boolean {@link
* AVKey#TEXTURE_FORMAT} TextureFormat String {@link
* AVKey#USE_MIP_MAPS} UseMipMaps Boolean {@link
* AVKey#USE_TRANSPARENT_TEXTURES} UseTransparentTextures Boolean {@link
* AVKey#URL_CONNECT_TIMEOUT} RetrievalTimeouts/ConnectTimeout/Time Integer milliseconds
* {@link AVKey#URL_READ_TIMEOUT} RetrievalTimeouts/ReadTimeout/Time Integer
* milliseconds {@link AVKey#RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT}
* RetrievalTimeouts/StaleRequestLimit/Time Integer milliseconds
This also writes
* common layer and LevelSet configuration parameters by invoking {@link gov.nasa.worldwind.layers.AbstractLayer#createLayerConfigElements(gov.nasa.worldwind.avlist.AVList,
* org.w3c.dom.Element)} and {@link DataConfigurationUtils#createLevelSetConfigElements(gov.nasa.worldwind.avlist.AVList,
* org.w3c.dom.Element)}.
*
* @param params the key-value pairs which define the TiledImageLayer configuration parameters.
* @param context the XML document root on which to append TiledImageLayer configuration elements.
*
* @return a reference to context.
*
* @throws IllegalArgumentException if either the parameters or the context are null.
*/
public static Element createTiledImageLayerConfigElements(AVList params, Element context)
{
if (params == null)
{
String message = Logging.getMessage("nullValue.ParametersIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (context == null)
{
String message = Logging.getMessage("nullValue.ContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
XPath xpath = WWXML.makeXPath();
// Common layer properties.
AbstractLayer.createLayerConfigElements(params, context);
// LevelSet properties.
DataConfigurationUtils.createLevelSetConfigElements(params, context);
// Service properties.
// Try to get the SERVICE_NAME property, but default to "WWTileService".
String s = AVListImpl.getStringValue(params, AVKey.SERVICE_NAME, "WWTileService");
if (s != null && s.length() > 0)
{
// The service element may already exist, in which case we want to append to it.
Element el = WWXML.getElement(context, "Service", xpath);
if (el == null)
el = WWXML.appendElementPath(context, "Service");
WWXML.setTextAttribute(el, "serviceName", s);
}
WWXML.checkAndAppendBooleanElement(params, AVKey.RETRIEVE_PROPERTIES_FROM_SERVICE, context,
"RetrievePropertiesFromService");
// Image format properties.
WWXML.checkAndAppendTextElement(params, AVKey.IMAGE_FORMAT, context, "ImageFormat");
WWXML.checkAndAppendTextElement(params, AVKey.TEXTURE_FORMAT, context, "TextureFormat");
Object o = params.getValue(AVKey.AVAILABLE_IMAGE_FORMATS);
if (o != null && o instanceof String[])
{
String[] strings = (String[]) o;
if (strings.length > 0)
{
// The available image formats element may already exists, in which case we want to append to it, rather
// than create entirely separate paths.
Element el = WWXML.getElement(context, "AvailableImageFormats", xpath);
if (el == null)
el = WWXML.appendElementPath(context, "AvailableImageFormats");
WWXML.appendTextArray(el, "ImageFormat", strings);
}
}
// Optional behavior properties.
WWXML.checkAndAppendBooleanElement(params, AVKey.FORCE_LEVEL_ZERO_LOADS, context, "ForceLevelZeroLoads");
WWXML.checkAndAppendBooleanElement(params, AVKey.RETAIN_LEVEL_ZERO_TILES, context, "RetainLevelZeroTiles");
WWXML.checkAndAppendBooleanElement(params, AVKey.USE_MIP_MAPS, context, "UseMipMaps");
WWXML.checkAndAppendBooleanElement(params, AVKey.USE_TRANSPARENT_TEXTURES, context, "UseTransparentTextures");
WWXML.checkAndAppendDoubleElement(params, AVKey.DETAIL_HINT, context, "DetailHint");
// Retrieval properties.
if (params.getValue(AVKey.URL_CONNECT_TIMEOUT) != null ||
params.getValue(AVKey.URL_READ_TIMEOUT) != null ||
params.getValue(AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT) != null)
{
Element el = WWXML.getElement(context, "RetrievalTimeouts", xpath);
if (el == null)
el = WWXML.appendElementPath(context, "RetrievalTimeouts");
WWXML.checkAndAppendTimeElement(params, AVKey.URL_CONNECT_TIMEOUT, el, "ConnectTimeout/Time");
WWXML.checkAndAppendTimeElement(params, AVKey.URL_READ_TIMEOUT, el, "ReadTimeout/Time");
WWXML.checkAndAppendTimeElement(params, AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT, el,
"StaleRequestLimit/Time");
}
return context;
}
/**
* Parses TiledImageLayer configuration parameters from the specified DOM document. This writes output as key-value
* pairs to params. If a parameter from the XML document already exists in params, that parameter is ignored.
* Supported key and parameter names are: Parameter Element Path Type
* {@link AVKey#SERVICE_NAME} Service/@serviceName String {@link
* AVKey#IMAGE_FORMAT} ImageFormat String {@link
* AVKey#AVAILABLE_IMAGE_FORMATS} AvailableImageFormats/ImageFormat String array
* {@link AVKey#FORCE_LEVEL_ZERO_LOADS} ForceLevelZeroLoads Boolean {@link
* AVKey#RETAIN_LEVEL_ZERO_TILES} RetainLevelZeroTiles Boolean {@link
* AVKey#TEXTURE_FORMAT} TextureFormat Boolean {@link
* AVKey#USE_MIP_MAPS} UseMipMaps Boolean {@link
* AVKey#USE_TRANSPARENT_TEXTURES} UseTransparentTextures Boolean {@link
* AVKey#URL_CONNECT_TIMEOUT} RetrievalTimeouts/ConnectTimeout/Time Integer milliseconds
* {@link AVKey#URL_READ_TIMEOUT} RetrievalTimeouts/ReadTimeout/Time Integer
* milliseconds {@link AVKey#RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT}
* RetrievalTimeouts/StaleRequestLimit/Time Integer milliseconds
This also parses
* common layer and LevelSet configuration parameters by invoking {@link gov.nasa.worldwind.layers.AbstractLayer#getLayerConfigParams(org.w3c.dom.Element,
* gov.nasa.worldwind.avlist.AVList)} and {@link gov.nasa.worldwind.util.DataConfigurationUtils#getLevelSetConfigParams(org.w3c.dom.Element,
* gov.nasa.worldwind.avlist.AVList)}.
*
* @param domElement the XML document root to parse for TiledImageLayer configuration parameters.
* @param params the output key-value pairs which recieve the TiledImageLayer configuration parameters. A null
* reference is permitted.
*
* @return a reference to params, or a new AVList if params is null.
*
* @throws IllegalArgumentException if the document is null.
*/
public static AVList getTiledImageLayerConfigParams(Element domElement, AVList params)
{
if (domElement == null)
{
String message = Logging.getMessage("nullValue.DocumentIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (params == null)
params = new AVListImpl();
XPath xpath = WWXML.makeXPath();
// Common layer properties.
AbstractLayer.getLayerConfigParams(domElement, params);
// LevelSet properties.
DataConfigurationUtils.getLevelSetConfigParams(domElement, params);
// Service properties.
WWXML.checkAndSetStringParam(domElement, params, AVKey.SERVICE_NAME, "Service/@serviceName", xpath);
WWXML.checkAndSetBooleanParam(domElement, params, AVKey.RETRIEVE_PROPERTIES_FROM_SERVICE,
"RetrievePropertiesFromService", xpath);
// Image format properties.
WWXML.checkAndSetStringParam(domElement, params, AVKey.IMAGE_FORMAT, "ImageFormat", xpath);
WWXML.checkAndSetStringParam(domElement, params, AVKey.TEXTURE_FORMAT, "TextureFormat", xpath);
WWXML.checkAndSetUniqueStringsParam(domElement, params, AVKey.AVAILABLE_IMAGE_FORMATS,
"AvailableImageFormats/ImageFormat", xpath);
// Optional behavior properties.
WWXML.checkAndSetBooleanParam(domElement, params, AVKey.FORCE_LEVEL_ZERO_LOADS, "ForceLevelZeroLoads", xpath);
WWXML.checkAndSetBooleanParam(domElement, params, AVKey.RETAIN_LEVEL_ZERO_TILES, "RetainLevelZeroTiles", xpath);
WWXML.checkAndSetBooleanParam(domElement, params, AVKey.USE_MIP_MAPS, "UseMipMaps", xpath);
WWXML.checkAndSetBooleanParam(domElement, params, AVKey.USE_TRANSPARENT_TEXTURES, "UseTransparentTextures",
xpath);
WWXML.checkAndSetDoubleParam(domElement, params, AVKey.DETAIL_HINT, "DetailHint", xpath);
WWXML.checkAndSetColorArrayParam(domElement, params, AVKey.TRANSPARENCY_COLORS, "TransparencyColors/Color",
xpath);
// Retrieval properties. Convert the Long time values to Integers, because BasicTiledImageLayer is expecting
// Integer values.
WWXML.checkAndSetTimeParamAsInteger(domElement, params, AVKey.URL_CONNECT_TIMEOUT,
"RetrievalTimeouts/ConnectTimeout/Time", xpath);
WWXML.checkAndSetTimeParamAsInteger(domElement, params, AVKey.URL_READ_TIMEOUT,
"RetrievalTimeouts/ReadTimeout/Time", xpath);
WWXML.checkAndSetTimeParamAsInteger(domElement, params, AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT,
"RetrievalTimeouts/StaleRequestLimit/Time", xpath);
// Parse the legacy configuration parameters. This enables TiledImageLayer to recognize elements from previous
// versions of configuration documents.
getLegacyTiledImageLayerConfigParams(domElement, params);
return params;
}
/**
* Parses TiledImageLayer configuration parameters from previous versions of configuration documents. This writes
* output as key-value pairs to params. If a parameter from the XML document already exists in params, that
* parameter is ignored. Supported key and parameter names are: Parameter Element
* Path Type {@link AVKey#TEXTURE_FORMAT} CompressTextures "image/dds" if
* CompressTextures is "true"; null otherwise
*
* @param domElement the XML document root to parse for legacy TiledImageLayer configuration parameters.
* @param params the output key-value pairs which recieve the TiledImageLayer configuration parameters. A null
* reference is permitted.
*
* @return a reference to params, or a new AVList if params is null.
*
* @throws IllegalArgumentException if the document is null.
*/
protected static AVList getLegacyTiledImageLayerConfigParams(Element domElement, AVList params)
{
if (domElement == null)
{
String message = Logging.getMessage("nullValue.DocumentIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (params == null)
params = new AVListImpl();
XPath xpath = WWXML.makeXPath();
Object o = params.getValue(AVKey.TEXTURE_FORMAT);
if (o == null)
{
Boolean b = WWXML.getBoolean(domElement, "CompressTextures", xpath);
if (b != null && b)
params.setValue(AVKey.TEXTURE_FORMAT, "image/dds");
}
return params;
}
// ============== Image Composition ======================= //
// ============== Image Composition ======================= //
// ============== Image Composition ======================= //
public List getAvailableImageFormats()
{
return new ArrayList(this.supportedImageFormats);
}
public boolean isImageFormatAvailable(String imageFormat)
{
return imageFormat != null && this.supportedImageFormats.contains(imageFormat);
}
public String getDefaultImageFormat()
{
return this.supportedImageFormats.size() > 0 ? this.supportedImageFormats.get(0) : null;
}
protected void setAvailableImageFormats(String[] formats)
{
this.supportedImageFormats.clear();
if (formats != null)
this.supportedImageFormats.addAll(Arrays.asList(formats));
}
protected BufferedImage requestImage(TextureTile tile, String mimeType)
throws URISyntaxException, InterruptedIOException, MalformedURLException
{
String pathBase = tile.getPathBase();
String suffix = WWIO.makeSuffixForMimeType(mimeType);
String path = pathBase + suffix;
File f = new File(path);
URL url;
if (f.isAbsolute() && f.exists())
url = f.toURI().toURL();
else
url = this.getDataFileStore().findFile(path, false);
if (url == null) // image is not local
return null;
if (WWIO.isFileOutOfDate(url, tile.getLevel().getExpiryTime()))
{
// The file has expired. Delete it.
this.getDataFileStore().removeFile(url);
String message = Logging.getMessage("generic.DataFileExpired", url);
Logging.logger().fine(message);
}
else
{
try
{
File imageFile = new File(url.toURI());
BufferedImage image = ImageIO.read(imageFile);
if (image == null)
{
String message = Logging.getMessage("generic.ImageReadFailed", imageFile);
throw new RuntimeException(message);
}
this.levels.unmarkResourceAbsent(tile);
return image;
}
catch (InterruptedIOException e)
{
throw e;
}
catch (IOException e)
{
// Assume that something's wrong with the file and delete it.
this.getDataFileStore().removeFile(url);
this.levels.markResourceAbsent(tile);
String message = Logging.getMessage("generic.DeletedCorruptDataFile", url);
Logging.logger().info(message);
}
}
return null;
}
protected void downloadImage(final TextureTile tile, String mimeType, int timeout) throws Exception
{
if (this.getValue(AVKey.RETRIEVER_FACTORY_LOCAL) != null)
this.retrieveLocalImage(tile, mimeType, timeout);
else
// Assume it's remote.
this.retrieveRemoteImage(tile, mimeType, timeout);
}
protected void retrieveRemoteImage(final TextureTile tile, String mimeType, int timeout) throws Exception
{
// TODO: apply retriever-factory pattern for remote retrieval case.
final URL resourceURL = tile.getResourceURL(mimeType);
if (resourceURL == null)
return;
Retriever retriever;
String protocol = resourceURL.getProtocol();
if ("http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol))
{
retriever = new HTTPRetriever(resourceURL, new CompositionRetrievalPostProcessor(tile));
retriever.setValue(URLRetriever.EXTRACT_ZIP_ENTRY, "true"); // supports legacy layers
}
else
{
String message = Logging.getMessage("layers.TextureLayer.UnknownRetrievalProtocol", resourceURL);
throw new RuntimeException(message);
}
Logging.logger().log(java.util.logging.Level.FINE, "Retrieving " + resourceURL.toString());
retriever.setConnectTimeout(10000);
retriever.setReadTimeout(timeout);
retriever.call();
}
protected void retrieveLocalImage(TextureTile tile, String mimeType, int timeout) throws Exception
{
if (!WorldWind.getLocalRetrievalService().isAvailable())
return;
RetrieverFactory retrieverFactory = (RetrieverFactory) this.getValue(AVKey.RETRIEVER_FACTORY_LOCAL);
if (retrieverFactory == null)
return;
AVListImpl avList = new AVListImpl();
avList.setValue(AVKey.SECTOR, tile.getSector());
avList.setValue(AVKey.WIDTH, tile.getWidth());
avList.setValue(AVKey.HEIGHT, tile.getHeight());
avList.setValue(AVKey.FILE_NAME, tile.getPath());
avList.setValue(AVKey.IMAGE_FORMAT, mimeType);
Retriever retriever = retrieverFactory.createRetriever(avList, new CompositionRetrievalPostProcessor(tile));
Logging.logger().log(java.util.logging.Level.FINE, "Locally retrieving " + tile.getPath());
retriever.setReadTimeout(timeout);
retriever.call();
}
public int computeLevelForResolution(Sector sector, double resolution)
{
if (sector == null)
{
String message = Logging.getMessage("nullValue.SectorIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
// Find the first level exceeding the desired resolution
double texelSize;
Level targetLevel = this.levels.getLastLevel();
for (int i = 0; i < this.getLevels().getLastLevel().getLevelNumber(); i++)
{
if (this.levels.isLevelEmpty(i))
continue;
texelSize = this.levels.getLevel(i).getTexelSize();
if (texelSize > resolution)
continue;
targetLevel = this.levels.getLevel(i);
break;
}
// Choose the level closest to the resolution desired
if (targetLevel.getLevelNumber() != 0 && !this.levels.isLevelEmpty(targetLevel.getLevelNumber() - 1))
{
Level nextLowerLevel = this.levels.getLevel(targetLevel.getLevelNumber() - 1);
double dless = Math.abs(nextLowerLevel.getTexelSize() - resolution);
double dmore = Math.abs(targetLevel.getTexelSize() - resolution);
if (dless < dmore)
targetLevel = nextLowerLevel;
}
Logging.logger().fine(Logging.getMessage("layers.TiledImageLayer.LevelSelection",
targetLevel.getLevelNumber(), Double.toString(targetLevel.getTexelSize())));
return targetLevel.getLevelNumber();
}
/**
* Create an image for the portion of this layer lying within a specified sector. The image is created at a
* specified aspect ratio within a canvas of a specified size. This returns the specified image if this layer has no
* content in the specified sector.
*
* @param sector the sector of interest.
* @param canvasWidth the width of the canvas.
* @param canvasHeight the height of the canvas.
* @param aspectRatio the aspect ratio, width/height, of the window. If the aspect ratio is greater or equal to
* one, the full width of the canvas is used for the image; the height used is proportional to
* the inverse of the aspect ratio. If the aspect ratio is less than one, the full height of the
* canvas is used, and the width used is proportional to the aspect ratio.
* @param levelNumber the target level of the tiled image layer.
* @param mimeType the type of image to create, e.g., "png" and "jpg".
* @param abortOnError indicates whether to stop assembling the image if an error occurs. If false, processing
* continues until all portions of the layer that intersect the specified sector have been added
* to the image. Portions for which an error occurs will be blank.
* @param image if non-null, a {@link BufferedImage} in which to place the image. If null, a new buffered
* image is created. The image must be the width and height specified in the
* canvasWidth
and canvasHeight
arguments.
* @param timeout The amount of time to allow for reading the image from the server.
*
* @return image the assembled image, of size indicated by the canvasWidth
and
* canvasHeight
. If the specified aspect ratio is one, all pixels contain values. If the aspect
* ratio is greater than one, a full-width segment along the top of the canvas is blank. If the aspect ratio
* is less than one, a full-height segment along the right side of the canvase is blank. If the
* image
argument was non-null, that buffered image is returned.
*
* @throws IllegalArgumentException if sector
is null.
* @see ImageUtil#mergeImage(gov.nasa.worldwind.geom.Sector, gov.nasa.worldwind.geom.Sector, double,
* java.awt.image.BufferedImage, java.awt.image.BufferedImage) ;
*/
public BufferedImage composeImageForSector(Sector sector, int canvasWidth, int canvasHeight, double aspectRatio,
int levelNumber, String mimeType, boolean abortOnError, BufferedImage image, int timeout) throws Exception
{
if (sector == null)
{
String message = Logging.getMessage("nullValue.SectorIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (!this.levels.getSector().intersects(sector))
{
Logging.logger().severe(Logging.getMessage("generic.SectorRequestedOutsideCoverageArea", sector,
this.levels.getSector()));
return image;
}
Sector intersection = this.levels.getSector().intersection(sector);
if (levelNumber < 0)
{
levelNumber = this.levels.getLastLevel().getLevelNumber();
}
else if (levelNumber > this.levels.getLastLevel().getLevelNumber())
{
Logging.logger().warning(Logging.getMessage("generic.LevelRequestedGreaterThanMaxLevel",
levelNumber, this.levels.getLastLevel().getLevelNumber()));
levelNumber = this.levels.getLastLevel().getLevelNumber();
}
int numTiles = 0;
TextureTile[][] tiles = this.getTilesInSector(intersection, levelNumber);
for (TextureTile[] row : tiles)
{
numTiles += row.length;
}
if (tiles.length == 0 || tiles[0].length == 0)
{
Logging.logger().severe(Logging.getMessage("layers.TiledImageLayer.NoImagesAvailable"));
return image;
}
if (image == null)
image = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_RGB);
double tileCount = 0;
for (TextureTile[] row : tiles)
{
for (TextureTile tile : row)
{
if (tile == null)
continue;
BufferedImage tileImage;
try
{
tileImage = this.getImage(tile, mimeType, timeout);
Thread.sleep(1); // generates InterruptedException if thread has been interupted
if (tileImage != null)
ImageUtil.mergeImage(sector, tile.getSector(), aspectRatio, tileImage, image);
this.firePropertyChange(AVKey.PROGRESS, tileCount / numTiles, ++tileCount / numTiles);
}
catch (InterruptedException e)
{
throw e;
}
catch (InterruptedIOException e)
{
throw e;
}
catch (Exception e)
{
if (abortOnError)
throw e;
String message = Logging.getMessage("generic.ExceptionWhileRequestingImage", tile.getPath());
Logging.logger().log(java.util.logging.Level.WARNING, message, e);
}
}
}
return image;
}
public long countImagesInSector(Sector sector)
{
long count = 0;
for (int i = 0; i <= this.getLevels().getLastLevel().getLevelNumber(); i++)
{
if (!this.levels.isLevelEmpty(i))
count += countImagesInSector(sector, i);
}
return count;
}
public long countImagesInSector(Sector sector, int levelNumber)
{
if (sector == null)
{
String msg = Logging.getMessage("nullValue.SectorIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
Level targetLevel = this.levels.getLastLevel();
if (levelNumber >= 0)
{
for (int i = levelNumber; i < this.getLevels().getLastLevel().getLevelNumber(); i++)
{
if (this.levels.isLevelEmpty(i))
continue;
targetLevel = this.levels.getLevel(i);
break;
}
}
// Collect all the tiles intersecting the input sector.
LatLon delta = targetLevel.getTileDelta();
LatLon origin = this.levels.getTileOrigin();
final int nwRow = Tile.computeRow(delta.getLatitude(), sector.getMaxLatitude(), origin.getLatitude());
final int nwCol = Tile.computeColumn(delta.getLongitude(), sector.getMinLongitude(), origin.getLongitude());
final int seRow = Tile.computeRow(delta.getLatitude(), sector.getMinLatitude(), origin.getLatitude());
final int seCol = Tile.computeColumn(delta.getLongitude(), sector.getMaxLongitude(), origin.getLongitude());
long numRows = nwRow - seRow + 1;
long numCols = seCol - nwCol + 1;
return numRows * numCols;
}
public TextureTile[][] getTilesInSector(Sector sector, int levelNumber)
{
if (sector == null)
{
String msg = Logging.getMessage("nullValue.SectorIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
Level targetLevel = this.levels.getLastLevel();
if (levelNumber >= 0)
{
for (int i = levelNumber; i < this.getLevels().getLastLevel().getLevelNumber(); i++)
{
if (this.levels.isLevelEmpty(i))
continue;
targetLevel = this.levels.getLevel(i);
break;
}
}
// Collect all the tiles intersecting the input sector.
LatLon delta = targetLevel.getTileDelta();
LatLon origin = this.levels.getTileOrigin();
final int nwRow = Tile.computeRow(delta.getLatitude(), sector.getMaxLatitude(), origin.getLatitude());
final int nwCol = Tile.computeColumn(delta.getLongitude(), sector.getMinLongitude(), origin.getLongitude());
final int seRow = Tile.computeRow(delta.getLatitude(), sector.getMinLatitude(), origin.getLatitude());
final int seCol = Tile.computeColumn(delta.getLongitude(), sector.getMaxLongitude(), origin.getLongitude());
int numRows = nwRow - seRow + 1;
int numCols = seCol - nwCol + 1;
TextureTile[][] sectorTiles = new TextureTile[numRows][numCols];
for (int row = nwRow; row >= seRow; row--)
{
for (int col = nwCol; col <= seCol; col++)
{
TileKey key = new TileKey(targetLevel.getLevelNumber(), row, col, targetLevel.getCacheName());
Sector tileSector = this.levels.computeSectorForKey(key);
sectorTiles[nwRow - row][col - nwCol] = new TextureTile(tileSector, targetLevel, row, col);
}
}
return sectorTiles;
}
protected BufferedImage getImage(TextureTile tile, String mimeType, int timeout) throws Exception
{
// Read the image from disk.
BufferedImage image = this.requestImage(tile, mimeType);
Thread.sleep(1); // generates InterruptedException if thread has been interrupted
if (image != null)
return image;
// Retrieve it from the net since it's not on disk.
this.downloadImage(tile, mimeType, timeout);
// Try to read from disk again after retrieving it from the net.
image = this.requestImage(tile, mimeType);
Thread.sleep(1); // generates InterruptedException if thread has been interupted
if (image == null)
{
String message =
Logging.getMessage("layers.TiledImageLayer.ImageUnavailable", tile.getPath());
throw new RuntimeException(message);
}
return image;
}
protected class CompositionRetrievalPostProcessor extends AbstractRetrievalPostProcessor
{
protected TextureTile tile;
public CompositionRetrievalPostProcessor(TextureTile tile)
{
this.tile = tile;
}
protected File doGetOutputFile()
{
String suffix = WWIO.makeSuffixForMimeType(this.getRetriever().getContentType());
if (suffix == null)
{
Logging.logger().severe(
Logging.getMessage("generic.UnknownContentType", this.getRetriever().getContentType()));
return null;
}
String path = this.tile.getPathBase();
path += suffix;
File f = new File(path);
final File outFile = f.isAbsolute() ? f : getDataFileStore().newFile(path);
if (outFile == null)
return null;
return outFile;
}
@Override
protected boolean isDeleteOnExit(File outFile)
{
return outFile.getPath().contains(WWIO.DELETE_ON_EXIT_PREFIX);
}
@Override
protected boolean overwriteExistingFile()
{
return true;
}
protected void markResourceAbsent()
{
TiledImageLayer.this.levels.markResourceAbsent(tile);
}
protected void handleUnsuccessfulRetrieval()
{
// Don't mark the tile as absent because the caller may want to try again.
}
}
}