diff --git a/settings.gradle b/settings.gradle index 013d71c0..ddc74185 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,7 @@ include ':vtm' include ':vtm-android' include ':vtm-android-example' include ':vtm-android-gdx' +include ':vtm-android-mvt' include ':vtm-app' include ':vtm-desktop' include ':vtm-desktop-lwjgl' diff --git a/vtm-android-example/AndroidManifest.xml b/vtm-android-example/AndroidManifest.xml index 544e9a42..933ceab5 100644 --- a/vtm-android-example/AndroidManifest.xml +++ b/vtm-android-example/AndroidManifest.xml @@ -79,7 +79,10 @@ android:name=".MarkerOverlayActivity" android:configChanges="keyboardHidden|orientation|screenSize" /> + 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 + To run this sample activity, you need an MBTiles database installed on storage.\n\nadb push %s %s Exit diff --git a/vtm-android-example/src/org/oscim/android/test/MBTilesBitmapTileActivity.java b/vtm-android-example/src/org/oscim/android/test/MBTilesBitmapActivity.java similarity index 78% rename from vtm-android-example/src/org/oscim/android/test/MBTilesBitmapTileActivity.java rename to vtm-android-example/src/org/oscim/android/test/MBTilesBitmapActivity.java index 63057626..e49b0abf 100644 --- a/vtm-android-example/src/org/oscim/android/test/MBTilesBitmapTileActivity.java +++ b/vtm-android-example/src/org/oscim/android/test/MBTilesBitmapActivity.java @@ -1,6 +1,7 @@ /* * Copyright 2019 Andrea Antonello * Copyright 2019 devemux86 + * Copyright 2019 Kostas Tzounopoulos * * 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 @@ -19,6 +20,7 @@ import android.app.AlertDialog; import android.content.DialogInterface; import android.os.Bundle; import org.oscim.android.tiling.source.mbtiles.MBTilesBitmapTileSource; +import org.oscim.android.tiling.source.mbtiles.MBTilesTileSource; import org.oscim.core.BoundingBox; import org.oscim.core.MapPosition; import org.oscim.core.Tile; @@ -27,19 +29,19 @@ import org.oscim.layers.tile.bitmap.BitmapTileLayer; import java.io.File; /** - * An example activity making use of MBTiles. + * An example activity making use of raster MBTiles. */ -public class MBTilesBitmapTileActivity extends BitmapTileActivity { +public class MBTilesBitmapActivity extends BitmapTileActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - File file = new File(getExternalFilesDir(null), "test.mbtiles"); + File file = new File(getExternalFilesDir(null), "raster.mbtiles"); if (!file.exists()) { AlertDialog.Builder builder = new AlertDialog.Builder(this) .setTitle(R.string.warning) - .setMessage(R.string.startup_message_mbtiles) + .setMessage(getResources().getString(R.string.startup_message_mbtiles, file.getName(), file.getAbsolutePath())) .setPositiveButton(R.string.exit, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -50,7 +52,8 @@ public class MBTilesBitmapTileActivity extends BitmapTileActivity { return; } - MBTilesBitmapTileSource tileSource = new MBTilesBitmapTileSource(file.getAbsolutePath(), 128, null); + MBTilesTileSource tileSource = new MBTilesBitmapTileSource(file.getAbsolutePath(), 128, null); + BitmapTileLayer bitmapLayer = new BitmapTileLayer(mMap, tileSource); mMap.layers().add(bitmapLayer); @@ -58,7 +61,7 @@ public class MBTilesBitmapTileActivity extends BitmapTileActivity { MapPosition pos = new MapPosition(); mMap.getMapPosition(pos); if (pos.x == 0.5 && pos.y == 0.5) { - BoundingBox bbox = tileSource.getBounds(); + BoundingBox bbox = tileSource.getDataSource().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/MBTilesMvtActivity.java b/vtm-android-example/src/org/oscim/android/test/MBTilesMvtActivity.java new file mode 100644 index 00000000..c5b4a31b --- /dev/null +++ b/vtm-android-example/src/org/oscim/android/test/MBTilesMvtActivity.java @@ -0,0 +1,93 @@ +/* + * Copyright 2019 Andrea Antonello + * Copyright 2019 devemux86 + * Copyright 2019 Kostas Tzounopoulos + * + * 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 org.oscim.android.mvt.tiling.source.mbtiles.MBTilesMvtTileSource; +import org.oscim.android.tiling.source.mbtiles.MBTilesTileSource; +import org.oscim.android.tiling.source.mbtiles.MBTilesUnsupportedException; +import org.oscim.core.BoundingBox; +import org.oscim.core.MapPosition; +import org.oscim.core.Tile; +import org.oscim.layers.tile.buildings.BuildingLayer; +import org.oscim.layers.tile.vector.VectorTileLayer; +import org.oscim.layers.tile.vector.labeling.LabelLayer; +import org.oscim.theme.VtmThemes; + +import java.io.File; + +/** + * An example activity making use of vector MBTiles. + */ +public class MBTilesMvtActivity extends MapActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + File file = new File(getExternalFilesDir(null), "vector.mbtiles"); + if (!file.exists()) { + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(R.string.warning) + .setMessage(getResources().getString(R.string.startup_message_mbtiles, file.getName(), file.getAbsolutePath())) + .setPositiveButton(R.string.exit, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + builder.show(); + return; + } + + MBTilesTileSource tileSource; + try { + tileSource = new MBTilesMvtTileSource(file.getAbsolutePath(), "en"); + } catch (MBTilesUnsupportedException e) { + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(R.string.warning) + .setMessage(e.getMessage()) + .setPositiveButton(R.string.exit, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + builder.show(); + return; + } + + VectorTileLayer l = mMap.setBaseMap(tileSource); + mMap.setTheme(VtmThemes.OPENMAPTILES); + + mMap.layers().add(new BuildingLayer(mMap, l)); + mMap.layers().add(new LabelLayer(mMap, l)); + + /* 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.getDataSource().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 49112bbe..79fdd26b 100644 --- a/vtm-android-example/src/org/oscim/android/test/Samples.java +++ b/vtm-android-example/src/org/oscim/android/test/Samples.java @@ -9,6 +9,7 @@ * Copyright 2018 boldtrn * Copyright 2018-2019 Gustl22 * Copyright 2019 Andrea Antonello + * Copyright 2019 Kostas Tzounopoulos * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -88,6 +89,7 @@ public class Samples extends Activity { linearLayout.addView(createLabel(null)); linearLayout.addView(createButton(SimpleMapActivity.class)); linearLayout.addView(createButton(MapsforgeActivity.class)); + linearLayout.addView(createButton(MBTilesMvtActivity.class)); linearLayout.addView(createButton(MapilionMvtActivity.class)); /*linearLayout.addView(createButton(MapzenMvtActivity.class)); linearLayout.addView(createButton(MapzenGeojsonActivity.class)); @@ -114,7 +116,7 @@ public class Samples extends Activity { linearLayout.addView(createLabel("Raster Maps")); linearLayout.addView(createButton(BitmapTileActivity.class)); - linearLayout.addView(createButton(MBTilesBitmapTileActivity.class)); + linearLayout.addView(createButton(MBTilesBitmapActivity.class)); linearLayout.addView(createLabel("Overlays")); linearLayout.addView(createButton(MarkerOverlayActivity.class)); diff --git a/vtm-android-mvt/AndroidManifest.xml b/vtm-android-mvt/AndroidManifest.xml new file mode 100644 index 00000000..6a915c8c --- /dev/null +++ b/vtm-android-mvt/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/vtm-android-mvt/build.gradle b/vtm-android-mvt/build.gradle new file mode 100644 index 00000000..b40c36b2 --- /dev/null +++ b/vtm-android-mvt/build.gradle @@ -0,0 +1,55 @@ +apply plugin: 'com.android.library' +apply plugin: 'com.github.dcendents.android-maven' + +dependencies { + api project(':vtm-android') + api project(':vtm-mvt') +} + +android { + compileSdkVersion androidCompileSdk() + buildToolsVersion "$androidBuildVersionTools" + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + defaultConfig { + versionCode project.versionCode() + versionName project.versionName() + minSdkVersion androidMinSdk() + targetSdkVersion androidTargetSdk() + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + } + debug.setRoot('build-types/debug') + release.setRoot('build-types/release') + } + + lintOptions { abortOnError false } +} + +android.libraryVariants.all { variant -> + def name = variant.buildType.name + if (name == "debug") + return + + def jar = project.tasks.create "jar${name.capitalize()}", Jar + jar.dependsOn variant.javaCompileProvider + jar.exclude '**/BuildConfig.class' + jar.exclude '**/R.class' + jar.exclude '**/R$*.class' + jar.from variant.javaCompileProvider.get().destinationDir + artifacts.add('archives', jar) +} + +if (project.hasProperty("SONATYPE_USERNAME")) { + afterEvaluate { + project.apply from: "${rootProject.projectDir}/deploy.gradle" + } +} diff --git a/vtm-android-mvt/src/org/oscim/android/mvt/tiling/source/mbtiles/MBTilesMvtTileDataSource.java b/vtm-android-mvt/src/org/oscim/android/mvt/tiling/source/mbtiles/MBTilesMvtTileDataSource.java new file mode 100644 index 00000000..b6ed4f52 --- /dev/null +++ b/vtm-android-mvt/src/org/oscim/android/mvt/tiling/source/mbtiles/MBTilesMvtTileDataSource.java @@ -0,0 +1,144 @@ +/* + * Copyright 2019 Kostas Tzounopoulos + * + * 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.mvt.tiling.source.mbtiles; + +import android.database.Cursor; +import org.oscim.android.tiling.source.mbtiles.MBTilesTileDataSource; +import org.oscim.android.tiling.source.mbtiles.MBTilesUnsupportedException; +import org.oscim.core.MercatorProjection; +import org.oscim.layers.tile.MapTile; +import org.oscim.tiling.ITileDataSink; +import org.oscim.tiling.OverzoomDataSink; +import org.oscim.tiling.QueryResult; +import org.oscim.tiling.source.mvt.MvtTileDecoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.zip.GZIPInputStream; + +/** + * A tile data source for MBTiles vector databases. + */ +public class MBTilesMvtTileDataSource extends MBTilesTileDataSource { + + private static final Logger log = LoggerFactory.getLogger(MBTilesMvtTileDataSource.class); + + private static final List SUPPORTED_FORMATS = Collections.singletonList("pbf"); + private static final String WHERE_FORMAT = "zoom_level=%d AND tile_column=%d AND tile_row=%d"; + + private final String mLanguage; + + private final ThreadLocal mThreadLocalDecoders = new ThreadLocal() { + @Override + protected MvtTileDecoder initialValue() { + return new MvtTileDecoder(mLanguage); + } + }; + + /** + * Create a tile data source for MBTiles vector databases. + * + * @param path the path to the MBTiles database. + * @param language the language to use when rendering the MBTiles. + */ + public MBTilesMvtTileDataSource(String path, String language) { + super(path); + mLanguage = language != null ? language : "en"; + + try { + assertDatabaseFormat(); + } catch (MBTilesUnsupportedException e) { + log.error("Invalid MBTiles database", e); + } + } + + @Override + public void cancel() { + // do nothing + } + + @Override + public void dispose() { + if (mDatabase != null && mDatabase.isOpen()) + mDatabase.close(); + } + + @Override + public List getSupportedFormats() { + return SUPPORTED_FORMATS; + } + + private MapTile mapTile(Cursor cursor) { + int tileX = cursor.getInt(cursor.getColumnIndexOrThrow("tile_column")); + int tileY = cursor.getInt(cursor.getColumnIndexOrThrow("tile_row")); + int zoomLevel = cursor.getInt(cursor.getColumnIndexOrThrow("zoom_level")); + long tmsTileY = MercatorProjection.tileYToTMS(tileY, (byte) zoomLevel); + return new MapTile(tileX, (int) tmsTileY, zoomLevel); + } + + /** + * Overzoom on the DB layer: generate a query for all tiles with lower zoomLevel than the one requested. + */ + private String overzoomQuery(MapTile tile) { + long tmsTileY = MercatorProjection.tileYToTMS(tile.tileY, tile.zoomLevel); + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int zoomLevel = tile.zoomLevel - 1; zoomLevel > 0; zoomLevel--) { + int diff = tile.zoomLevel - zoomLevel; + sb.append(String.format(Locale.US, WHERE_FORMAT, zoomLevel, tile.tileX >> diff, tmsTileY >> diff)); + if (zoomLevel > 1) // Not the last iteration + sb.append(") OR ("); + } + sb.append(")"); + return String.format(SELECT_TILES_FORMAT, sb.toString()); + } + + @Override + public void query(MapTile requestTile, ITileDataSink requestDataSink) { + Cursor cursor = null; + ITileDataSink responseDataSink = requestDataSink; + try { + long tmsTileY = MercatorProjection.tileYToTMS(requestTile.tileY, requestTile.zoomLevel); + cursor = mDatabase.rawQuery(String.format(SELECT_TILES_FORMAT, String.format(Locale.US, WHERE_FORMAT, requestTile.zoomLevel, requestTile.tileX, tmsTileY)), null); + + if (cursor.getCount() == 0) { + cursor.close(); + cursor = mDatabase.rawQuery(overzoomQuery(requestTile), null); + } + + if (cursor.moveToFirst()) { + byte[] bytes = cursor.getBlob(cursor.getColumnIndexOrThrow("tile_data")); + + MapTile responseTile = mapTile(cursor); + if (requestTile.zoomLevel != responseTile.zoomLevel) + responseDataSink = new OverzoomDataSink(requestDataSink, responseTile, requestTile); + + boolean success = mThreadLocalDecoders.get().decode(responseTile, responseDataSink, new GZIPInputStream(new ByteArrayInputStream(bytes))); + responseDataSink.completed(success ? QueryResult.SUCCESS : QueryResult.FAILED); + } else + responseDataSink.completed(QueryResult.TILE_NOT_FOUND); + } catch (IOException e) { + responseDataSink.completed(QueryResult.FAILED); + } finally { + if (cursor != null) + cursor.close(); + } + } +} diff --git a/vtm-android-mvt/src/org/oscim/android/mvt/tiling/source/mbtiles/MBTilesMvtTileSource.java b/vtm-android-mvt/src/org/oscim/android/mvt/tiling/source/mbtiles/MBTilesMvtTileSource.java new file mode 100644 index 00000000..9a66a9f4 --- /dev/null +++ b/vtm-android-mvt/src/org/oscim/android/mvt/tiling/source/mbtiles/MBTilesMvtTileSource.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Kostas Tzounopoulos + * + * 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.mvt.tiling.source.mbtiles; + +import org.oscim.android.tiling.source.mbtiles.MBTilesTileSource; + +/** + * A tile source for MBTiles vector databases. + */ +public class MBTilesMvtTileSource extends MBTilesTileSource { + + /** + * Create a tile source for MBTiles vector databases. + * + * @param path the path to the MBTiles database. + */ + public MBTilesMvtTileSource(String path) { + this(path, null); + } + + /** + * Create a tile source for MBTiles vector databases. + * + * @param path the path to the MBTiles database. + * @param language the language to use when rendering the MBTiles. + */ + public MBTilesMvtTileSource(String path, String language) { + super(new MBTilesMvtTileDataSource(path, language)); + } +} 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 index 8138c85f..7f12f4a3 100644 --- a/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesBitmapTileDataSource.java +++ b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesBitmapTileDataSource.java @@ -1,6 +1,7 @@ /* * Copyright 2019 Andrea Antonello * Copyright 2019 devemux86 + * Copyright 2019 Kostas Tzounopoulos * * 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 @@ -16,144 +17,70 @@ 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; +import java.util.Arrays; +import java.util.List; /** * A tile data source for MBTiles raster databases. */ -public class MBTilesBitmapTileDataSource implements ITileDataSource { +public class MBTilesBitmapTileDataSource extends MBTilesTileDataSource { 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 static final List SUPPORTED_FORMATS = Arrays.asList("png", "jpg", "jpeg"); private final Integer mAlpha; - private final SQLiteDatabase mDatabase; - private Map mMetadata; private final Integer mTransparentColor; /** - * Create a MBTiles tile data source. + * Create a tile data source for MBTiles raster databases. * * @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); + public MBTilesBitmapTileDataSource(String path, Integer alpha, Integer transparentColor) { + super(path); mAlpha = alpha; mTransparentColor = transparentColor; + + try { + assertDatabaseFormat(); + } catch (MBTilesUnsupportedException e) { + log.error("Invalid MBTiles database", e); + } } @Override public void cancel() { - mDatabase.close(); + if (mDatabase != null && mDatabase.isOpen()) + mDatabase.close(); } @Override public void dispose() { - mDatabase.close(); + if (mDatabase != null && mDatabase.isOpen()) + 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"); + @Override + public List getSupportedFormats() { + return SUPPORTED_FORMATS; } private static android.graphics.Bitmap processAlpha(android.graphics.Bitmap bitmap, int alpha) { @@ -216,9 +143,9 @@ public class MBTilesBitmapTileDataSource implements ITileDataSource { 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)}); + cursor = mDatabase.rawQuery(String.format(MBTilesTileDataSource.SELECT_TILES_FORMAT, MBTilesTileDataSource.WHERE_FORMAT), new String[]{String.valueOf(zoomLevel), String.valueOf(tileX), String.valueOf(tmsTileY)}); if (cursor.moveToFirst()) - return cursor.getBlob(0); + return cursor.getBlob(cursor.getColumnIndexOrThrow("tile_data")); } finally { if (cursor != null) cursor.close(); 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 index c37ed377..90c177ec 100644 --- a/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesBitmapTileSource.java +++ b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesBitmapTileSource.java @@ -1,6 +1,7 @@ /* * Copyright 2019 Andrea Antonello * Copyright 2019 devemux86 + * Copyright 2019 Kostas Tzounopoulos * * 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 @@ -15,19 +16,13 @@ */ 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; +public class MBTilesBitmapTileSource extends MBTilesTileSource { /** - * Create a MBTiles tile source. + * Create a tile source for MBTiles raster databases. * * @param path the path to the MBTiles database. */ @@ -36,64 +31,13 @@ public class MBTilesBitmapTileSource extends TileSource { } /** - * Create a MBTiles tile source. + * Create a tile source for MBTiles raster databases. * * @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; + super(new MBTilesBitmapTileDataSource(path, alpha, transparentColor)); } } diff --git a/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesTileDataSource.java b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesTileDataSource.java new file mode 100644 index 00000000..f39b406c --- /dev/null +++ b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesTileDataSource.java @@ -0,0 +1,154 @@ +/* + * Copyright 2019 Andrea Antonello + * Copyright 2019 devemux86 + * Copyright 2019 Kostas Tzounopoulos + * + * 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.text.TextUtils; +import org.oscim.core.BoundingBox; +import org.oscim.core.MapPosition; +import org.oscim.map.Viewport; +import org.oscim.tiling.ITileDataSource; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A tile data source for MBTiles databases. + */ +public abstract class MBTilesTileDataSource implements ITileDataSource { + + private static final String SELECT_METADATA = "SELECT name, value FROM metadata"; + protected static final String SELECT_TILES_FORMAT = + "SELECT zoom_level, tile_column, tile_row, tile_data " + + "FROM tiles " + + "WHERE %s " + + "ORDER BY zoom_level DESC " + + "LIMIT 1"; + static final String WHERE_FORMAT = "zoom_level=? AND tile_column=? AND tile_row=?"; + + protected final SQLiteDatabase mDatabase; + private Map mMetadata; + + public MBTilesTileDataSource(String path) { + mDatabase = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY); + } + + protected void assertDatabaseFormat() { + String format = getFormat(); + + if (format == null) + throw new RuntimeException("'metadata.format' field was not found. Is this an MBTiles database?"); + + List supportedFormats = getSupportedFormats(); + if (!supportedFormats.contains(format)) + throw new MBTilesUnsupportedException(String.format("Unsupported MBTiles 'metadata.format: %s'. Supported format(s) are: %s", format, TextUtils.join(", ", supportedFormats))); + } + + public String getAttribution() { + return getMetadata().get("attribution"); + } + + public BoundingBox getBounds() { + String bounds = getMetadata().get("bounds"); + if (bounds == null) + return 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); + } + + public MapPosition getCenter() { + String center = getMetadata().get("center"); + if (center == null) + return null; + String[] split = center.split(","); + double latitude = Double.parseDouble(split[1]); + double longitude = Double.parseDouble(split[0]); + int zoomLevel = Integer.parseInt(split[2]); + return new MapPosition(latitude, longitude, 1 << zoomLevel); + } + + public String getDescription() { + return getMetadata().get("description"); + } + + public String getFormat() { + return getMetadata().get("format"); + } + + public String getId() { + return getMetadata().get("id"); + } + + public String getJson() { + return getMetadata().get("json"); + } + + public int getMaxZoom() { + String maxZoom = getMetadata().get("maxzoom"); + return maxZoom != null ? Integer.parseInt(maxZoom) : 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; + } + + public int getMinZoom() { + String minZoom = getMetadata().get("minzoom"); + return minZoom != null ? Integer.parseInt(minZoom) : Viewport.MIN_ZOOM_LEVEL; + } + + public Long getMTime() { + String mTime = getMetadata().get("mtime"); + return mTime != null ? Long.parseLong(mTime) : null; + } + + public String getName() { + return getMetadata().get("name"); + } + + public Integer getPixelScale() { + String pixelScale = getMetadata().get("pixel_scale"); + return pixelScale != null ? Integer.parseInt(pixelScale) : null; + } + + abstract public List getSupportedFormats(); + + public String getVersion() { + return getMetadata().get("version"); + } +} diff --git a/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesTileSource.java b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesTileSource.java new file mode 100644 index 00000000..828f45d7 --- /dev/null +++ b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesTileSource.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Andrea Antonello + * Copyright 2019 devemux86 + * Copyright 2019 Kostas Tzounopoulos + * + * 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.tiling.TileSource; + +/** + * A tile source for MBTiles databases. + */ +public abstract class MBTilesTileSource extends TileSource { + + private final MBTilesTileDataSource mTileDataSource; + + public MBTilesTileSource(MBTilesTileDataSource tileDataSource) { + mTileDataSource = tileDataSource; + } + + @Override + public void close() { + getDataSource().dispose(); + } + + @Override + public MBTilesTileDataSource getDataSource() { + return mTileDataSource; + } + + @Override + public OpenResult open() { + return OpenResult.SUCCESS; + } +} diff --git a/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesUnsupportedException.java b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesUnsupportedException.java new file mode 100644 index 00000000..f63c2b3f --- /dev/null +++ b/vtm-android/src/org/oscim/android/tiling/source/mbtiles/MBTilesUnsupportedException.java @@ -0,0 +1,21 @@ +/* + * Copyright 2019 Kostas Tzounopoulos + * + * 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; + +public class MBTilesUnsupportedException extends RuntimeException { + public MBTilesUnsupportedException(String message) { + super(message); + } +} diff --git a/vtm/src/org/oscim/tiling/OverzoomDataSink.java b/vtm/src/org/oscim/tiling/OverzoomDataSink.java index 99748d6b..f72b26dc 100644 --- a/vtm/src/org/oscim/tiling/OverzoomDataSink.java +++ b/vtm/src/org/oscim/tiling/OverzoomDataSink.java @@ -22,7 +22,7 @@ import org.oscim.core.Tile; import org.oscim.utils.geom.TileClipper; import org.oscim.utils.geom.TileSeparator; -class OverzoomDataSink implements ITileDataSink { +public class OverzoomDataSink implements ITileDataSink { private final ITileDataSink sink; @@ -30,7 +30,7 @@ class OverzoomDataSink implements ITileDataSink { private final TileSeparator separator; private final float dx, dy, scale; - OverzoomDataSink(ITileDataSink sink, Tile overzoomTile, Tile tile) { + public OverzoomDataSink(ITileDataSink sink, Tile overzoomTile, Tile tile) { this.sink = sink; int diff = tile.zoomLevel - overzoomTile.zoomLevel;