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;
}