package org.mapsforge.android.maps.mapgenerator.mbtiles; import org.mapsforge.android.maps.mapgenerator.MapGeneratorJob; import org.mapsforge.android.maps.mapgenerator.MapRenderer; import org.mapsforge.core.model.BoundingBox; import org.mapsforge.core.model.GeoPoint; import org.mapsforge.core.model.Tile; import android.content.Context; import android.database.sqlite.SQLiteException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.util.Log; /** * @author Robert Oehler class which uses a local mbtiles database to render tiles for the mapsforge library */ public class MbTilesDatabaseRenderer implements MapRenderer { private final static String TAG = MbTilesDatabaseRenderer.class.getSimpleName(); private MbTilesDatabase db; private boolean isDBOpen = false; public MbTilesDatabaseRenderer(final Context pContext, final String pDBPath) { this.db = new MbTilesDatabase(pContext, pDBPath); } /** * called from MapWorker: executes a mapgeneratorJob and modifies the @param bitmap which will be the result * according to the parameters inside @param mapGeneratorJob */ @Override public boolean executeJob(MapGeneratorJob mapGeneratorJob, Bitmap bitmap) { final Tile tile = mapGeneratorJob.tile; long localTileX = tile.tileX; long localTileY = tile.tileY; // conversion needed to fit the MbTiles coordinate system final int[] tmsTileXY = googleTile2TmsTile(localTileX, localTileY, tile.zoomLevel); // Log.d(TAG,String.format("Tile requested %d %d is now %d %d", tile.tileX, tile.tileY, tmsTileXY[0], // tmsTileXY[1])); byte[] rasterBytes = null; Bitmap decodedBitmap = null; int[] pixels = new int[Tile.TILE_SIZE * Tile.TILE_SIZE]; rasterBytes = this.db.getTileAsBytes(String.valueOf(tmsTileXY[0]), String.valueOf(tmsTileXY[1]), Byte.toString(tile.zoomLevel)); if (rasterBytes == null) { // got nothing,return to zoom for higher zoom levels if (tile.zoomLevel > 11) { // TODO register the "real" max zoom level and compare here return false; } // got nothing,make white pixels for lower zoom levels for (int i = 0; i < pixels.length; i++) { pixels[i] = 0xff << 24 | (0xff << 16) | (0xff << 8) | 0xff; } } else { decodedBitmap = BitmapFactory.decodeByteArray(rasterBytes, 0, rasterBytes.length); // check if the input stream could be decoded into a bitmap if (decodedBitmap != null) { // copy all pixels from the decoded bitmap to the color array decodedBitmap.getPixels(pixels, 0, Tile.TILE_SIZE, 0, 0, Tile.TILE_SIZE, Tile.TILE_SIZE); decodedBitmap.recycle(); } else { for (int i = 0; i < pixels.length; i++) { pixels[i] = Color.WHITE; } } } if (bitmap == null) { Bitmap.Config conf = Bitmap.Config.ARGB_8888; bitmap = Bitmap.createBitmap(Tile.TILE_SIZE, Tile.TILE_SIZE, conf); } // copy all pixels from the color array to the tile bitmap bitmap.setPixels(pixels, 0, Tile.TILE_SIZE, 0, 0, Tile.TILE_SIZE, Tile.TILE_SIZE); return true; } /** * @return the center point of this database data's bounding box */ @Override public GeoPoint getStartPoint() { try { this.db.openDataBase(); final BoundingBox bb = this.db.getBoundingBox(); this.db.close(); return bb.getCenterPoint(); } catch (NullPointerException e) { Log.e(TAG, "NullPointerException getStartPoint", e); return null; } catch (SQLiteException e) { Log.e(TAG, "SQLiteException getStartPoint", e); return null; } } public BoundingBox getBoundingBox() { try { this.db.openDataBase(); final BoundingBox bb = this.db.getBoundingBox(); this.db.close(); return bb; } catch (NullPointerException e) { Log.e(TAG, "NullPointerException getBoundingBox", e); return null; } catch (SQLiteException e) { Log.e(TAG, "SQLiteException getBoundingBox", e); return null; } } /** * @return the default start zoom level of this renderer */ @Override public Byte getStartZoomLevel() { // this could be read from the db too, but actually Mapsforge uses the zoom the user is using anyway return Byte.valueOf((byte) 8); } /** * @return the max zoom level this renderer has data for */ @Override public byte getZoomLevelMax() { // this could be read from the db too, but to allow to zoom deeper higher levels are possible return 22; } /** * @return the currently used db file name */ @Override public String getFileName() { return this.db.getDBName(); } @Override public void start() { if (!this.isDBOpen) { this.db.openDataBase(); this.isDBOpen = true; } } @Override public void stop() { if (this.isDBOpen) { this.db.close(); this.isDBOpen = false; } } @Override public boolean isWorking() { return this.isDBOpen; } /** * closes and destroys any resources needed */ @Override public void destroy() { if (this.db != null) { stop(); this.db = null; } } /** * Converts Google tile coordinates to TMS Tile coordinates. * <p> * Code copied from: http://code.google.com/p/gmap-tile-generator/ * </p> * * @param tx * the x tile number. * @param ty * the y tile number. * @param zoom * the current zoom level. * @return the converted values. */ public static int[] googleTile2TmsTile(long tx, long ty, byte zoom) { return new int[] { (int) tx, (int) ((Math.pow(2, zoom) - 1) - ty) }; } }