/*
Copyright (C) 2001, 2006 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.layers;
import com.sun.opengl.util.texture.*;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.cache.*;
import gov.nasa.worldwind.formats.dds.DDSConverter;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.retrieve.*;
import gov.nasa.worldwind.util.*;
import java.io.File;
import java.net.*;
import java.nio.ByteBuffer;
/**
* @author tag
* @version $Id: BasicTiledImageLayer.java 5051 2008-04-14 04:51:50Z tgaskins $
*/
public class BasicTiledImageLayer extends TiledImageLayer
{
private final Object fileLock = new Object();
public BasicTiledImageLayer(LevelSet levelSet)
{
super(levelSet);
if (!WorldWind.getMemoryCacheSet().containsCache(TextureTile.class.getName()))
{
long size = Configuration.getLongValue(AVKey.TEXTURE_IMAGE_CACHE_SIZE, 3000000L);
MemoryCache cache = new BasicMemoryCache((long) (0.85 * size), size);
cache.setName("Texture Tiles");
WorldWind.getMemoryCacheSet().addCache(TextureTile.class.getName(), cache);
}
}
public BasicTiledImageLayer(AVList params)
{
this(new LevelSet(params));
this.setValue(AVKey.CONSTRUCTION_PARAMETERS, params);
}
protected void forceTextureLoad(TextureTile tile)
{
final URL textureURL = WorldWind.getDataFileCache().findFile(tile.getPath(), true);
if (textureURL != null && !this.isTextureExpired(tile, textureURL))
{
this.loadTexture(tile, textureURL);
}
}
protected void requestTexture(DrawContext dc, TextureTile tile)
{
Vec4 centroid = tile.getCentroidPoint(dc.getGlobe());
if (this.getReferencePoint() != null)
tile.setPriority(centroid.distanceTo3(this.getReferencePoint()));
RequestTask task = new RequestTask(tile, this);
this.getRequestQ().add(task);
}
private static class RequestTask implements Runnable, Comparable<RequestTask>
{
private final BasicTiledImageLayer layer;
private final TextureTile tile;
private RequestTask(TextureTile tile, BasicTiledImageLayer layer)
{
this.layer = layer;
this.tile = tile;
}
public void run()
{
// TODO: check to ensure load is still needed
final java.net.URL textureURL = WorldWind.getDataFileCache().findFile(tile.getPath(), false);
if (textureURL != null && !this.layer.isTextureExpired(tile, textureURL))
{
if (this.layer.loadTexture(tile, textureURL))
{
layer.getLevels().unmarkResourceAbsent(tile);
this.layer.firePropertyChange(AVKey.LAYER, null, this);
return;
}
else
{
// Assume that something's wrong with the file and delete it.
gov.nasa.worldwind.WorldWind.getDataFileCache().removeFile(textureURL);
layer.getLevels().markResourceAbsent(tile);
String message = Logging.getMessage("generic.DeletedCorruptDataFile", textureURL);
Logging.logger().info(message);
}
}
this.layer.downloadTexture(this.tile);
}
/**
* @param that the task to compare
* @return -1 if <code>this</code> less than <code>that</code>, 1 if greater than, 0 if equal
* @throws IllegalArgumentException if <code>that</code> is null
*/
public int compareTo(RequestTask that)
{
if (that == null)
{
String msg = Logging.getMessage("nullValue.RequestTaskIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
return this.tile.getPriority() == that.tile.getPriority() ? 0 :
this.tile.getPriority() < that.tile.getPriority() ? -1 : 1;
}
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final RequestTask that = (RequestTask) o;
// Don't include layer in comparison so that requests are shared among layers
return !(tile != null ? !tile.equals(that.tile) : that.tile != null);
}
public int hashCode()
{
return (tile != null ? tile.hashCode() : 0);
}
public String toString()
{
return this.tile.toString();
}
}
private boolean isTextureExpired(TextureTile tile, java.net.URL textureURL)
{
if (!WWIO.isFileOutOfDate(textureURL, tile.getLevel().getExpiryTime()))
return false;
// The file has expired. Delete it.
gov.nasa.worldwind.WorldWind.getDataFileCache().removeFile(textureURL);
String message = Logging.getMessage("generic.DataFileExpired", textureURL);
Logging.logger().fine(message);
return true;
}
private boolean loadTexture(TextureTile tile, java.net.URL textureURL)
{
TextureData textureData;
synchronized (this.fileLock)
{
textureData = readTexture(textureURL, this.isUseMipMaps());
}
if (textureData == null)
return false;
tile.setTextureData(textureData);
if (tile.getLevelNumber() != 0 || !this.isRetainLevelZeroTiles())
this.addTileToCache(tile);
return true;
}
private static TextureData readTexture(java.net.URL url, boolean useMipMaps)
{
try
{
return TextureIO.newTextureData(url, useMipMaps, null);
}
catch (Exception e)
{
Logging.logger().log(
java.util.logging.Level.SEVERE, "layers.TextureLayer.ExceptionAttemptingToReadTextureFile", e);
return null;
}
}
private void addTileToCache(TextureTile tile)
{
WorldWind.getMemoryCache(TextureTile.class.getName()).add(tile.getTileKey(), tile);
}
protected void downloadTexture(final TextureTile tile)
{
if (!WorldWind.getRetrievalService().isAvailable())
return;
java.net.URL url;
try
{
url = tile.getResourceURL();
if (url == null)
return;
if (WorldWind.getNetworkStatus().isHostUnavailable(url))
return;
}
catch (java.net.MalformedURLException e)
{
Logging.logger().log(java.util.logging.Level.SEVERE,
Logging.getMessage("layers.TextureLayer.ExceptionCreatingTextureUrl", tile), e);
return;
}
Retriever retriever;
if ("http".equalsIgnoreCase(url.getProtocol()))
{
retriever = new HTTPRetriever(url, new DownloadPostProcessor(tile, this));
}
else
{
Logging.logger().severe(
Logging.getMessage("layers.TextureLayer.UnknownRetrievalProtocol", url.toString()));
return;
}
// Apply any overridden timeouts.
Integer cto = AVListImpl.getIntegerValue(this, AVKey.URL_CONNECT_TIMEOUT);
if (cto != null && cto > 0)
retriever.setConnectTimeout(cto);
Integer cro = AVListImpl.getIntegerValue(this, AVKey.URL_READ_TIMEOUT);
if (cro != null && cro > 0)
retriever.setReadTimeout(cro);
Integer srl = AVListImpl.getIntegerValue(this, AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT);
if (srl != null && srl > 0)
retriever.setStaleRequestLimit(srl);
WorldWind.getRetrievalService().runRetriever(retriever, tile.getPriority());
}
private void saveBuffer(java.nio.ByteBuffer buffer, java.io.File outFile) throws java.io.IOException
{
synchronized (this.fileLock) // sychronized with read of file in RequestTask.run()
{
WWIO.saveBuffer(buffer, outFile);
}
}
private static class DownloadPostProcessor implements RetrievalPostProcessor
{
// TODO: Rewrite this inner class, factoring out the generic parts.
private final TextureTile tile;
private final BasicTiledImageLayer layer;
public DownloadPostProcessor(TextureTile tile, BasicTiledImageLayer layer)
{
this.tile = tile;
this.layer = layer;
}
public ByteBuffer run(Retriever retriever)
{
if (retriever == null)
{
String msg = Logging.getMessage("nullValue.RetrieverIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
try
{
if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
return null;
URLRetriever r = (URLRetriever) retriever;
ByteBuffer buffer = r.getBuffer();
if (retriever instanceof HTTPRetriever)
{
HTTPRetriever htr = (HTTPRetriever) retriever;
if (htr.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT)
{
// Mark tile as missing to avoid excessive attempts
this.layer.getLevels().markResourceAbsent(this.tile);
return null;
}
else if (htr.getResponseCode() != HttpURLConnection.HTTP_OK)
{
// Also mark tile as missing, but for an unknown reason.
this.layer.getLevels().markResourceAbsent(this.tile);
return null;
}
}
final File outFile = WorldWind.getDataFileCache().newFile(this.tile.getPath());
if (outFile == null)
return null;
if (outFile.exists())
return buffer;
// TODO: Better, more generic and flexible handling of file-format type
if (buffer != null)
{
String contentType = r.getContentType();
if (contentType == null)
{
// TODO: logger message
return null;
}
if (contentType.contains("xml") || contentType.contains("html") || contentType.contains("text"))
{
this.layer.getLevels().markResourceAbsent(this.tile);
StringBuffer sb = new StringBuffer();
while (buffer.hasRemaining())
{
sb.append((char) buffer.get());
}
// TODO: parse out the message if the content is xml or html.
Logging.logger().severe(sb.toString());
return null;
}
else if (contentType.contains("dds"))
{
this.layer.saveBuffer(buffer, outFile);
}
else if (contentType.contains("zip"))
{
// Assume it's zipped DDS, which the retriever would have unzipped into the buffer.
this.layer.saveBuffer(buffer, outFile);
}
else if (outFile.getName().endsWith(".dds"))
{
// Convert to DDS and save the result.
buffer = DDSConverter.convertToDDS(buffer, contentType);
if (buffer != null)
this.layer.saveBuffer(buffer, outFile);
}
else if (contentType.contains("image"))
{
// Just save whatever it is to the cache.
this.layer.saveBuffer(buffer, outFile);
}
if (buffer != null)
{
this.layer.firePropertyChange(AVKey.LAYER, null, this);
}
return buffer;
}
}
catch (java.io.IOException e)
{
this.layer.getLevels().markResourceAbsent(this.tile);
Logging.logger().log(java.util.logging.Level.SEVERE,
Logging.getMessage("layers.TextureLayer.ExceptionSavingRetrievedTextureFile", tile.getPath()), e);
}
return null;
}
}
}