diff --git a/vtm-android-example/AndroidManifest.xml b/vtm-android-example/AndroidManifest.xml index 45a68312..bbc07a7e 100644 --- a/vtm-android-example/AndroidManifest.xml +++ b/vtm-android-example/AndroidManifest.xml @@ -91,6 +91,9 @@ + diff --git a/vtm-android-example/res/values/strings.xml b/vtm-android-example/res/values/strings.xml index fa0a4521..205bf6c7 100644 --- a/vtm-android-example/res/values/strings.xml +++ b/vtm-android-example/res/values/strings.xml @@ -25,5 +25,8 @@ \'*\' or OSM key void or value Now + Warning + To run this sample activity, you need any MBTiles with filename test.mbtiles installed on storage.\n\nadb push file.mbtiles /sdcard/test.mbtiles + Exit diff --git a/vtm-android-example/src/org/oscim/android/test/MBTilesBitmapTileActivity.java b/vtm-android-example/src/org/oscim/android/test/MBTilesBitmapTileActivity.java new file mode 100644 index 00000000..2e9ed49e --- /dev/null +++ b/vtm-android-example/src/org/oscim/android/test/MBTilesBitmapTileActivity.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Andrea Antonello + * Copyright 2019 devemux86 + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ +package org.oscim.android.test; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Environment; +import org.oscim.android.tiling.source.mbtiles.MBTilesBitmapTileSource; +import org.oscim.core.BoundingBox; +import org.oscim.core.MapPosition; +import org.oscim.core.Tile; +import org.oscim.layers.tile.bitmap.BitmapTileLayer; + +import java.io.File; + +/** + * An example activity making use of MBTiles. + */ +public class MBTilesBitmapTileActivity extends BitmapTileActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + File file = new File(Environment.getExternalStorageDirectory(), "test.mbtiles"); + if (!file.exists()) { + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(R.string.warning) + .setMessage(R.string.startup_message_mbtiles) + .setPositiveButton(R.string.exit, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + builder.show(); + return; + } + + MBTilesBitmapTileSource tileSource = new MBTilesBitmapTileSource(file.getAbsolutePath(), 128, null); + BitmapTileLayer bitmapLayer = new BitmapTileLayer(mMap, tileSource); + mMap.layers().add(bitmapLayer); + + /* set initial position on first run */ + MapPosition pos = new MapPosition(); + mMap.getMapPosition(pos); + if (pos.x == 0.5 && pos.y == 0.5) { + BoundingBox bbox = tileSource.getBounds(); + if (bbox != null) { + pos.setByBoundingBox(bbox, Tile.SIZE * 4, Tile.SIZE * 4); + mMap.setMapPosition(pos); + } + } + } +} diff --git a/vtm-android-example/src/org/oscim/android/test/Samples.java b/vtm-android-example/src/org/oscim/android/test/Samples.java index e6720e48..113eebc5 100644 --- a/vtm-android-example/src/org/oscim/android/test/Samples.java +++ b/vtm-android-example/src/org/oscim/android/test/Samples.java @@ -8,6 +8,7 @@ * Copyright 2017 nebular * Copyright 2018 boldtrn * Copyright 2018-2019 Gustl22 + * Copyright 2019 Andrea Antonello * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -115,6 +116,7 @@ public class Samples extends Activity { linearLayout.addView(createLabel("Raster Maps")); linearLayout.addView(createButton(BitmapTileActivity.class)); + linearLayout.addView(createButton(MBTilesBitmapTileActivity.class)); linearLayout.addView(createLabel("Overlays")); linearLayout.addView(createButton(MarkerOverlayActivity.class)); diff --git a/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesBitmapTileDataSource.java b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesBitmapTileDataSource.java new file mode 100644 index 00000000..8138c85f --- /dev/null +++ b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesBitmapTileDataSource.java @@ -0,0 +1,228 @@ +/* + * Copyright 2019 Andrea Antonello + * Copyright 2019 devemux86 + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ +package org.oscim.android.tiling.source.mbtiles; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import org.oscim.backend.CanvasAdapter; +import org.oscim.backend.canvas.Bitmap; +import org.oscim.core.BoundingBox; +import org.oscim.core.MercatorProjection; +import org.oscim.layers.tile.MapTile; +import org.oscim.map.Viewport; +import org.oscim.tiling.ITileDataSink; +import org.oscim.tiling.ITileDataSource; +import org.oscim.tiling.QueryResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * A tile data source for MBTiles raster databases. + */ +public class MBTilesBitmapTileDataSource implements ITileDataSource { + + private static final Logger log = LoggerFactory.getLogger(MBTilesBitmapTileDataSource.class); + + private static final String TABLE_TILES = "tiles"; + private static final String COL_TILES_ZOOM_LEVEL = "zoom_level"; + private static final String COL_TILES_TILE_COLUMN = "tile_column"; + private static final String COL_TILES_TILE_ROW = "tile_row"; + private static final String COL_TILES_TILE_DATA = "tile_data"; + private static final String SELECT_TILES = "SELECT " + COL_TILES_TILE_DATA + " from " + TABLE_TILES + " where " + + COL_TILES_ZOOM_LEVEL + "=? AND " + COL_TILES_TILE_COLUMN + "=? AND " + COL_TILES_TILE_ROW + "=?"; + + private static final String TABLE_METADATA = "metadata"; + private static final String COL_METADATA_NAME = "name"; + private static final String COL_METADATA_VALUE = "value"; + private static final String SELECT_METADATA = "select " + COL_METADATA_NAME + "," + COL_METADATA_VALUE + " from " + + TABLE_METADATA; + + private final Integer mAlpha; + private final SQLiteDatabase mDatabase; + private Map mMetadata; + private final Integer mTransparentColor; + + /** + * Create a MBTiles tile data source. + * + * @param path the path to the MBTiles database. + * @param alpha an optional alpha value [0-255] to make the tiles transparent. + * @param transparentColor an optional color that will be made transparent in the bitmap. + */ + MBTilesBitmapTileDataSource(String path, Integer alpha, Integer transparentColor) { + mDatabase = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY); + mAlpha = alpha; + mTransparentColor = transparentColor; + } + + @Override + public void cancel() { + mDatabase.close(); + } + + @Override + public void dispose() { + mDatabase.close(); + } + + String getAttribution() { + return getMetadata().get("attribution"); + } + + BoundingBox getBounds() { + String bounds = getMetadata().get("bounds"); + if (bounds != null) { + String[] split = bounds.split(","); + double w = Double.parseDouble(split[0]); + double s = Double.parseDouble(split[1]); + double e = Double.parseDouble(split[2]); + double n = Double.parseDouble(split[3]); + return new BoundingBox(s, w, n, e); + } + return null; + } + + public String getDescription() { + return getMetadata().get("description"); + } + + /** + * @return the image format (jpg, png) + */ + public String getFormat() { + return getMetadata().get("format"); + } + + int getMaxZoom() { + String maxZoom = getMetadata().get("maxzoom"); + if (maxZoom != null) + return Integer.parseInt(maxZoom); + return Viewport.MAX_ZOOM_LEVEL; + } + + private Map getMetadata() { + if (mMetadata == null) { + mMetadata = new HashMap<>(); + Cursor cursor = null; + try { + cursor = mDatabase.rawQuery(SELECT_METADATA, null); + while (cursor.moveToNext()) { + String key = cursor.getString(0); + String value = cursor.getString(1); + mMetadata.put(key, value); + } + } finally { + if (cursor != null) + cursor.close(); + } + } + return mMetadata; + } + + int getMinZoom() { + String minZoom = getMetadata().get("minzoom"); + if (minZoom != null) + return Integer.parseInt(minZoom); + return Viewport.MIN_ZOOM_LEVEL; + } + + String getName() { + return getMetadata().get("name"); + } + + public String getVersion() { + return getMetadata().get("version"); + } + + private static android.graphics.Bitmap processAlpha(android.graphics.Bitmap bitmap, int alpha) { + android.graphics.Bitmap newBitmap = android.graphics.Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), android.graphics.Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(newBitmap); + Paint paint = new Paint(); + paint.setAlpha(alpha); + canvas.drawBitmap(bitmap, 0, 0, paint); + return newBitmap; + } + + private static android.graphics.Bitmap processTransparentColor(android.graphics.Bitmap bitmap, int colorToRemove) { + int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()]; + bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); + for (int i = 0; i < pixels.length; i++) { + if (pixels[i] == colorToRemove) + pixels[i] = Color.alpha(Color.TRANSPARENT); + } + android.graphics.Bitmap newBitmap = android.graphics.Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), android.graphics.Bitmap.Config.ARGB_8888); + newBitmap.setPixels(pixels, 0, newBitmap.getWidth(), 0, 0, newBitmap.getWidth(), newBitmap.getHeight()); + return newBitmap; + } + + @Override + public void query(MapTile tile, ITileDataSink sink) { + QueryResult res = QueryResult.FAILED; + try { + byte[] bytes = readTile(tile.tileX, tile.tileY, tile.zoomLevel); + + if (mTransparentColor != null || mAlpha != null) { + android.graphics.Bitmap androidBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + if (mTransparentColor != null) + androidBitmap = processTransparentColor(androidBitmap, mTransparentColor); + if (mAlpha != null) + androidBitmap = processAlpha(androidBitmap, mAlpha); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + androidBitmap.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, bos); + bytes = bos.toByteArray(); + } + + Bitmap bitmap = CanvasAdapter.decodeBitmap(new ByteArrayInputStream(bytes)); + sink.setTileImage(bitmap); + res = QueryResult.SUCCESS; + } catch (Exception e) { + log.debug("{} invalid bitmap", tile); + } finally { + sink.completed(res); + } + } + + /** + * Read a Tile's image bytes from the MBTiles database. + * + * @param tileX the x tile index. + * @param tileY the y tile index (OSM notation). + * @param zoomLevel the zoom level. + * @return the tile image bytes. + */ + private byte[] readTile(int tileX, int tileY, byte zoomLevel) { + Cursor cursor = null; + try { + long tmsTileY = MercatorProjection.tileYToTMS(tileY, zoomLevel); + cursor = mDatabase.rawQuery(SELECT_TILES, new String[]{String.valueOf(zoomLevel), String.valueOf(tileX), String.valueOf(tmsTileY)}); + if (cursor.moveToFirst()) + return cursor.getBlob(0); + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } +} diff --git a/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesBitmapTileSource.java b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesBitmapTileSource.java new file mode 100644 index 00000000..c37ed377 --- /dev/null +++ b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesBitmapTileSource.java @@ -0,0 +1,99 @@ +/* + * Copyright 2019 Andrea Antonello + * Copyright 2019 devemux86 + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ +package org.oscim.android.tiling.source.mbtiles; + +import org.oscim.core.BoundingBox; +import org.oscim.tiling.ITileDataSource; +import org.oscim.tiling.TileSource; + +/** + * A tile source for MBTiles raster databases. + */ +public class MBTilesBitmapTileSource extends TileSource { + + private final MBTilesBitmapTileDataSource mTileDataSource; + + /** + * Create a MBTiles tile source. + * + * @param path the path to the MBTiles database. + */ + public MBTilesBitmapTileSource(String path) { + this(path, null, null); + } + + /** + * Create a MBTiles tile source. + * + * @param path the path to the MBTiles database. + * @param alpha an optional alpha value [0-255] to make the tiles transparent. + * @param transparentColor an optional color that will be made transparent in the bitmap. + */ + public MBTilesBitmapTileSource(String path, Integer alpha, Integer transparentColor) { + mTileDataSource = new MBTilesBitmapTileDataSource(path, alpha, transparentColor); + } + + @Override + public void close() { + mTileDataSource.dispose(); + } + + + public String getAttribution() { + return mTileDataSource.getAttribution(); + } + + public BoundingBox getBounds() { + return mTileDataSource.getBounds(); + } + + @Override + public ITileDataSource getDataSource() { + return mTileDataSource; + } + + public String getDescription() { + return mTileDataSource.getDescription(); + } + + /** + * @return the image format (jpg, png) + */ + public String getFormat() { + return mTileDataSource.getFormat(); + } + + public int getMaxZoom() { + return mTileDataSource.getMaxZoom(); + } + + public int getMinZoom() { + return mTileDataSource.getMinZoom(); + } + + public String getName() { + return mTileDataSource.getName(); + } + + public String getVersion() { + return mTileDataSource.getVersion(); + } + + @Override + public OpenResult open() { + return OpenResult.SUCCESS; + } +} diff --git a/vtm/src/org/oscim/core/MercatorProjection.java b/vtm/src/org/oscim/core/MercatorProjection.java index e4c81020..58b108d3 100644 --- a/vtm/src/org/oscim/core/MercatorProjection.java +++ b/vtm/src/org/oscim/core/MercatorProjection.java @@ -2,7 +2,8 @@ * Copyright 2010, 2011, 2012 mapsforge.org * Copyright 2012 Hannes Janetzek * Copyright 2014 Ludwig M Brinckmann - * Copyright 2016-2017 devemux86 + * Copyright 2016-2019 devemux86 + * Copyright 2019 Andrea Antonello * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -577,6 +578,17 @@ public final class MercatorProjection { return pixelYToLatitude(tileY * Tile.SIZE, getMapSize(zoomLevel)); } + /** + * Converts a tile Y number at a certain zoom level to TMS notation. + * + * @param tileY the tile Y number that should be converted. + * @param zoomLevel the zoom level at which the number should be converted. + * @return the TMS value of the tile Y number. + */ + public static long tileYToTMS(long tileY, byte zoomLevel) { + return (long) (zoomLevelToScale(zoomLevel) - tileY - 1); + } + public static double toLatitude(double y) { return 90 - 360 * Math.atan(Math.exp((y - 0.5) * (2 * Math.PI))) / Math.PI; }