/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package org.jgrasstools.nww.layers.defaults.raster;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.ByteBuffer;
import javax.imageio.ImageIO;
import com.jogamp.opengl.util.texture.TextureData;
import gov.nasa.worldwind.Configuration;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.avlist.AVList;
import gov.nasa.worldwind.avlist.AVListImpl;
import gov.nasa.worldwind.cache.BasicMemoryCache;
import gov.nasa.worldwind.cache.MemoryCache;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.layers.mercator.MercatorSector;
import gov.nasa.worldwind.layers.mercator.MercatorTextureTile;
import gov.nasa.worldwind.layers.mercator.MercatorTiledImageLayer;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.retrieve.HTTPRetriever;
import gov.nasa.worldwind.retrieve.RetrievalPostProcessor;
import gov.nasa.worldwind.retrieve.Retriever;
import gov.nasa.worldwind.retrieve.URLRetriever;
import gov.nasa.worldwind.util.LevelSet;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.OGLUtil;
import gov.nasa.worldwind.util.WWIO;
/**
* BasicTiledImageLayer modified 2009-02-03 to add support for Mercator projections.
*
* @author tag
* @version $Id: BasicMercatorTiledImageLayer.java 1171 2013-02-11 21:45:02Z dcollins $
*/
public class BasicMercatorTiledImageLayer extends MercatorTiledImageLayer {
private final Object fileLock = new Object();
public BasicMercatorTiledImageLayer( LevelSet levelSet ) {
super(levelSet);
if (!WorldWind.getMemoryCacheSet().containsCache(MercatorTextureTile.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(MercatorTextureTile.class.getName(), cache);
}
}
public BasicMercatorTiledImageLayer( AVList params ) {
this(new LevelSet(params));
this.setValue(AVKey.CONSTRUCTION_PARAMETERS, params);
}
protected void forceTextureLoad( MercatorTextureTile tile ) {
final URL textureURL = this.getDataFileStore().findFile(tile.getPath(), true);
if (textureURL != null && !this.isTextureExpired(tile, textureURL)) {
this.loadTexture(tile, textureURL);
}
}
protected void requestTexture( DrawContext dc, MercatorTextureTile 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 BasicMercatorTiledImageLayer layer;
private final MercatorTextureTile tile;
private RequestTask( MercatorTextureTile tile, BasicMercatorTiledImageLayer layer ) {
this.layer = layer;
this.tile = tile;
}
public void run() {
// TODO: check to ensure load is still needed
final java.net.URL textureURL = this.layer.getDataFileStore().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.
this.layer.getDataFileStore().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( MercatorTextureTile tile, java.net.URL textureURL ) {
if (!WWIO.isFileOutOfDate(textureURL, tile.getLevel().getExpiryTime()))
return false;
// The file has expired. Delete it.
this.getDataFileStore().removeFile(textureURL);
String message = Logging.getMessage("generic.DataFileExpired", textureURL);
Logging.logger().fine(message);
return true;
}
private boolean loadTexture( MercatorTextureTile 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 OGLUtil.newTextureData(Configuration.getMaxCompatibleGLProfile(), url, useMipMaps);
} catch (Exception e) {
// String msg = Logging.getMessage("layers.TextureLayer.ExceptionAttemptingToReadTextureFile", url.toString());
// Logging.logger().log(java.util.logging.Level.SEVERE, msg, e);
return null;
}
}
private void addTileToCache( MercatorTextureTile tile ) {
WorldWind.getMemoryCache(MercatorTextureTile.class.getName()).add(tile.getTileKey(), tile);
}
protected void downloadTexture( final MercatorTextureTile 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));
retriever.setValue(URLRetriever.EXTRACT_ZIP_ENTRY, "true"); // supports legacy layers
} else if ("file".equalsIgnoreCase(url.getProtocol())) {
retriever = new FileRetriever(url, new DownloadPostProcessor(tile, this));
retriever.setValue(URLRetriever.EXTRACT_ZIP_ENTRY, "true"); // supports legacy layers
} 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) // synchronized 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 MercatorTextureTile tile;
private final BasicMercatorTiledImageLayer layer;
public DownloadPostProcessor( MercatorTextureTile tile, BasicMercatorTiledImageLayer 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 = this.layer.getDataFileStore().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")) {
BufferedImage image = this.layer.convertBufferToImage(buffer);
if (image != null) {
image = this.layer.modifyImage(image);
if (this.layer.isTileValid(image)) {
if (!this.layer.transformAndSave(image, tile.getMercatorSector(), outFile))
image = null;
} else {
this.layer.getLevels().markResourceAbsent(this.tile);
return null;
}
}
if (image == null) {
// 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;
}
}
protected boolean isTileValid( BufferedImage image ) {
// override in subclass to check image tile
// if false is returned, then tile is marked absent
return true;
}
protected BufferedImage modifyImage( BufferedImage image ) {
// override in subclass to modify image tile
return image;
}
private BufferedImage convertBufferToImage( ByteBuffer buffer ) {
try {
InputStream is = new ByteArrayInputStream(buffer.array());
return ImageIO.read(is);
} catch (Exception e) {
return null;
}
}
private boolean transformAndSave( BufferedImage image, MercatorSector sector, File outFile ) {
try {
image = transform(image, sector);
String extension = outFile.getName().substring(outFile.getName().lastIndexOf('.') + 1);
synchronized (this.fileLock) // synchronized with read of file in RequestTask.run()
{
return ImageIO.write(image, extension, outFile);
}
} catch (IOException e) {
return false;
}
}
private BufferedImage transform( BufferedImage image, MercatorSector sector ) {
int type = image.getType();
if (type == 0)
type = BufferedImage.TYPE_INT_RGB;
BufferedImage trans = new BufferedImage(image.getWidth(), image.getHeight(), type);
double miny = sector.getMinLatPercent();
double maxy = sector.getMaxLatPercent();
for( int y = 0; y < image.getHeight(); y++ ) {
double sy = 1.0 - y / (double) (image.getHeight() - 1);
Angle lat = Angle.fromRadians(sy * sector.getDeltaLatRadians() + sector.getMinLatitude().radians);
double dy = 1.0 - (MercatorSector.gudermannianInverse(lat) - miny) / (maxy - miny);
dy = Math.max(0.0, Math.min(1.0, dy));
int iy = (int) (dy * (image.getHeight() - 1));
for( int x = 0; x < image.getWidth(); x++ ) {
trans.setRGB(x, y, image.getRGB(x, iy));
}
}
return trans;
}
}