/*******************************************************************************
* 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.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
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 mobac.utilities.Utilities;
import mobac.utilities.jdbc.SQLiteLoader;
import org.apache.log4j.Logger;
/**
*
* MBTiles input http://mbtiles.org/
*
*/
@XmlRootElement(name = "localTileSQLite")
public class CustomLocalTileSQliteMapSource implements FileBasedMapSource {
private static Logger log = Logger.getLogger(CustomLocalTileSQliteMapSource.class);
private static enum SQLiteAtlasType {
RMaps, MBTiles, BigPlanetTracks, Galileo, NaviComputer, OSMAND
};
private MapSourceLoaderInfo loaderInfo = null;
private boolean initialized = false;
@XmlElement(required = false)
private TileImageType tileImageType = null;
@XmlElement(nillable = false, defaultValue = "CustomLocalSQLite")
private String name = "CustomLocalSQLite";
private int minZoom = PreviewMap.MIN_ZOOM;
private int maxZoom = PreviewMap.MAX_ZOOM;
@XmlElement(required = true)
private File sourceFile = null;
@XmlElement(required = true)
private SQLiteAtlasType atlasType = null;
@XmlElement(defaultValue = "#000000")
@XmlJavaTypeAdapter(ColorAdapter.class)
private Color backgroundColor = Color.BLACK;
@XmlElement(defaultValue = "msMercatorSpherical")
private MapSpaceType mapSpaceType = MapSpaceType.msMercatorSpherical;
@XmlElement(defaultValue = "false")
private boolean hiddenDefault = false;
private String sqlMaxZoomStatement;
private String sqlMinZoomStatement;
private String sqlTileStatement;
private String sqlTileImageTypeStatement;
/**
* SQLite connection with database file
*/
private Connection conn = null;
//private final MapSpace mapSpace = MapSpaceFactory.getInstance(256, MapSpaceType.msMercatorSpherical);
public CustomLocalTileSQliteMapSource() {
super();
}
protected void updateZoomLevelInfo() {
Statement statement = null;
try {
statement = conn.createStatement();
if (statement.execute(sqlMaxZoomStatement)) {
ResultSet rs = statement.getResultSet();
if (rs.next()) {
maxZoom = rs.getInt(1);
}
rs.close();
}
if (statement.execute(sqlMinZoomStatement)) {
ResultSet rs = statement.getResultSet();
if (rs.next()) {
minZoom = rs.getInt(1);
}
rs.close();
}
statement.close();
} catch (SQLException e) {
log.error("", e);
} finally {
Utilities.closeStatement(statement);
}
}
public synchronized void initialize() {
if (initialized)
return;
reinitialize();
}
public void reinitialize() {
if (atlasType == null) {
JOptionPane.showMessageDialog(null,
String.format(I18nUtils.localizedStringForKey("msg_custom_map_invalid_source_file"),
name, sourceFile),
I18nUtils.localizedStringForKey("msg_custom_map_invalid_source_file_title"),
JOptionPane.ERROR_MESSAGE);
initialized = true;
return;
}
if (!sourceFile.isFile()) {
JOptionPane.showMessageDialog(null,
String.format(I18nUtils.localizedStringForKey("msg_custom_map_invalid_source_sqlitedb"),
name, sourceFile),
I18nUtils.localizedStringForKey("msg_custom_map_invalid_source_file_title"),
JOptionPane.ERROR_MESSAGE);
initialized = true;
return;
}
if (!SQLiteLoader.loadSQLiteOrShowError()) {
initialized = true;
return;
}
log.debug("Loading SQLite database " + sourceFile);
String url = "jdbc:sqlite:" + this.sourceFile;
try {
conn = DriverManager.getConnection(url);
} catch (SQLException e) {
JOptionPane.showMessageDialog(null,
String.format(I18nUtils.localizedStringForKey("msg_custom_map_source_failed_load_sqlitedb"),
name,sourceFile,e.getMessage()),
I18nUtils.localizedStringForKey("msg_custom_map_source_failed_load_sqlitedb_title"),
JOptionPane.ERROR_MESSAGE);
initialized = true;
return;
}
switch (atlasType) {
case MBTiles:
// DISTINCT works much faster than min(zoom_level) or max(zoom_level) - uses index?
sqlMaxZoomStatement = "SELECT DISTINCT zoom_level FROM tiles ORDER BY zoom_level DESC LIMIT 1;";
sqlMinZoomStatement = "SELECT DISTINCT zoom_level FROM tiles ORDER BY zoom_level ASC LIMIT 1;";
sqlTileStatement = "SELECT tile_data from tiles WHERE zoom_level=? AND tile_column=? AND tile_row=?;";
sqlTileImageTypeStatement = "SELECT tile_data from tiles LIMIT 1;";
break;
case RMaps:
case BigPlanetTracks:
case Galileo:
case OSMAND:
sqlMaxZoomStatement = "SELECT DISTINCT (17 - z) as zoom FROM tiles ORDER BY zoom DESC LIMIT 1;";
sqlMinZoomStatement = "SELECT DISTINCT (17 - z) as zoom FROM tiles ORDER BY zoom ASC LIMIT 1;";
sqlTileStatement = "SELECT image from tiles WHERE z=(17 - ?) AND x=? AND y=?;";
sqlTileImageTypeStatement = "SELECT image from tiles LIMIT 1;";
break;
case NaviComputer:
sqlMaxZoomStatement = "SELECT DISTINCT zoom FROM Tiles ORDER BY zoom DESC LIMIT 1;";
sqlMinZoomStatement = "SELECT DISTINCT zoom FROM Tiles ORDER BY zoom ASC LIMIT 1;";
sqlTileStatement = "SELECT Tile FROM Tiles LEFT JOIN Tilesdata ON Tiles.id=Tilesdata.id WHERE Zoom=? AND X=? AND Y=?;";
sqlTileImageTypeStatement = "SELECT Tile from Tilesdata LIMIT 1;";
break;
}
updateZoomLevelInfo();
detectTileImageType();
initialized = true;
}
protected void detectTileImageType() {
if (tileImageType != null)
return; // Already specified manually by user
Statement statement = null;
try {
statement = conn.createStatement();
if (statement.execute(sqlTileImageTypeStatement)) {
ResultSet rs = statement.getResultSet();
if (rs.next())
tileImageType = Utilities.getImageType(rs.getBytes(1));
rs.close();
}
statement.close();
} catch (SQLException e) {
log.error("", e);
} finally {
Utilities.closeStatement(statement);
}
if (tileImageType == null)
throw new RuntimeException("Unable to detect image type of " + sourceFile + ".\n"
+ "Please specify it manually using <tileImageType> entry in map source definition.");
}
public byte[] getTileData(int zoom, int x, int y, LoadMethod loadMethod) throws IOException, TileException,
InterruptedException {
if (!initialized)
initialize();
PreparedStatement statement = null;
try {
switch (atlasType) {
case MBTiles:
y = (1 << zoom) - y - 1;
break;
default:
break;
}
statement = conn.prepareStatement(sqlTileStatement);
statement.setInt(1, zoom);
statement.setInt(2, x);
statement.setInt(3, y);
if (log.isTraceEnabled())
log.trace(String.format("Loading tile z=%d x=%d y=%d", zoom, x, y));
if (statement.execute()) {
ResultSet rs = statement.getResultSet();
if (!rs.next()) {
if (log.isDebugEnabled())
log.debug(String.format("Tile in database not found: z=%d x=%d y=%d", zoom, x, y));
return null;
}
byte[] data = rs.getBytes(1);
rs.close();
return data;
}
} catch (SQLException e) {
log.error("", e);
} finally {
Utilities.closeStatement(statement);
}
return null;
}
public BufferedImage getTileImage(int zoom, int x, int y, LoadMethod loadMethod) throws IOException, TileException,
InterruptedException {
byte[] data = getTileData(zoom, x, y, loadMethod);
if (data == null)
return null;
return ImageIO.read(new ByteArrayInputStream(data));
}
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;
return MapSpaceFactory.getInstance(256, mapSpaceType);
}
public Color getBackgroundColor() {
return backgroundColor;
}
@XmlTransient
public MapSourceLoaderInfo getLoaderInfo() {
return loaderInfo;
}
public void setLoaderInfo(MapSourceLoaderInfo loaderInfo) {
this.loaderInfo = loaderInfo;
}
protected void closeConnection() {
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
}
conn = null;
}
public MapSpaceType getMapSpaceType() {
return mapSpaceType;
}
@Override
public boolean getHiddenDefault() {
return hiddenDefault;
}
}