/******************************************************************************* * 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.delegate; 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.Vec4; import gov.nasa.worldwind.globes.Globe; import gov.nasa.worldwind.layers.TextureTile; import gov.nasa.worldwind.render.DrawContext; import gov.nasa.worldwind.retrieve.RetrievalPostProcessor; import gov.nasa.worldwind.retrieve.Retriever; import gov.nasa.worldwind.util.ImageUtil; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.WWIO; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.URL; import java.nio.ByteBuffer; import javax.imageio.ImageIO; import org.w3c.dom.Element; import au.gov.ga.earthsci.worldwind.common.layers.curtain.BasicTiledCurtainLayer; import au.gov.ga.earthsci.worldwind.common.layers.curtain.CurtainLevel; import au.gov.ga.earthsci.worldwind.common.layers.curtain.CurtainTextureTile; import au.gov.ga.earthsci.worldwind.common.layers.curtain.Segment; import au.gov.ga.earthsci.worldwind.common.layers.delegate.IDelegatorLayer; import au.gov.ga.earthsci.worldwind.common.layers.delegate.IDelegatorTile; import au.gov.ga.earthsci.worldwind.common.layers.delegate.ITileRequesterDelegate; import au.gov.ga.earthsci.worldwind.common.layers.tiled.image.delegate.FileLockSharer; import au.gov.ga.earthsci.worldwind.common.util.AVKeyMore; import au.gov.ga.earthsci.worldwind.common.util.DDSUncompressor; import com.jogamp.opengl.util.texture.TextureData; import com.jogamp.opengl.util.texture.TextureIO; import javax.media.opengl.GLProfile; import com.jogamp.opengl.util.texture.awt.AWTTextureIO; /** * {@link BasicTiledCurtainLayer} subclass that uses delegates provided by a * {@link CurtainDelegateKit} for the retrieval, transformation, and creation of * curtain data. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class DelegatorTiledCurtainLayer extends BasicTiledCurtainLayer implements IDelegatorLayer<DelegatorCurtainTextureTile> { protected final Object fileLock; protected final URL context; protected final CurtainDelegateKit delegateKit; protected boolean prerendered; protected Globe currentGlobe; public DelegatorTiledCurtainLayer(AVList params) { super(params); Object o = params.getValue(AVKeyMore.DELEGATE_KIT); if (o != null && o instanceof CurtainDelegateKit) delegateKit = (CurtainDelegateKit) o; else delegateKit = new CurtainDelegateKit(); o = params.getValue(AVKeyMore.CONTEXT_URL); if (o != null && o instanceof URL) context = (URL) o; else context = null; //Share the filelock with other layers with the same cache name. This allows //multiple layers to save and load from the same cache location. fileLock = FileLockSharer.getLock(getLevels().getFirstLevel().getCacheName()); } public DelegatorTiledCurtainLayer(Element domElement, AVList params) { this(getParamsFromDocument(domElement, params)); } protected static AVList getParamsFromDocument(Element domElement, AVList params) { /*String serviceName = XMLUtil.getText(domElement, "Service/@serviceName"); if (OGCConstants.WMS_SERVICE_NAME.equals(serviceName)) { //if serviceName defines a WMS sources, then use the WMSTiledImageLayer to initialise params params = WMSTIL.wmsGetParamsFromDocument(domElement, params); } else*/ { params = BasicTiledCurtainLayer.getParamsFromDocument(domElement, params); } //create the delegate kit from the XML element CurtainDelegateKit delegateKit = new CurtainDelegateKit().createFromXML(domElement, params); params.setValue(AVKeyMore.DELEGATE_KIT, delegateKit); return params; } @Override public void render(DrawContext dc) { if (dc != null) { currentGlobe = dc.getGlobe(); } super.render(dc); } @Override protected void setBlendingFunction(DrawContext dc) { super.setBlendingFunction(dc); //call the delegate's preRender function here, after the blending function //is set up, so that any delegates can modify the blending if they wish delegateKit.preRender(dc); prerendered = true; } @Override protected void draw(DrawContext dc) { prerendered = false; super.draw(dc); if(prerendered) { delegateKit.postRender(dc); } } @Override public boolean isUseTransparentTextures() { //ensure that setBlendingFunction is called, so that the delegate's preRender //function is called return true; } @Override public boolean isTextureFileExpired(DelegatorCurtainTextureTile tile, URL textureURL, FileStore fileStore) { return super.isTextureFileExpired(tile, textureURL, fileStore); } @Override protected void forceTextureLoad(CurtainTextureTile tile) { validateTileClass(tile); //pass request to delegate delegateKit.forceTextureLoad((DelegatorCurtainTextureTile) tile, this); } @Override protected void requestTexture(DrawContext dc, CurtainTextureTile tile) { validateTileClass(tile); currentGlobe = dc.getGlobe(); Vec4 centroid = getPath().getSegmentCenterPoint(dc, tile.getSegment(), getCurtainTop(), getCurtainBottom(), isFollowTerrain()); Vec4 referencePoint = this.getReferencePoint(dc); if (referencePoint != null) tile.setPriority(centroid.distanceTo3(referencePoint)); //pass request to delegate Runnable task = delegateKit.createRequestTask((DelegatorCurtainTextureTile) tile, this); //if returned task is null, the task has already been run by //the immediate delegates, so don't add to queue if (task != null) { this.getRequestQ().add(task); } } protected void validateTileClass(Object tile) { if (!(tile instanceof DelegatorCurtainTextureTile)) { throw new IllegalArgumentException("Tile must be a " + DelegatorCurtainTextureTile.class.getName()); } } /** * Load a texture from a URL (must be file protocol) and set the tile's * texture data to the loaded texture. Should be called by the * {@link ITileRequesterDelegate}. * * @param tile * Tile to set texture data * @param textureURL * File URL of the texture * @return true if the texture data was loaded successfully */ @Override public boolean loadTexture(DelegatorCurtainTextureTile tile, URL textureURL) { //public for delegate access TextureData textureData; synchronized (fileLock) { textureData = readTexture(tile, textureURL); } if (textureData == null) return false; tile.setTextureData(textureData); if (tile.getLevelNumber() != 0 || !isRetainLevelZeroTiles()) addTileToCache(tile); return true; } @Override public void addTileToCache(IDelegatorTile tile) { TextureTile.getMemoryCache().add(tile.getTransformedTileKey(), tile); } @Override public TextureData readTexture(DelegatorCurtainTextureTile tile, URL url) { try { //if the file is a DDS file, just read it directly (skip all delegate readers/transformers) if (url.toString().toLowerCase().endsWith("dds")) return TextureIO.newTextureData(GLProfile.get(GLProfile.GL2), url, isUseMipMaps(), null); BufferedImage image = readImage(tile, url); if ("image/dds".equalsIgnoreCase(getTextureFormat())) { //if required to compress textures, then compress the image to a DDS image DXTCompressionAttributes attributes = DDSCompressor.getDefaultCompressionAttributes(); attributes.setBuildMipmaps(isUseMipMaps()); ByteBuffer buffer; if (image != null) { buffer = new DDSCompressor().compressImage(image, attributes); } else { buffer = DDSCompressor.compressImageURL(url, attributes); } //return the dds image as TextureData return TextureIO.newTextureData(GLProfile.get(GLProfile.GL2), WWIO.getInputStreamFromByteBuffer(buffer), isUseMipMaps(), null); } //return the image as TextureData return AWTTextureIO.newTextureData(GLProfile.get(GLProfile.GL2), image, isUseMipMaps()); } catch (Exception e) { String msg = Logging.getMessage("layers.TextureLayer.ExceptionAttemptingToReadTextureFile", url); Logging.logger().log(java.util.logging.Level.SEVERE, msg, e); } return null; } /** * Read image from a File URL and return it as a {@link BufferedImage}. * * @param tile * Tile for which to read an image * @param url * File URL to read from * @return Image read from URL * @throws IOException * If image could not be read */ protected BufferedImage readImage(DelegatorCurtainTextureTile tile, URL url) throws IOException { //first try to read the image via the ImageReaderDelegates BufferedImage image = delegateKit.readImage(tile, url, currentGlobe); if (image == null) { if (url.toString().toLowerCase().endsWith(".dds")) { ByteBuffer buffer = WWIO.readURLContentToBuffer(url, false); image = DDSUncompressor.readDxt3(buffer); } else { //if that doesn't work, just read it with ImageIO class image = ImageIO.read(url); } if (image == null) { throw new IOException("Could not read image"); } } //perform any transformations on the image image = delegateKit.transformImage(image, tile); //manually do the TRANSPARENCY_COLORS transform, for compatibility with AbstractRetrievalPostProcessor int[] colors = (int[]) this.getValue(AVKey.TRANSPARENCY_COLORS); if (colors != null) image = ImageUtil.mapTransparencyColors(image, colors); return image; } /** * Extension to superclass' DownloadPostProcessor which returns this class' * fileLock instead of the superclass'. * * @author Michael de Hoog */ protected static class DownloadPostProcessor extends BasicTiledCurtainLayer.DownloadPostProcessor { private final DelegatorTiledCurtainLayer layer; public DownloadPostProcessor(CurtainTextureTile tile, DelegatorTiledCurtainLayer layer) { super(tile, layer); this.layer = layer; } @Override protected Object getFileLock() { return layer.fileLock; } } @Override protected CurtainTextureTile createCurtainTextureTile(Segment segment, CurtainLevel level, int row, int col) { return delegateKit.createTextureTile(segment, level, row, col); } @Override public void unmarkResourceAbsent(DelegatorCurtainTextureTile tile) { getLevels().unmarkResourceAbsent(tile); } @Override public void markResourceAbsent(DelegatorCurtainTextureTile tile) { getLevels().markResourceAbsent(tile); } @Override public URL getContext() { return context; } /* ********************************************************************************************** * Below here is copied from BasicTiledImageLayer, with some modifications to use the delegates * ********************************************************************************************** */ @Override public void retrieveRemoteTexture(CurtainTextureTile tile, BasicTiledCurtainLayer.DownloadPostProcessor postProcessor) { createAndRunRetriever(tile, postProcessor); } @Override public void retrieveRemoteTexture(DelegatorCurtainTextureTile tile, RetrievalPostProcessor postProcessor) { createAndRunRetriever(tile, postProcessor); } protected void createAndRunRetriever(CurtainTextureTile tile, RetrievalPostProcessor postProcessor) { Retriever retriever = createRetriever(tile, postProcessor); if (retriever != null) WorldWind.getRetrievalService().runRetriever(retriever, tile.getPriority()); } protected Retriever createRetriever(final CurtainTextureTile tile, RetrievalPostProcessor postProcessor) { //copied from BasicTiledImageLayer.downloadTexture(), with the following modifications: // - uses the delegateKit to instanciate the Retriever // - returns the Retriever instead of adding it to the RetrievalService if (!this.isNetworkRetrievalEnabled()) { this.getLevels().markResourceAbsent(tile); return null; } if (!WorldWind.getRetrievalService().isAvailable()) return null; java.net.URL url; try { //MODIFIED validateTileClass(tile); url = delegateKit.getRemoteTileURL((DelegatorCurtainTextureTile) tile, null); //MODIFIED if (url == null) return null; if (WorldWind.getNetworkStatus().isHostUnavailable(url)) { this.getLevels().markResourceAbsent(tile); return null; } } catch (java.net.MalformedURLException e) { Logging.logger().log(java.util.logging.Level.SEVERE, Logging.getMessage("layers.TextureLayer.ExceptionCreatingTextureUrl", tile), e); return null; } Retriever retriever; if ("http".equalsIgnoreCase(url.getProtocol()) || "https".equalsIgnoreCase(url.getProtocol())) { if (postProcessor == null) postProcessor = new DownloadPostProcessor(tile, this); //MODIFIED //retriever = new HTTPRetriever(url, postProcessor); retriever = delegateKit.createRetriever(url, postProcessor); //MODIFIED } else { Logging.logger().severe(Logging.getMessage("layers.TextureLayer.UnknownRetrievalProtocol", url.toString())); return null; } // 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); //MODIFIED //WorldWind.getRetrievalService().runRetriever(retriever, tile.getPriority()); return retriever; //MODIFIED } }