/*******************************************************************************
* 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.mapsources.custom;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import mobac.exceptions.TileException;
import mobac.gui.mapview.PreviewMap;
import mobac.mapsources.mapspace.MapSpaceFactory;
import mobac.program.interfaces.FileBasedMapSource;
import mobac.program.interfaces.MapSpace;
import mobac.program.interfaces.MapSpace.MapSpaceType;
import mobac.program.jaxb.ColorAdapter;
import mobac.program.model.MapSourceLoaderInfo;
import mobac.program.model.TileImageType;
import mobac.utilities.I18nUtils;
import org.apache.log4j.Logger;
@XmlRootElement(name = "localImageFile")
public class CustomLocalImageFileMapSource implements FileBasedMapSource {
private static final Logger log = Logger.getLogger(CustomLocalImageFileMapSource.class);
private MapSourceLoaderInfo loaderInfo = null;
@XmlElement(required = true, nillable = false)
private double boxNorth = 90.0;
@XmlElement(required = true, nillable = false)
private double boxSouth = -90.0;
@XmlElement(required = true, nillable = false)
private double boxEast = 180.0;
@XmlElement(required = true, nillable = false)
private double boxWest = -180.0;
private MapSpace mapSpace = MapSpaceFactory.getInstance(256, MapSpaceType.msMercatorSpherical);
private boolean initialized = false;
BufferedImage fullImage = null;
private TileImageType tileImageType = null;
@XmlElement(nillable = false, defaultValue = "CustomImage")
private String name = "Custom";
@XmlElement(nillable = false, defaultValue = "0")
private int minZoom = PreviewMap.MIN_ZOOM;
@XmlElement(nillable = false, defaultValue = "20")
private int maxZoom = PreviewMap.MAX_ZOOM;
@XmlElement(required = true)
private File imageFile = null;
@XmlElement(nillable = false, defaultValue = "false")
private boolean retinaDisplay = false;
// @XmlElement()
// private CustomMapSourceType sourceType = CustomMapSourceType.DIR_ZOOM_X_Y;
@XmlElement(defaultValue = "false")
private boolean invertYCoordinate = false;
@XmlElement(defaultValue = "#00000000")
@XmlJavaTypeAdapter(ColorAdapter.class)
private Color backgroundColor = Color.BLACK;
@XmlElement(defaultValue = "false")
private boolean hiddenDefault = false;
public CustomLocalImageFileMapSource() {
super();
}
public synchronized void initialize() {
if (initialized)
return;
reinitialize();
}
public void reinitialize() {
try {
if (!imageFile.isFile()) {
JOptionPane.showMessageDialog(null, String.format(
I18nUtils.localizedStringForKey("msg_environment_invalid_source_folder"), name,
imageFile.toString()), I18nUtils
.localizedStringForKey("msg_environment_invalid_source_folder_title"),
JOptionPane.ERROR_MESSAGE);
initialized = true;
return;
}
String[] parts = imageFile.getName().split("\\.");
if (parts.length >= 2) {
tileImageType = TileImageType.getTileImageType(parts[parts.length - 1]);
} else {
tileImageType = TileImageType.PNG;
}
boxWest = Math.min(boxEast, boxWest);
boxEast = Math.max(boxEast, boxWest);
boxSouth = Math.min(boxNorth, boxSouth);
boxNorth = Math.max(boxNorth, boxSouth);
} finally {
initialized = true;
}
}
public byte[] getTileData(int zoom, int x, int y, LoadMethod loadMethod) throws IOException, TileException,
InterruptedException {
ByteArrayOutputStream buf = new ByteArrayOutputStream(16000);
BufferedImage image = getTileImage(zoom, x, y, loadMethod);
if (image == null)
return null;
ImageIO.write(image, tileImageType.getFileExt(), buf);
return buf.toByteArray();
}
// integer nearest to zero
private int absFloor(double value) {
return value > 0 ? (int) Math.floor(value) : (int) Math.ceil(value);
}
// integer farest from zero
private int absCeil(double value) {
return value > 0 ? (int) Math.ceil(value) : (int) Math.floor(value);
}
public BufferedImage getTileImage(int zoom, int x, int y, LoadMethod loadMethod) throws IOException, TileException,
InterruptedException {
if (!initialized)
initialize();
if (log.isTraceEnabled())
log.trace(String.format("Loading tile z=%d x=%d y=%d", zoom, x, y));
BufferedImage image = null;
Graphics2D g2 = null;
try {
if (fullImage == null) {
fullImage = ImageIO.read(imageFile);
}
int imageWidth = fullImage.getWidth();
int imageHeight = fullImage.getHeight();
int tileSize = mapSpace.getTileSize();
//double tileWest = mapSpace.cXToLon(x * tileSize, zoom);
//double tileNorth = mapSpace.cYToLat(y * tileSize, zoom);
//double tileEast = mapSpace.cXToLon((x + 1) * tileSize, zoom);
//double tileSouth = mapSpace.cYToLat((y + 1) * tileSize, zoom);
Point2D.Double p1 = mapSpace.cXYToLonLat(x * tileSize, y * tileSize, zoom);
Point2D.Double p2 = mapSpace.cXYToLonLat((x + 1) * tileSize, (y + 1) * tileSize, zoom);
double tileWest = p1.x;
double tileNorth = p1.y;
double tileEast = p2.x;
double tileSouth = p2.y;
double tileWidth = tileEast - tileWest;
double tileHeight = tileNorth - tileSouth;
// intersects coordinate region
double intersectWest = Math.max(tileWest, boxWest);
double intersectEast = Math.min(tileEast, boxEast);
double intersectNorth = Math.min(tileNorth, boxNorth);
double intersectSouth = Math.max(tileSouth, boxSouth);
double intersectWidth = intersectEast - intersectWest;
double intersectHeight = intersectNorth - intersectSouth;
// intersects
if (intersectWidth > 0 && intersectHeight > 0) {
int graphContextSize = tileSize * (retinaDisplay ? 2 : 1);
image = new BufferedImage(graphContextSize, graphContextSize, BufferedImage.TYPE_4BYTE_ABGR);
g2 = image.createGraphics();
g2.setColor(getBackgroundColor());
g2.fillRect(0, 0, graphContextSize, graphContextSize);
double boxWidth = (boxEast - boxWest);
double boxHeight = (boxNorth - boxSouth);
// crop parameters
double cropWScale = (boxWidth <= 0) ? 0 : (imageWidth / boxWidth);
double cropHScale = (boxHeight <= 0) ? 0 : (imageHeight / boxHeight);
int cropW = absCeil(intersectWidth * cropWScale);
int cropH = absCeil(intersectHeight * cropHScale);
int cropX = absFloor((intersectWest - boxWest) * cropWScale);
// int cropY = imageHeight - absCeil((boxNorth - intersectNorth) * cropHScale) - cropH;
int cropY = absFloor((boxNorth - intersectNorth) * cropHScale);
// skip when no valid crop
if (cropX < imageWidth && cropY < imageHeight && (cropX + cropW) > 0 && (cropY + cropH) > 0) {
// draw rect
double drawrectWScale = (tileWidth <= 0) ? 0 : (graphContextSize / tileWidth);
double drawrectHScale = (tileHeight <= 0) ? 0 : (graphContextSize / tileHeight);
int drawrectW = absCeil(intersectWidth * drawrectWScale);
int drawrectH = absCeil(intersectHeight * drawrectHScale);
int drawrectX = absFloor((intersectWest - tileWest) * drawrectWScale);
// int drawrectY = tileSize - absCeil((tileNorth - intersectNorth) * drawrectHScale) - drawrectH;
int drawrectY = absFloor((tileNorth - intersectNorth) * drawrectHScale);
// skip when draw rectangle totally draw out of image
if (drawrectX < graphContextSize && drawrectY < graphContextSize && (drawrectX + drawrectW) > 1
&& (drawrectY + drawrectH) > 1) {
g2.drawImage(fullImage, drawrectX, drawrectY, drawrectX + drawrectW, drawrectY + drawrectH,
cropX, cropY, cropX + cropW, cropY + cropH, null);
}
}
}
} catch (FileNotFoundException e) {
log.debug("Map image file not found: " + imageFile.getAbsolutePath());
} finally {
if (g2 != null) {
g2.dispose();
}
}
return image;
}
public TileImageType getTileImageType() {
return tileImageType;
}
public int getMaxZoom() {
return maxZoom;
}
public int getMinZoom() {
return minZoom;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
public MapSpace getMapSpace() {
return mapSpace;
}
public Color getBackgroundColor() {
return backgroundColor;
}
@XmlTransient
public MapSourceLoaderInfo getLoaderInfo() {
return loaderInfo;
}
public void setLoaderInfo(MapSourceLoaderInfo loaderInfo) {
this.loaderInfo = loaderInfo;
}
@Override
public boolean getHiddenDefault() {
return hiddenDefault;
}
}