/*******************************************************************************
* 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.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Locale;
import mobac.exceptions.AtlasTestException;
import mobac.exceptions.MapCreationException;
import mobac.program.annotations.AtlasCreatorName;
import mobac.program.atlascreators.impl.MapTileWriter;
import mobac.program.atlascreators.tileprovider.CacheTileProvider;
import mobac.program.interfaces.AtlasInterface;
import mobac.program.interfaces.LayerInterface;
import mobac.program.interfaces.RequiresSQLite;
import mobac.utilities.jdbc.SQLiteLoader;
/**
* Creates maps using the OruxMaps (Android) atlas format.
*
* @author orux Some code based on BigPlanetSql atlascreator
*/
@AtlasCreatorName("OruxMaps Sqlite")
public class OruxMapsSqlite extends OruxMaps implements RequiresSQLite {
private static final String TABLE_TILES_DDL = "CREATE TABLE IF NOT EXISTS tiles (x int, y int, z int, image blob, PRIMARY KEY (x,y,z))";
private static final String INDEX_DDL = "CREATE INDEX IF NOT EXISTS IND on tiles (x,y,z)";
private static final String INSERT_SQL = "INSERT or IGNORE INTO tiles (x,y,z,image) VALUES (?,?,?,?)";
private static final String TABLE_ANDROID_METADATA_DDL = "CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)";
private static final String DATABASE_FILENAME = "OruxMapsImages.db";
private String databaseFile;
private Connection conn = null;
private PreparedStatement prepStmt;
private StringBuilder otrk2MapsContent;
public OruxMapsSqlite() {
super();
SQLiteLoader.loadSQLiteOrShowError();
calVersionCode = "3.0";
}
/*
* (non-Javadoc)
*
* @see mobac.program.atlascreators.AtlasCreator#startAtlasCreation(mobac.program.interfaces.AtlasInterface)
*/
@Override
public void startAtlasCreation(AtlasInterface atlas, File customAtlasDir) throws AtlasTestException, IOException,
InterruptedException {
super.startAtlasCreation(atlas, customAtlasDir);
try {
SQLiteLoader.loadSQLite();
} catch (SQLException e) {
throw new AtlasTestException(SQLiteLoader.getMsgSqliteMissing(), e);
}
}
/*
* @see mobac.program.atlascreators.AtlasCreator#initLayerCreation(mobac.program .interfaces.LayerInterface)
*/
@Override
public void initLayerCreation(LayerInterface layer) throws IOException {
super.initLayerCreation(layer);
databaseFile = new File(oruxMapsMainDir, DATABASE_FILENAME).getAbsolutePath();
log.debug("SQLite Database file: " + databaseFile);
otrk2MapsContent = new StringBuilder();
try {
conn = getConnection();
initializeDB();
} catch (SQLException e) {
throw new IOException(e);
} finally {
closeConnection();
}
}
@Override
public void finishAtlasCreation() throws IOException, InterruptedException {
super.finishAtlasCreation();
closeConnection();
}
@Override
public void abortAtlasCreation() throws IOException {
super.abortAtlasCreation();
log.debug("Aborting OruxMapsSqlite atlas creation");
closeConnection();
}
private Connection getConnection() throws SQLException {
String url = "jdbc:sqlite:/" + this.databaseFile;
Connection conn = DriverManager.getConnection(url);
return conn;
}
private void closeConnection() {
try {
if (conn != null) {
log.debug("Closing database connection");
conn.close();
}
} catch (Exception e) {
}
conn = null;
}
private void initializeDB() throws SQLException {
Statement stat = conn.createStatement();
stat.executeUpdate(TABLE_TILES_DDL);
stat.executeUpdate(INDEX_DDL);
stat.executeUpdate(TABLE_ANDROID_METADATA_DDL);
stat.executeUpdate("INSERT INTO android_metadata VALUES ('" + Locale.getDefault().toString() + "')");
stat.close();
}
@Override
public void createMap() throws MapCreationException, InterruptedException {
otrk2MapsContent.append(prepareOtrk2File());
try {
conn = getConnection();
conn.setAutoCommit(false);
prepStmt = conn.prepareStatement(INSERT_SQL);
createTiles();
conn.close();
} catch (InterruptedException e) {
// User has aborted process
return;
} catch (MapCreationException e) {
throw e;
} catch (Exception e) {
throw new MapCreationException(map, e);
}
}
@Override
protected void createTiles() throws InterruptedException, MapCreationException {
CacheTileProvider ctp = new CacheTileProvider(mapDlTileProvider);
try {
mapDlTileProvider = ctp;
MapTileWriter mtw = new OruxMapTileWriterDB();
OruxMapTileBuilder mapTileBuilder = new OruxMapTileBuilder(this, mtw);
// customTileCount = mapTileBuilder.getCustomTileCount();
atlasProgress.initMapCreation(mapTileBuilder.getCustomTileCount());
mapTileBuilder.createTiles();
mtw.finalizeMap();
} catch (IOException e) {
throw new MapCreationException(map, e);
} finally {
ctp.cleanup();
}
}
@Override
protected String appendMapContent() {
return otrk2MapsContent.toString();
}
private class OruxMapTileWriterDB implements MapTileWriter {
private static final int MAX_BATCH_SIZE = 1000;
private int tileCounter = 0;
private Runtime r = Runtime.getRuntime();
public void writeTile(int tilex, int tiley, String tileType, byte[] tileData) throws IOException {
try {
prepStmt.setInt(1, tilex);
prepStmt.setInt(2, tiley);
prepStmt.setInt(3, zoom);
prepStmt.setBytes(4, tileData);
prepStmt.addBatch();
long heapAvailable = r.maxMemory() - r.totalMemory() + r.freeMemory();
tileCounter++;
if (heapAvailable < HEAP_MIN || tileCounter > MAX_BATCH_SIZE) {
commit();
}
} catch (SQLException e) {
throw new IOException(e);
}
}
private void commit() throws SQLException {
prepStmt.executeBatch();
prepStmt.clearBatch();
atlasProgress.incMapCreationProgress(tileCounter);
tileCounter = 0;
conn.commit();
System.gc();
}
public void finalizeMap() throws IOException {
try {
commit();
} catch (SQLException e) {
throw new IOException(e);
}
}
}
}