/*******************************************************************************
* Copyright 2012 Geoscience Australia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package au.gov.ga.earthsci.worldwind.common.layers.curtain;
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.FileStore;
import gov.nasa.worldwind.formats.dds.DDSCompressor;
import gov.nasa.worldwind.formats.dds.DXTCompressionAttributes;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.render.ScreenCredit;
import gov.nasa.worldwind.retrieve.AbstractRetrievalPostProcessor;
import gov.nasa.worldwind.retrieve.Retriever;
import gov.nasa.worldwind.retrieve.URLRetriever;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.WWIO;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import au.gov.ga.earthsci.worldwind.common.layers.tiled.image.delegate.FileLockSharer;
import au.gov.ga.earthsci.worldwind.common.util.AVKeyMore;
import com.jogamp.opengl.util.texture.TextureData;
import com.jogamp.opengl.util.texture.TextureIO;
import javax.media.opengl.GLProfile;
/**
* Basic implementation subclass of the abstract {@link TiledCurtainLayer}.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public class BasicTiledCurtainLayer extends TiledCurtainLayer
{
private final Object fileLock;
public BasicTiledCurtainLayer(CurtainLevelSet levelSet)
{
super(levelSet);
fileLock = FileLockSharer.getLock(getLevels().getFirstLevel().getCacheName());
}
public BasicTiledCurtainLayer(AVList params)
{
this(new CurtainLevelSet(params));
String s = params.getStringValue(AVKey.DISPLAY_NAME);
if (s != null)
this.setName(s);
String[] strings = (String[]) params.getValue(AVKey.AVAILABLE_IMAGE_FORMATS);
if (strings != null && strings.length > 0)
this.setAvailableImageFormats(strings);
s = params.getStringValue(AVKey.TEXTURE_FORMAT);
if (s != null)
this.setTextureFormat(s);
Double d = (Double) params.getValue(AVKey.OPACITY);
if (d != null)
this.setOpacity(d);
d = (Double) params.getValue(AVKey.MAX_ACTIVE_ALTITUDE);
if (d != null)
this.setMaxActiveAltitude(d);
d = (Double) params.getValue(AVKey.MIN_ACTIVE_ALTITUDE);
if (d != null)
this.setMinActiveAltitude(d);
d = (Double) params.getValue(AVKey.MAP_SCALE);
if (d != null)
this.setValue(AVKey.MAP_SCALE, d);
d = (Double) params.getValue(AVKey.DETAIL_HINT);
if (d != null)
this.setDetailHint(d);
Boolean b = (Boolean) params.getValue(AVKey.FORCE_LEVEL_ZERO_LOADS);
if (b != null)
this.setForceLevelZeroLoads(b);
b = (Boolean) params.getValue(AVKey.RETAIN_LEVEL_ZERO_TILES);
if (b != null)
this.setRetainLevelZeroTiles(b);
b = (Boolean) params.getValue(AVKey.NETWORK_RETRIEVAL_ENABLED);
if (b != null)
this.setNetworkRetrievalEnabled(b);
b = (Boolean) params.getValue(AVKey.USE_MIP_MAPS);
if (b != null)
this.setUseMipMaps(b);
b = (Boolean) params.getValue(AVKey.USE_TRANSPARENT_TEXTURES);
if (b != null)
this.setUseTransparentTextures(b);
Object o = params.getValue(AVKey.URL_CONNECT_TIMEOUT);
if (o != null)
this.setValue(AVKey.URL_CONNECT_TIMEOUT, o);
o = params.getValue(AVKey.URL_READ_TIMEOUT);
if (o != null)
this.setValue(AVKey.URL_READ_TIMEOUT, o);
o = params.getValue(AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT);
if (o != null)
this.setValue(AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT, o);
ScreenCredit sc = (ScreenCredit) params.getValue(AVKey.SCREEN_CREDIT);
if (sc != null)
this.setScreenCredit(sc);
if (params.getValue(AVKey.TRANSPARENCY_COLORS) != null)
this.setValue(AVKey.TRANSPARENCY_COLORS, params.getValue(AVKey.TRANSPARENCY_COLORS));
//curtain specific keys
b = (Boolean) params.getValue(AVKeyMore.FOLLOW_TERRAIN);
if (b != null)
this.setFollowTerrain(b);
d = (Double) params.getValue(AVKeyMore.CURTAIN_TOP);
if (d != null)
this.setCurtainTop(d);
d = (Double) params.getValue(AVKeyMore.CURTAIN_BOTTOM);
if (d != null)
this.setCurtainBottom(d);
Integer i = (Integer) params.getValue(AVKeyMore.SUBSEGMENTS);
if (i != null)
this.setSubsegments(i);
Path path = (Path) params.getValue(AVKeyMore.PATH);
if (path != null)
this.setPath(path);
this.setValue(AVKey.CONSTRUCTION_PARAMETERS, params.copy());
// If any resources should be retrieved for this Layer, start a task to retrieve those resources, and initialize
// this Layer once those resources are retrieved.
// if (this.isRetrieveResources())
// {
// this.startResourceRetrieval();
// }
}
public BasicTiledCurtainLayer(Document dom, AVList params)
{
this(dom.getDocumentElement(), params);
}
public BasicTiledCurtainLayer(Element domElement, AVList params)
{
this(getParamsFromDocument(domElement, params));
}
protected static AVList getParamsFromDocument(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();
getTiledCurtainLayerConfigParams(domElement, params);
setFallbacks(params);
return params;
}
protected static void setFallbacks(AVList params)
{
if (params.getValue(AVKey.LEVEL_ZERO_TILE_DELTA) == null)
{
Angle delta = Angle.fromDegrees(36);
params.setValue(AVKey.LEVEL_ZERO_TILE_DELTA, new LatLon(delta, delta));
}
if (params.getValue(AVKey.TILE_WIDTH) == null)
params.setValue(AVKey.TILE_WIDTH, 512);
if (params.getValue(AVKey.TILE_HEIGHT) == null)
params.setValue(AVKey.TILE_HEIGHT, 512);
if (params.getValue(AVKey.FORMAT_SUFFIX) == null)
params.setValue(AVKey.FORMAT_SUFFIX, ".dds");
if (params.getValue(AVKey.NUM_LEVELS) == null)
params.setValue(AVKey.NUM_LEVELS, 19); // approximately 0.1 meters per pixel
if (params.getValue(AVKey.NUM_EMPTY_LEVELS) == null)
params.setValue(AVKey.NUM_EMPTY_LEVELS, 0);
}
@Override
protected void forceTextureLoad(CurtainTextureTile tile)
{
final URL textureURL = this.getDataFileStore().findFile(tile.getPath(), true);
if (textureURL != null && !this.isTextureFileExpired(tile, textureURL, this.getDataFileStore()))
{
this.loadTexture(tile, textureURL);
}
}
@Override
protected void requestTexture(DrawContext dc, CurtainTextureTile tile)
{
Vec4 centroid =
getPath().getSegmentCenterPoint(dc, tile.getSegment(), getCurtainTop(), getCurtainBottom(),
isFollowTerrain());
Vec4 referencePoint = this.getReferencePoint(dc);
if (referencePoint != null)
tile.setPriority(centroid.distanceTo3(referencePoint));
RequestTask task = new RequestTask(tile, this);
this.getRequestQ().add(task);
}
protected boolean isTextureFileExpired(CurtainTextureTile tile, URL textureURL, FileStore fileStore)
{
if (!WWIO.isFileOutOfDate(textureURL, tile.getLevel().getExpiryTime()))
return false;
// The file has expired. Delete it.
fileStore.removeFile(textureURL);
String message = Logging.getMessage("generic.DataFileExpired", textureURL);
Logging.logger().fine(message);
return true;
}
private boolean loadTexture(CurtainTextureTile tile, java.net.URL textureURL)
{
TextureData textureData;
synchronized (this.fileLock)
{
textureData = readTexture(textureURL, this.getTextureFormat(), this.isUseMipMaps());
}
if (textureData == null)
return false;
tile.setTextureData(textureData);
if (tile.getLevelNumber() != 0 || !this.isRetainLevelZeroTiles())
this.addTileToCache(tile);
return true;
}
private void addTileToCache(CurtainTextureTile tile)
{
CurtainTextureTile.getMemoryCache().add(tile.getTileKey(), tile);
}
private static TextureData readTexture(java.net.URL url, String textureFormat, boolean useMipMaps)
{
try
{
// If the caller has enabled texture compression, and the texture data is not a DDS file, then use read the
// texture data and convert it to DDS.
if ("image/dds".equalsIgnoreCase(textureFormat) && !url.toString().toLowerCase().endsWith("dds"))
{
// Configure a DDS compressor to generate mipmaps based according to the 'useMipMaps' parameter, and
// convert the image URL to a compressed DDS format.
DXTCompressionAttributes attributes = DDSCompressor.getDefaultCompressionAttributes();
attributes.setBuildMipmaps(useMipMaps);
ByteBuffer buffer = DDSCompressor.compressImageURL(url, attributes);
return TextureIO.newTextureData(GLProfile.get(GLProfile.GL2), WWIO.getInputStreamFromByteBuffer(buffer), useMipMaps, null);
}
// If the caller has disabled texture compression, or if the texture data is already a DDS file, then read
// the texture data without converting it.
else
{
return TextureIO.newTextureData(GLProfile.get(GLProfile.GL2), url, useMipMaps, null);
}
}
catch (Exception e)
{
String msg = Logging.getMessage("layers.TextureLayer.ExceptionAttemptingToReadTextureFile", url);
Logging.logger().log(java.util.logging.Level.SEVERE, msg, e);
return null;
}
}
private static class RequestTask implements Runnable, Comparable<RequestTask>
{
private final BasicTiledCurtainLayer layer;
private final CurtainTextureTile tile;
private RequestTask(CurtainTextureTile tile, BasicTiledCurtainLayer layer)
{
this.layer = layer;
this.tile = tile;
}
@Override
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.isTextureFileExpired(tile, textureURL, this.layer.getDataFileStore()))
{
if (this.layer.loadTexture(tile, textureURL))
{
layer.getLevels().unmarkResourceAbsent(this.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);
String message = Logging.getMessage("generic.DeletedCorruptDataFile", textureURL);
Logging.logger().info(message);
}
}
this.layer.retrieveTexture(this.tile, new DownloadPostProcessor(this.tile, this.layer));
}
/**
* @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
*/
@Override
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;
}
@Override
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);
}
@Override
public int hashCode()
{
return (tile != null ? tile.hashCode() : 0);
}
@Override
public String toString()
{
return this.tile.toString();
}
}
protected void retrieveTexture(CurtainTextureTile tile, DownloadPostProcessor postProcessor)
{
//TODO add support for a Local Curtain RetrieverFactory
/*if (this.getValue(AVKey.RETRIEVER_FACTORY_LOCAL) != null)
this.retrieveLocalTexture(tile, postProcessor);
else*/
// Assume it's remote, which handles the legacy cases.
this.retrieveRemoteTexture(tile, postProcessor);
}
/*protected void retrieveLocalTexture(CurtainTextureTile tile, DownloadPostProcessor postProcessor)
{
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());
Retriever retriever = retrieverFactory.createRetriever(avList, postProcessor);
WorldWind.getLocalRetrievalService().runRetriever(retriever, tile.getPriority());
}*/
protected void retrieveRemoteTexture(final CurtainTextureTile tile, DownloadPostProcessor postProcessor)
{
if (!this.isNetworkRetrievalEnabled())
{
this.getLevels().markResourceAbsent(tile);
return;
}
if (!WorldWind.getRetrievalService().isAvailable())
return;
java.net.URL url;
try
{
url = tile.getResourceURL();
if (url == null)
return;
if (WorldWind.getNetworkStatus().isHostUnavailable(url))
{
this.getLevels().markResourceAbsent(tile);
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 (postProcessor == null)
postProcessor = new DownloadPostProcessor(tile, this);
retriever = URLRetriever.createRetriever(url, postProcessor);
if (retriever == null)
{
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());
}
protected static class DownloadPostProcessor extends AbstractRetrievalPostProcessor
{
protected final CurtainTextureTile tile;
protected final BasicTiledCurtainLayer layer;
protected final FileStore fileStore;
public DownloadPostProcessor(CurtainTextureTile tile, BasicTiledCurtainLayer layer)
{
this(tile, layer, null);
}
public DownloadPostProcessor(CurtainTextureTile tile, BasicTiledCurtainLayer layer, FileStore fileStore)
{
//noinspection RedundantCast
super((AVList) layer);
this.tile = tile;
this.layer = layer;
this.fileStore = fileStore;
}
protected FileStore getFileStore()
{
return this.fileStore != null ? this.fileStore : this.layer.getDataFileStore();
}
@Override
protected void markResourceAbsent()
{
this.layer.getLevels().markResourceAbsent(this.tile);
}
@Override
protected Object getFileLock()
{
return this.layer.fileLock;
}
@Override
protected File doGetOutputFile()
{
return this.getFileStore().newFile(this.tile.getPath());
}
@Override
protected ByteBuffer handleSuccessfulRetrieval()
{
ByteBuffer buffer = super.handleSuccessfulRetrieval();
if (buffer != null)
{
// We've successfully cached data. Check if there's a configuration file for this layer, create one
// if there's not.
//this.layer.writeConfigurationFile(this.getFileStore()); //TODO implement
// Fire a property change to denote that the layer's backing data has changed.
this.layer.firePropertyChange(AVKey.LAYER, null, this);
}
return buffer;
}
@Override
protected ByteBuffer handleTextContent() throws IOException
{
this.markResourceAbsent();
return super.handleTextContent();
}
}
}