package org.osmdroid.tileprovider.modules; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.util.Log; import org.osmdroid.api.IMapView; import org.osmdroid.config.Configuration; import org.osmdroid.tileprovider.MapTile; import org.osmdroid.tileprovider.tilesource.ITileSource; import java.io.BufferedInputStream; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import static org.osmdroid.tileprovider.modules.DatabaseFileArchive.TABLE; /** * An implementation of {@link IFilesystemCache} based on the original TileWriter. It writes tiles to a sqlite database. * It does NOT support expiration and provides more of a MOBAC like functionality (non-expiring file archives). * Uses the same schema as MOBAC osm sqlite and the {@link DatabaseFileArchive} * <p> * https://github.com/osmdroid/osmdroid/issues/348 * @see SqlTileWriter * @see DatabaseFileArchive * @author Alex O'Ree * @since 5.2 7/8/16. */ public class SqliteArchiveTileWriter implements IFilesystemCache { final File db_file; final SQLiteDatabase db; final int questimate = 8000; static boolean hasInited = false; public SqliteArchiveTileWriter(String outputFile) throws Exception { // do this in the background because it takes a long time db_file = new File(outputFile); try { db = SQLiteDatabase.openOrCreateDatabase(db_file.getAbsolutePath(), null); } catch (Exception ex) { throw new Exception("Trouble creating database file at " + outputFile, ex); } try { db.execSQL("CREATE TABLE IF NOT EXISTS " + DatabaseFileArchive.TABLE + " (key INTEGER , provider TEXT, tile BLOB, PRIMARY KEY (key, provider));"); } catch (Throwable t) { t.printStackTrace(); Log.d(IMapView.LOGTAG, "error setting db schema, it probably exists already", t); // throw new IOException("Trouble creating database file"+ t.getMessage()); } } @Override public boolean saveFile(ITileSource pTileSourceInfo, MapTile pTile, InputStream pStream) { try { ContentValues cv = new ContentValues(); final long x = (long) pTile.getX(); final long y = (long) pTile.getY(); final long z = (long) pTile.getZoomLevel(); final long index = ((z << z) + x << z) + y; cv.put(DatabaseFileArchive.COLUMN_PROVIDER, pTileSourceInfo.name()); BufferedInputStream bis = new BufferedInputStream(pStream); List<Byte> list = new ArrayList<Byte>(); //ByteArrayBuffer baf = new ByteArrayBuffer(500); int current = 0; while ((current = bis.read()) != -1) { list.add((byte) current); } byte[] bits = new byte[list.size()]; for (int i = 0; i < list.size(); i++) bits[i] = list.get(i); cv.put(DatabaseFileArchive.COLUMN_KEY, index); cv.put(DatabaseFileArchive.COLUMN_TILE, bits); //this shouldn't happen, but just in case db.insert(DatabaseFileArchive.TABLE, null, cv); if (Configuration.getInstance().isDebugMode()) Log.d(IMapView.LOGTAG, "tile inserted " + pTileSourceInfo.name() + pTile.toString()); } catch (Throwable ex) { Log.e(IMapView.LOGTAG, "Unable to store cached tile from " + pTileSourceInfo.name() + " " + pTile.toString(), ex); } return false; } @Override public boolean exists(ITileSource pTileSource, MapTile pTile) { try { final String[] tile = {DatabaseFileArchive.COLUMN_TILE}; final long x = (long) pTile.getX(); final long y = (long) pTile.getY(); final long z = (long) pTile.getZoomLevel(); final long index = ((z << z) + x << z) + y; final Cursor cur = db.query(TABLE, tile, DatabaseFileArchive.COLUMN_KEY + " = " + index + " and " + DatabaseFileArchive.COLUMN_PROVIDER + " = '" + pTileSource.name() + "'", null, null, null, null); if (cur.getCount() != 0) { cur.close(); return true; } cur.close(); } catch (Throwable ex) { Log.e(IMapView.LOGTAG, "Unable to store cached tile from " + pTileSource.name() + " " + pTile.toString(), ex); } return false; } @Override public void onDetach() { if (db != null) db.close(); } @Override public boolean remove(ITileSource tileSource, MapTile tile) { //not supported return false; } }