/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2015, Open Source Geospatial Foundation (OSGeo)
* (C) 2004-2008, Refractions Research Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.tile;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.util.logging.Logging;
/**
* <p>
* At tile represents a single space on the map within a specific
* ReferencedEnvelope. It holds a RenderExecutorComposite for fetching its
* image, and an SWTImage (which is disposed at various times). It listens to
* events for when to fetch, dispose, and construct new images. From
* </p>
*
* @author GDavis
* @author Ugo Taddei
* @since 12
* @source $URL:
* http://svn.osgeo.org/geotools/trunk/modules/unsupported/tile-client
* /src/main/java/org/geotools/tile/Tile.java $
*/
public abstract class Tile implements ImageLoader {
private static final Logger LOGGER = Logging.getLogger(Tile.class
.getPackage().getName());
/**
* These are the states of the tile. This state represents if the tile needs
* to be re-rendered or not. A state of new or invalid means the tile should
* be re-rendered
*/
public enum RenderState {
NEW, RENDERED, INVALID
};
/**
* These states represent the state of the context. If the context is
* invalid than the rendering stack no longer matches the rendering stack
* the user has defined and the rendering stack needs to be updated.
*/
public enum ContextState {
OKAY, INVALID
};
/**
* These states represent if the tile is on or off screen. This information
* is used to determine what tiles can be disposed.
*/
public enum ScreenState {
ONSCREEN, OFFSCREEN
};
/**
* These states represent if the tile has been validated in response to a
* user event.
* <p>
* This information is used along with the screen state to determine if a
* tile can be disposed.
*/
public enum ValidatedState {
VALIDATED, OLD
};
/**
* The bounds of the tile
*/
private ReferencedEnvelope env;
/**
* The size of the tile in pixels
*/
private int tileSize;
/**
* Identifies the tile in the grid space.
*/
private TileIdentifier tileIdentifier;
/**
* The state of the image.
*/
private RenderState renderState = RenderState.NEW;
/**
* The state of the rendering stack
*/
private ContextState contextState = ContextState.INVALID;
/**
* If the tile is on screen or not.
*/
private ScreenState screenState = ScreenState.OFFSCREEN;
/**
* If after an update event the tile has been validated
*/
private ValidatedState tileState = ValidatedState.VALIDATED;
/**
* A listener that is notified when the state is changed.
*/
private TileStateChangedListener listener = null;
/**
* A listener that is notified when the state is changed.
*/
private BufferedImage tileImage = null;
/**
* A delegate to proved direct loading or load from a disk (cache).
*/
private ImageLoader imageLoader = this;
public void setImageLoader(ImageLoader imageLoader) {
if (imageLoader == null) {
throw new IllegalArgumentException("ImageLoader cannot be null");
}
this.imageLoader = imageLoader;
}
/**
* for locking on the SWT image to prevent creating it multiple times
*/
// private Object SWTLock = new Object();
public Tile(TileIdentifier tileId, ReferencedEnvelope env, int tileSize) {
if (env == null) {
throw new IllegalArgumentException("Envelope cannot be null");
}
this.env = env;
this.tileSize = tileSize;
if (tileId == null) {
throw new IllegalArgumentException("TileIdentifier cannot be null");
}
this.tileIdentifier = tileId;
}
public void setStateChangedListener(TileStateChangedListener listener) {
this.listener = listener;
}
/**
* Disposes of the tile.
*/
public void dispose() {
// disposeSWTImage();
setScreenState(ScreenState.OFFSCREEN);
// TODO: figure out how to properly dispose of the render executors
// we cannot just call dispose because this drops all the layer
// listeners
// that listen for events
// getRenderExecutor().dispose();
}
public BufferedImage getBufferedImage() {
// TODO REVIEW this getter has side effects!
if (isImageLoadedOK()) {
return this.tileImage;
}
try {
this.tileImage = this.imageLoader.loadImageTileImage(this);
setRenderState(RenderState.RENDERED);
return this.tileImage;
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to load image: " + this.getUrl(),
e);
setRenderState(RenderState.INVALID);
return createErrorImage("Failed: " + getId());
}
}
public BufferedImage loadImageTileImage(Tile tile) throws IOException {
return ImageIO.read(getUrl());
}
/**
* Returns true if the image has been correctly loaded and the render state
* is {@link RenderState.RENDERED}.
*
* @return the tile image
*/
private boolean isImageLoadedOK() {
return this.renderState == RenderState.RENDERED
&& this.tileImage != null;
}
/**
* Gets an image showing an error, possibly indicating a failure to load the
* tile image.
*
* @return
*/
protected BufferedImage createErrorImage(final String message) {
BufferedImage buffImage = null;
final int size = getTileSize();
buffImage = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = buffImage.createGraphics();
graphics.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, (float) 0.5));
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, size, size);
graphics.setColor(Color.RED);
graphics.drawLine(0, 0, size, size);
graphics.drawLine(0, size, size, 0);
int mesgWidth = graphics.getFontMetrics().stringWidth(message);
graphics.drawString(message, (size - mesgWidth) / 2, size / 2);
graphics.dispose();
return buffImage;
}
/**
* Creates an swt image from the tiles buffered image. private void
* createSWTImage() { // synchronize this code to prevent multiple threads
* from creating the SWT image more times than needed synchronized (SWTLock)
* { // if the SWTImage is created once the lock is gained, exit if
* (swtImage != null && !swtImage.isDisposed()) { return; } // otherwise try
* creating the SWTImage now try { BufferedImage buffImage =
* getBufferedImage(); swtImage = AWTSWTImageUtils.createSWTImage(buffImage,
* false); } catch (Exception ex) { ex.printStackTrace(); } } }
*/
/**
* @return The size of the tile in pixels.
*/
public int getTileSize() {
return tileSize;
}
/**
* @return the bounds of the tile
*/
public ReferencedEnvelope getExtent() {
return env;
}
/**
* @return the parent render executor public RenderExecutorComposite
* getRenderExecutor() { return renderExecutorComp; }
*/
/**
* Sets the state of the tiles image.
* <p>
* See getRenderState() for a description of the valid states.
*
* @param newState
*/
public void setRenderState(RenderState newState) {
this.renderState = newState;
if (listener != null) {
listener.renderStateChanged(this);
}
}
/**
* Gets the state of the tiled image.
* <p>
* One Of:
* <ul>
* <li>RenderState.NEW - a new tile that needs to be rendered
* <li>RenderState.Renderer - the tile has been rendered or is in the state
* of being rendered
* <li>RenderState.Invalid - something has changed and the tile's rendered
* image is not longer valid and needs to be re-rendered
* </ul>
*
* @return
*/
public RenderState getRenderState() {
return this.renderState;
}
/**
* This function returns the state of the tile render stack. If the context
* is invalid then the context needs to be updated before the tile is
* rendered.
* <p>
* Should be one of:
* <ul>
* <li>INVALID - The context needs to be updated.
* <li>OKAY - The context is okay and does not need updating.
* </ul>
*
* @return the state of the tiles rendering stack
*/
public ContextState getContextState() {
return this.contextState;
}
/**
* Sets the state of the tile rendering stack.
* <p>
* See getContextState() for valid value descriptions.
*
* @param newState
*/
public void setContextState(ContextState newState) {
this.contextState = newState;
if (listener != null) {
listener.contextStateChanged(this);
}
}
/**
* Sets if the tile is on screen or not.
* <p>
* This is used with other information to determine if a tile can be
* disposed of. Valid values include:
* <ul>
* <li>ONSCREEN - the tile has been requested by the viewport therefore we
* assume it is on screen
* <li>OFFSCREEN - this tile was not requested by the viewport
* </ul>
*
* @return
*/
public ScreenState getScreenState() {
return this.screenState;
}
/**
* Sets the screen state.
* <p>
* See getScreenState() for a description of the valid values.
*
* @param newState
*/
public void setScreenState(ScreenState newState) {
this.screenState = newState;
if (listener != null) {
listener.screenStateChanged(this);
}
}
/**
* Gets the validation state.
* <p>
* This is used in conjunction with the screen state to determine it a tile
* can be disposed of. This state is set during a refresh event that is
* triggered from some gui event. Valid values include:
* <ul>
* <li>VALIDATED - The tile is validated and ready to be used for painting
* on the screen. Don't remove this tile.
* <li>OLD - This tile is an old tile that if off screen can be removed.
* </ul>
*
* @return
*/
public ValidatedState getTileState() {
return this.tileState;
}
/**
* Sets the validation state.
* <p>
* See getTileState() for a description of valid values.
*
* @param newState
*/
public void setTileState(ValidatedState newState) {
this.tileState = newState;
if (listener != null) {
listener.validationStateChanged(this);
}
}
/**
* Diese Methode wird verwendet um... TODO.
*
* @return
*/
public String getId() {
return this.tileIdentifier.getId();
}
/**
* Diese Methode wird verwendet um... TODO.
*
* @return
*/
public TileIdentifier getTileIdentifier() {
return this.tileIdentifier;
}
@Override
public int hashCode() {
return getUrl().hashCode();
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Tile)) {
return false;
}
return getUrl().equals(((Tile) other).getUrl());
}
public String toString() {
return this.getUrl().toString();
}
public abstract URL getUrl();
}