/*******************************************************************************
* 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.program.atlascreators;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.EnumSet;
import java.util.Locale;
import mobac.exceptions.AtlasTestException;
import mobac.mapsources.mapspace.MercatorPower2MapSpace;
import mobac.program.annotations.AtlasCreatorName;
import mobac.program.interfaces.LayerInterface;
import mobac.program.interfaces.MapInterface;
import mobac.program.interfaces.MapSource;
import mobac.program.interfaces.MapSpace;
import mobac.program.model.TileImageParameters;
import mobac.program.model.TileImageType;
/**
* https://github.com/mapbox/mbtiles-spec/tree/master/1.1
*/
@AtlasCreatorName(value = "MBTiles SQLite")
public class MBTiles extends RMapsSQLite {
private static final String INSERT_SQL = "INSERT or REPLACE INTO tiles (tile_column,tile_row,zoom_level,tile_data) VALUES (?,?,?,?)";
private static final String TABLE_TILES = "CREATE TABLE IF NOT EXISTS tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob);";
private static final String INDEX_TILES = "CREATE INDEX IF NOT EXISTS tiles_idx on tiles (zoom_level, tile_column, tile_row)";
private static final String TABLE_METADATA = "CREATE TABLE IF NOT EXISTS metadata (name text, value text);";
private static final String INSERT_METADATA = "INSERT INTO metadata (name,value) VALUES (?,?);";
private static final String INDEX_METADATA = "CREATE UNIQUE INDEX IF NOT EXISTS metadata_idx ON metadata (name);";
private double boundsLatMin;
private double boundsLatMax;
private double boundsLonMin;
private double boundsLonMax;
private TileImageType atlasTileImageType;
@Override
public boolean testMapSource(MapSource mapSource) {
return MercatorPower2MapSpace.INSTANCE_256.equals(mapSource.getMapSpace());
}
@Override
protected void testAtlas() throws AtlasTestException {
EnumSet<TileImageType> allowed = EnumSet.of(TileImageType.JPG, TileImageType.PNG);
// Test of output format - only jpg xor png is allowed
TileImageType tit = null;
for (LayerInterface layer : atlas) {
for (MapInterface map : layer) {
TileImageParameters parameters = map.getParameters();
TileImageType currentTit;
if (parameters == null) {
currentTit = map.getMapSource().getTileImageType();
if (!allowed.contains(currentTit))
throw new AtlasTestException(
"Map source format incompatible - tile format conversion to PNG or JPG is required for this map.",
map);
} else {
currentTit = parameters.getFormat().getType();
if (!allowed.contains(currentTit))
throw new AtlasTestException(
"Selected custom tile format not supported - only JPG and PNG formats are supported.",
map);
}
if (tit != null && !currentTit.equals(tit)) {
throw new AtlasTestException("All maps within one atlas must use the same format (PNG or JPG). "
+ "Use tile format conversion converting maps with a different format.", map);
}
tit = currentTit;
}
}
atlasTileImageType = tit;
}
@Override
protected void openConnection() throws SQLException {
if (databaseFile.isFile()) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HHmmss");
databaseFile = new File(atlasDir, atlas.getName() + "_" + sdf.format(new Date()) + ".mbtiles");
}
super.openConnection();
}
@Override
protected void initializeDB() throws SQLException {
Statement stat = conn.createStatement();
stat.executeUpdate(TABLE_TILES);
stat.executeUpdate(INDEX_TILES);
stat.executeUpdate(TABLE_METADATA);
stat.executeUpdate(INDEX_METADATA);
stat.close();
boundsLatMin = Double.MAX_VALUE;
boundsLatMax = Double.MIN_VALUE;
boundsLonMin = Double.MAX_VALUE;
boundsLonMax = Double.MIN_VALUE;
}
@Override
protected void updateTileMetaInfo() throws SQLException {
MapSpace ms = map.getMapSource().getMapSpace();
//double lon1 = ms.cXToLon(map.getMinTileCoordinate().x, zoom);
//double lon2 = ms.cXToLon(map.getMaxTileCoordinate().x, zoom);
//double lat1 = ms.cYToLat(map.getMinTileCoordinate().y, zoom);
//double lat2 = ms.cYToLat(map.getMaxTileCoordinate().y, zoom);
Point2D.Double p1 = ms.cXYToLonLat(map.getMinTileCoordinate().x, map.getMinTileCoordinate().y, zoom);
Point2D.Double p2 = ms.cXYToLonLat(map.getMaxTileCoordinate().x, map.getMaxTileCoordinate().y, zoom);
double lon1 = p1.x;
double lon2 = p2.x;
double lat1 = p1.y;
double lat2 = p2.y;
boundsLatMin = Math.min(boundsLatMin, Math.min(lat1, lat2));
boundsLatMax = Math.max(boundsLatMax, Math.max(lat1, lat2));
boundsLonMin = Math.min(boundsLonMin, Math.min(lon1, lon2));
boundsLonMax = Math.max(boundsLonMin, Math.max(lon1, lon2));
}
@Override
public void finishAtlasCreation() throws IOException, InterruptedException {
PreparedStatement st;
try {
st = conn.prepareStatement(INSERT_METADATA);
st.setString(1, "bounds");
st.setString(2, String.format(Locale.ENGLISH, "%.3f,%.3f,%.3f,%.3f", boundsLonMin, boundsLatMin,
boundsLonMax, boundsLatMax));
st.execute();
st.setString(1, "name");
st.setString(2, atlas.getName());
st.execute();
st.setString(1, "type");
st.setString(2, "baselayer");
st.execute();
st.setString(1, "version");
st.setString(2, "1.1");
st.execute();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
st.setString(1, "description");
st.setString(2, atlas.getName() + " created on " + sdf.format(new Date()) + " by MOBAC");
st.execute();
st.setString(1, "format");
st.setString(2, atlasTileImageType.getFileExt());
st.execute();
st.close();
conn.commit();
} catch (SQLException e) {
throw new IOException(e);
}
super.finishAtlasCreation();
}
@Override
protected String getTileInsertSQL() {
return INSERT_SQL;
}
@Override
protected void writeTile(int x, int y, int z, byte[] tileData) throws SQLException, IOException {
y = (1 << z) - y - 1;
prepStmt.setInt(1, x);
prepStmt.setInt(2, y);
prepStmt.setInt(3, z);
prepStmt.setBytes(4, tileData);
prepStmt.addBatch();
}
protected String getDatabaseFileName() {
return atlas.getName() + ".mbtiles";
}
}