/*******************************************************************************
* Copyright (c) MOBAC developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package mobac.gui.mapview;
//License: GPL. Copyright 2008 by Jan Peter Stotz
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import mobac.program.interfaces.MapSource;
import mobac.utilities.Utilities;
/**
* Holds one map tile. Additionally the code for loading the tile image and painting it is also included in this class.
*
* @author Jan Peter Stotz
*/
public class Tile {
/**
* Hourglass image that is displayed until a map tile has been loaded
*/
public static BufferedImage LOADING_IMAGE;
public static BufferedImage ERROR_IMAGE;
static {
try {
LOADING_IMAGE = ImageIO.read(Utilities.getResourceImageUrl("hourglass.png"));
ERROR_IMAGE = ImageIO.read(Utilities.getResourceImageUrl("error.png"));
} catch (Exception e1) {
LOADING_IMAGE = null;
ERROR_IMAGE = null;
}
}
public enum TileState {
TS_NEW, TS_LOADING, TS_LOADED, TS_ERROR
};
protected MapSource mapSource;
protected int xtile;
protected int ytile;
protected int zoom;
protected BufferedImage image;
protected String key;
protected TileState tileState = TileState.TS_NEW;
/**
* Creates a tile with empty image.
*
* @param mapSource
* @param xtile
* @param ytile
* @param zoom
*/
public Tile(MapSource mapSource, int xtile, int ytile, int zoom) {
super();
this.mapSource = mapSource;
this.xtile = xtile;
this.ytile = ytile;
this.zoom = zoom;
this.image = LOADING_IMAGE;
this.key = getTileKey(mapSource, xtile, ytile, zoom);
}
public Tile(MapSource source, int xtile, int ytile, int zoom, BufferedImage image) {
this(source, xtile, ytile, zoom);
this.image = image;
}
/**
* Tries to get tiles of a lower or higher zoom level (one or two level difference) from cache and use it as a
* placeholder until the tile has been loaded.
*/
public void loadPlaceholderFromCache(MemoryTileCache cache) {
int tileSize = mapSource.getMapSpace().getTileSize();
BufferedImage tmpImage = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) tmpImage.getGraphics();
// g.drawImage(image, 0, 0, null);
for (int zoomDiff = 1; zoomDiff < 5; zoomDiff++) {
// first we check if there are already the 2^x tiles
// of a higher detail level
int zoom_high = zoom + zoomDiff;
if (zoomDiff < 3 && zoom_high <= JMapViewer.MAX_ZOOM) {
int factor = 1 << zoomDiff;
int xtile_high = xtile << zoomDiff;
int ytile_high = ytile << zoomDiff;
double scale = 1.0 / factor;
g.setTransform(AffineTransform.getScaleInstance(scale, scale));
int paintedTileCount = 0;
for (int x = 0; x < factor; x++) {
for (int y = 0; y < factor; y++) {
Tile tile = cache.getTile(mapSource, xtile_high + x, ytile_high + y, zoom_high);
if (tile != null && tile.tileState == TileState.TS_LOADED) {
paintedTileCount++;
tile.paint(g, x * tileSize, y * tileSize);
}
}
}
if (paintedTileCount == factor * factor) {
image = tmpImage;
return;
}
}
int zoom_low = zoom - zoomDiff;
if (zoom_low >= JMapViewer.MIN_ZOOM) {
int xtile_low = xtile >> zoomDiff;
int ytile_low = ytile >> zoomDiff;
int factor = (1 << zoomDiff);
double scale = factor;
AffineTransform at = new AffineTransform();
int translate_x = (xtile % factor) * tileSize;
int translate_y = (ytile % factor) * tileSize;
at.setTransform(scale, 0, 0, scale, -translate_x, -translate_y);
g.setTransform(at);
Tile tile = cache.getTile(mapSource, xtile_low, ytile_low, zoom_low);
if (tile != null && tile.tileState == TileState.TS_LOADED) {
tile.paint(g, 0, 0);
image = tmpImage;
return;
}
}
}
}
public MapSource getSource() {
return mapSource;
}
/**
* @return tile number on the x axis of this tile
*/
public int getXtile() {
return xtile;
}
/**
* @return tile number on the y axis of this tile
*/
public int getYtile() {
return ytile;
}
/**
* @return zoom level of this tile
*/
public int getZoom() {
return zoom;
}
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
this.image = image;
}
public void setErrorImage() {
image = ERROR_IMAGE;
tileState = TileState.TS_ERROR;
}
public void loadImage(InputStream input) throws IOException {
image = ImageIO.read(input);
}
public void loadImage(byte[] data) throws IOException {
loadImage(new ByteArrayInputStream(data));
}
/**
* @return key that identifies a tile
*/
public String getKey() {
return key;
}
public boolean isErrorTile() {
return (ERROR_IMAGE.equals(image));
}
public TileState getTileState() {
return tileState;
}
public void setTileState(TileState tileState) {
this.tileState = tileState;
}
/**
* Paints the tile-image on the {@link Graphics} <code>g</code> at the position <code>x</code>/<code>y</code>.
*
* @param g
* @param x
* x-coordinate in <code>g</code>
* @param y
* y-coordinate in <code>g</code>
*/
public void paint(Graphics g, int x, int y) {
if (image == null)
return;
int tileSize = mapSource.getMapSpace().getTileSize();
//Google Scale = 2, retina support
g.drawImage(image, x, y, tileSize, tileSize, Color.WHITE, null);
//g.drawImage(image, x, y, Color.WHITE);
}
public void paintTransparent(Graphics g, int x, int y) {
if (image == null)
return;
int tileSize = mapSource.getMapSpace().getTileSize();
//Google Scale = 2, retina support
g.drawImage(image, x, y, tileSize, tileSize, null);
//g.drawImage(image, x, y, null);
}
@Override
public String toString() {
return "tile " + key;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Tile))
return false;
Tile tile = (Tile) obj;
return (xtile == tile.xtile) && (ytile == tile.ytile) && (zoom == tile.zoom);
}
@Override
public int hashCode() {
assert false : "hashCode not designed";
return -1;
}
public static String getTileKey(MapSource source, int xtile, int ytile, int zoom) {
return zoom + "/" + xtile + "/" + ytile + "@" + source.getName();
}
}