diff --git a/osmdroid-geopackage/README.md b/osmdroid-geopackage/README.md new file mode 100644 index 0000000000..3207fb355c --- /dev/null +++ b/osmdroid-geopackage/README.md @@ -0,0 +1,13 @@ +# Geopackage support for osmdroid + +https://github.com/ngageoint/geopackage-android + + +# License +This library: ASF 2.0 +Dependencies: NGA code is MIT + + +# How to use + +TODO \ No newline at end of file diff --git a/osmdroid-geopackage/build.gradle b/osmdroid-geopackage/build.gradle new file mode 100644 index 0000000000..4b1295f437 --- /dev/null +++ b/osmdroid-geopackage/build.gradle @@ -0,0 +1,39 @@ +description = 'Geopackage support for osmdroid' + +apply plugin: 'com.android.application' +apply from: "https://raw.githubusercontent.com/gradle-fury/gradle-fury/v1.1.3/gradle/android-support.gradle" + +android { + + defaultConfig { + applicationId "org.osmdroid.gpkg" + minSdkVersion 14 + targetSdkVersion 22 + } +} + +dependencies { + //crash logging + //compile 'ch.acra:acra:4.7.0' + + testCompile 'junit:junit:4.12' + compile project(':osmdroid-android') + + compile "mil.nga.geopackage:geopackage-android:1.3.0" + + compile 'com.google.android.gms:play-services:8.4.0' + compile 'com.google.maps.android:android-maps-utils:0.4.3' + compile 'com.j256.ormlite:ormlite-android:4.48' + compile 'mil.nga.geopackage:geopackage-core:1.2.0' + compile 'ar.com.hjg:pngj:2.1.0' + compile 'mil.nga:tiff:1.0.0' + + //compile 'com.j256.ormlite:ormlite-android:4.48' + //compile 'mil.nga.geopackage:geopackage-core:1.2.0' + //compile 'mil.nga.geopackage:geopackage:1.2.0' + + //compile 'org.sqldroid:sqldroid:1.1.0-SNAPSHOT' + //compile 'org.xerial:sqlite-jdbc:3.7.2' + //compile 'mil.nga.geopackage:geopackage-core:1.1.0' + +} diff --git a/osmdroid-geopackage/src/androidTest/java/org/osmdroid/ApplicationTest.java b/osmdroid-geopackage/src/androidTest/java/org/osmdroid/ApplicationTest.java new file mode 100644 index 0000000000..a759b4bc08 --- /dev/null +++ b/osmdroid-geopackage/src/androidTest/java/org/osmdroid/ApplicationTest.java @@ -0,0 +1,13 @@ +package org.osmdroid; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/osmdroid-geopackage/src/main/AndroidManifest.xml b/osmdroid-geopackage/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..4b3d74d8e0 --- /dev/null +++ b/osmdroid-geopackage/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/osmdroid-geopackage/src/main/java/org/osmdroid/gpkg/GeoPackageMapTileModuleProvider.java b/osmdroid-geopackage/src/main/java/org/osmdroid/gpkg/GeoPackageMapTileModuleProvider.java new file mode 100644 index 0000000000..3b03cc42ba --- /dev/null +++ b/osmdroid-geopackage/src/main/java/org/osmdroid/gpkg/GeoPackageMapTileModuleProvider.java @@ -0,0 +1,236 @@ +package org.osmdroid.gpkg; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import org.osmdroid.api.IMapView; +import org.osmdroid.config.Configuration; +import org.osmdroid.tileprovider.MapTile; +import org.osmdroid.tileprovider.MapTileRequestState; +import org.osmdroid.tileprovider.modules.IFilesystemCache; +import org.osmdroid.tileprovider.modules.MapTileModuleProviderBase; +import org.osmdroid.tileprovider.tilesource.ITileSource; +import org.osmdroid.tileprovider.tilesource.XYTileSource; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import mil.nga.geopackage.GeoPackage; +import mil.nga.geopackage.GeoPackageManager; +import mil.nga.geopackage.factory.GeoPackageFactory; +import mil.nga.geopackage.tiles.retriever.GeoPackageTile; +import mil.nga.geopackage.tiles.retriever.GeoPackageTileRetriever; +import mil.nga.geopackage.tiles.user.TileDao; + +/** + * This is called the GeoPackage Slowmo because it's damn slow. + * Created by alex on 10/29/15. + */ +public class GeoPackageMapTileModuleProvider extends MapTileModuleProviderBase { + + //TileRetriever retriever; + IFilesystemCache tileWriter = null; + GeoPackageManager manager; + + //GeoPackage geoPackage; + List tiles; + ITileSource currentTileSource; + Set tileSources = new HashSet<>(); + /** + * Compress format + */ + private static final Bitmap.CompressFormat COMPRESS_FORMAT = Bitmap.CompressFormat.PNG; + + + + + public GeoPackageMapTileModuleProvider(File[] pFile, + final Context context, IFilesystemCache cache) { + //int pThreadPoolSize, final int pPendingQueueSize + super(Configuration.getInstance().getTileFileSystemThreads(), Configuration.getInstance().getTileFileSystemMaxQueueSize()); + + tileWriter = cache; + // Get a manager + manager = GeoPackageFactory.getManager(context); + // Available databases + + + // Import database + for (int i = 0; i < pFile.length; i++) { + try { + boolean imported = manager.importGeoPackage((pFile[i])); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + // Available databases + List databases = manager.databases(); + // Open database + for (int i = 0; i < databases.size(); i++) { + tileSources.add(manager.open(databases.get(i))); + } + + } + + + public Drawable getMapTile(MapTile pTile) { + + + Drawable tile = null; + + + String src = currentTileSource.name(); //table name + //String database = currentTileSource.getBaseUrls()[0]; //database name + + GeoPackage next = null; + boolean found = false; + //find out db connection + Iterator iterator = tileSources.iterator(); + while (iterator.hasNext()) { + next = iterator.next(); + if (next.getTileTables().contains(src)) { + found = true; + break; + } + } + + + if (found) { + TileDao tileDao = next.getTileDao(src); + GeoPackageTileRetriever retriever = new GeoPackageTileRetriever(tileDao); + + int zoom = pTile.getZoomLevel(); + int x = pTile.getX(); + int y = pTile.getY(); + + + GeoPackageTile geoPackageTile = retriever.getTile(x, y, zoom); + if (geoPackageTile != null && geoPackageTile.data != null) { + byte[] image = geoPackageTile.data; + if (image != null) { + BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.outHeight = 256; //360 + opt.outWidth = 256;//248 + Bitmap imageBitmap = BitmapFactory.decodeByteArray(image, 0, image.length, opt); + tile = new BitmapDrawable(imageBitmap); + /*Date dateExpires; + Long override=Configuration.getInstance().getExpirationOverrideDuration(); + if (override!=null) { + dateExpires= new Date(System.currentTimeMillis() + override); + } else { + dateExpires = new Date(System.currentTimeMillis() + OpenStreetMapTileProviderConstants.DEFAULT_MAXIMUM_CACHED_FILE_AGE + Configuration.getInstance().getExpirationExtendedDuration()); + } + pTile.setExpires(dateExpires); + tileWriter.saveFile(src, pTile, new ByteArrayInputStream(image)); + */ + } + } + } + + return tile; + + } + + public static class Container { + String database; + List tiles; + List features; + } + + + public List getTileSources() { + List srcs = new ArrayList<>(); + List databases = manager.databases(); + for (int i = 0; i < databases.size(); i++) { + GeoPackage handle = manager.open(databases.get(i)); + Container c = new Container(); + c.database = databases.get(i); + c.tiles = new ArrayList<>(); + c.tiles.addAll(handle.getTileTables()); + c.features = new ArrayList<>(); + c.features.addAll(handle.getFeatureTables()); + srcs.add(c); + } + + return srcs; + } + + @Override + public void detach() { + + + Iterator iterator = tileSources.iterator(); + while (iterator.hasNext()){ + iterator.next().close(); + } + tileSources.clear(); + + manager = null; + } + + + protected class TileLoader extends MapTileModuleProviderBase.TileLoader { + + @Override + public Drawable loadTile(final MapTileRequestState pState) { + + final MapTile pTile = pState.getMapTile(); + + try { + Drawable mapTile = getMapTile(pTile); + return mapTile; + } catch (final Throwable e) { + Log.e(IMapView.LOGTAG, "Error loading tile", e); + } finally { + } + + return null; + } + } + + @Override + protected String getName() { + return "Geopackage"; + } + + @Override + protected String getThreadGroupName() { + return getName(); + } + + @Override + protected Runnable getTileLoader() { + return new TileLoader(); + } + + @Override + public boolean getUsesDataConnection() { + return false; + } + + @Override + public int getMinimumZoomLevel() { + return 0; + } + + @Override + public int getMaximumZoomLevel() { + return 22; + } + + @Override + public void setTileSource(ITileSource tileSource) { + currentTileSource = tileSource; + } + + +} diff --git a/osmdroid-geopackage/src/main/java/org/osmdroid/gpkg/GeoPackageProvider.java b/osmdroid-geopackage/src/main/java/org/osmdroid/gpkg/GeoPackageProvider.java new file mode 100644 index 0000000000..4d78c30731 --- /dev/null +++ b/osmdroid-geopackage/src/main/java/org/osmdroid/gpkg/GeoPackageProvider.java @@ -0,0 +1,99 @@ +package org.osmdroid.gpkg; + +import android.content.Context; +import android.os.Build; + +import org.osmdroid.tileprovider.IMapTileProviderCallback; +import org.osmdroid.tileprovider.IRegisterReceiver; +import org.osmdroid.tileprovider.MapTileProviderArray; +import org.osmdroid.tileprovider.modules.IFilesystemCache; +import org.osmdroid.tileprovider.modules.INetworkAvailablityCheck; +import org.osmdroid.tileprovider.modules.MapTileDownloader; +import org.osmdroid.tileprovider.modules.MapTileFilesystemProvider; +import org.osmdroid.tileprovider.modules.MapTileSqlCacheProvider; +import org.osmdroid.tileprovider.modules.NetworkAvailabliltyCheck; +import org.osmdroid.tileprovider.modules.SqlTileWriter; +import org.osmdroid.tileprovider.modules.TileWriter; +import org.osmdroid.tileprovider.tilesource.ITileSource; +import org.osmdroid.tileprovider.tilesource.TileSourceFactory; +import org.osmdroid.tileprovider.tilesource.XYTileSource; +import org.osmdroid.tileprovider.util.SimpleRegisterReceiver; + +import java.io.File; + +/** + * GeoPackage + + * created on 1/5/2017. + * + * @author Alex O'Ree + */ + +public class GeoPackageProvider extends MapTileProviderArray implements IMapTileProviderCallback { + + protected GeoPackageMapTileModuleProvider geopackage; + protected IFilesystemCache tileWriter; + + public GeoPackageProvider(File[] db, Context context) { + this(new SimpleRegisterReceiver(context), new NetworkAvailabliltyCheck(context), + TileSourceFactory.DEFAULT_TILE_SOURCE, context, null, db); + } + + + public GeoPackageProvider(final IRegisterReceiver pRegisterReceiver, + final INetworkAvailablityCheck aNetworkAvailablityCheck, final ITileSource pTileSource, + final Context pContext, final IFilesystemCache cacheWriter, File[] databases) { + + + super(pTileSource, pRegisterReceiver); + + + if (cacheWriter != null) { + tileWriter = cacheWriter; + } else { + if (Build.VERSION.SDK_INT < 10) { + tileWriter = new TileWriter(); + } else { + tileWriter = new SqlTileWriter(); + } + } + + if (Build.VERSION.SDK_INT < 10) { + final MapTileFilesystemProvider fileSystemProvider = new MapTileFilesystemProvider( + pRegisterReceiver, pTileSource); + mTileProviderList.add(fileSystemProvider); + } else { + final MapTileSqlCacheProvider cachedProvider = new MapTileSqlCacheProvider(pRegisterReceiver, pTileSource); + mTileProviderList.add(cachedProvider); + } + geopackage = new GeoPackageMapTileModuleProvider(databases, pContext, tileWriter); + mTileProviderList.add(geopackage); + + final MapTileDownloader downloaderProvider = new MapTileDownloader(pTileSource, tileWriter, + aNetworkAvailablityCheck); + mTileProviderList.add(downloaderProvider); + } + + public GeoPackageMapTileModuleProvider geoPackageMapTileModuleProvider(){ + return geopackage; + } + + + @Override + public IFilesystemCache getTileWriter() { + return tileWriter; + } + + @Override + public void detach() { + //https://github.com/osmdroid/osmdroid/issues/213 + //close the writer + if (tileWriter != null) + tileWriter.onDetach(); + tileWriter = null; + super.detach(); + } + + public static ITileSource getTileSource(String database, String table) { + return new XYTileSource(table, 0, 22, 256, "jpg", new String[]{database}); + } +} diff --git a/osmdroid-geopackage/src/main/java/org/osmdroid/gpkg/MainActivity.java b/osmdroid-geopackage/src/main/java/org/osmdroid/gpkg/MainActivity.java new file mode 100644 index 0000000000..59ecdc02ae --- /dev/null +++ b/osmdroid-geopackage/src/main/java/org/osmdroid/gpkg/MainActivity.java @@ -0,0 +1,184 @@ +package org.osmdroid.gpkg; + + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import org.osmdroid.api.IGeoPoint; +import org.osmdroid.config.Configuration; +import org.osmdroid.events.MapListener; +import org.osmdroid.events.ScrollEvent; +import org.osmdroid.events.ZoomEvent; +import org.osmdroid.tileprovider.modules.ArchiveFileFactory; +import org.osmdroid.tileprovider.util.StorageUtils; +import org.osmdroid.util.GeoPoint; +import org.osmdroid.views.MapView; + +import java.io.File; +import java.io.FileFilter; +import java.sql.Driver; +import java.sql.DriverManager; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public class MainActivity extends Activity implements MapListener { + MapView mOsmv; + TextView viewcenter; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + + + //first let's up our map source, mapsforge needs you to explicitly specify which map files to load + //this bit does some basic file system scanning + Set mapfiles = findMapFiles(); + //do a simple scan of local storage for .gpkg files. + File[] maps = new File[mapfiles.size()]; + maps = mapfiles.toArray(maps); + if (maps.length==0){ + //show a warning that no map files were found + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder( + this); + + // set title + alertDialogBuilder.setTitle("No Mapsforge files found"); + + // set dialog message + alertDialogBuilder + .setMessage("In order to render map tiles, you'll need to either create or obtain mapsforge .map files. See https://github.com/mapsforge/mapsforge for more info.") + .setCancelable(false) + .setPositiveButton("Yes",new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog,int id) { + + } + }); + + + // create alert dialog + AlertDialog alertDialog = alertDialogBuilder.create(); + + // show it + alertDialog.show(); + + } + else + Toast.makeText(this, "Loaded " + maps.length + " map files", Toast.LENGTH_LONG).show(); + + + //ACK! terrible idea, ArchiveFileFactory.registerArchiveFileProvider(GeoPackageFileArchiveProvider.class, "gpkg"); + + + Configuration.getInstance().load(this, PreferenceManager.getDefaultSharedPreferences(this)); + //Configuration.getInstance().setDebugTileProviders(true); + Configuration.getInstance().setDebugMode(true); + setContentView(R.layout.activity_main); + + + + + mOsmv = (MapView) findViewById(R.id.mapview); + + GeoPackageProvider geoPackageProvider = new GeoPackageProvider(maps, this.getApplicationContext()); + mOsmv.setTileProvider(geoPackageProvider); + List tileSources = geoPackageProvider.geoPackageMapTileModuleProvider().getTileSources(); + + boolean sourceSet=false; + for (int i=0; i < tileSources.size(); i++) { + //this is a list of geopackages, since we only support tile tables, pick the first one of those + if (tileSources.get(i).tiles!=null && !tileSources.get(i).tiles.isEmpty()) { + mOsmv.setTileSource(GeoPackageProvider.getTileSource(tileSources.get(i).database, + tileSources.get(0).tiles.get(i) + )); + sourceSet = true; + } + } + + + if (!sourceSet) { + Toast.makeText(this, "No tile source is available, get your geopackages for 'tiles' tables", Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(this, "Tile source set to " + mOsmv.getTileProvider().getTileSource().name(), Toast.LENGTH_LONG).show(); + } + mOsmv.setUseDataConnection(false); + mOsmv.setMultiTouchControls(true); + mOsmv.setBuiltInZoomControls(true); + mOsmv.setMapListener(this); + mOsmv.setMinZoomLevel(0); + mOsmv.setMaxZoomLevel(18); + + + viewcenter= (TextView) findViewById(R.id.viewcenter); + updateView(); + + } + + @Override + public void onStart() { + super.onStart(); + //white horse sample set + mOsmv.getController().setCenter(new GeoPoint(60.84892, -135.18848)); + mOsmv.getController().setZoom(12); + } + + /** + * simple function to scan for paths that match /something/osmdroid/*.map to find mapforge database files + * @return + */ + protected static Set findMapFiles() { + Set maps = new HashSet<>(); + List storageList = StorageUtils.getStorageList(); + for (int i = 0; i < storageList.size(); i++) { + File f = new File(storageList.get(i).path + File.separator + "osmdroid" + File.separator); + if (f.exists()) { + maps.addAll(scan(f)); + } + } + return maps; + } + + static private Collection scan(File f) { + List ret = new ArrayList<>(); + File[] files = f.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + if (pathname.getName().toLowerCase().endsWith(".gpkg")) + return true; + return false; + } + }); + if (files != null) { + for (int i = 0; i < files.length; i++) { + ret.add(files[i]); + } + } + return ret; + } + + @Override + public boolean onScroll(ScrollEvent event) { + updateView(); + return false; + } + + @Override + public boolean onZoom(ZoomEvent event) { + updateView(); + return false; + } + + private void updateView() { + IGeoPoint mapCenter = mOsmv.getMapCenter(); + viewcenter.setText(mapCenter.getLatitude() + "," + mapCenter.getLongitude() + " " + mOsmv.getZoomLevel()); + } +} diff --git a/osmdroid-geopackage/src/main/res/drawable/icon.png b/osmdroid-geopackage/src/main/res/drawable/icon.png new file mode 100644 index 0000000000..b74f1ea49d Binary files /dev/null and b/osmdroid-geopackage/src/main/res/drawable/icon.png differ diff --git a/osmdroid-geopackage/src/main/res/layout/activity_main.xml b/osmdroid-geopackage/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..2e981e9bb3 --- /dev/null +++ b/osmdroid-geopackage/src/main/res/layout/activity_main.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/osmdroid-geopackage/src/main/res/values-w820dp/dimens.xml b/osmdroid-geopackage/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000000..63fc816444 --- /dev/null +++ b/osmdroid-geopackage/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/osmdroid-geopackage/src/main/res/values/dimens.xml b/osmdroid-geopackage/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..47c8224673 --- /dev/null +++ b/osmdroid-geopackage/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/osmdroid-geopackage/src/main/res/values/strings.xml b/osmdroid-geopackage/src/main/res/values/strings.xml new file mode 100644 index 0000000000..7942c231e3 --- /dev/null +++ b/osmdroid-geopackage/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + osmdroid-gpkg + diff --git a/osmdroid-geopackage/src/test/java/org/osmdroid/ExampleUnitTest.java b/osmdroid-geopackage/src/test/java/org/osmdroid/ExampleUnitTest.java new file mode 100644 index 0000000000..4ea88c16a2 --- /dev/null +++ b/osmdroid-geopackage/src/test/java/org/osmdroid/ExampleUnitTest.java @@ -0,0 +1,15 @@ +package org.osmdroid; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index bae07d66d0..7c3974153b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,7 @@ rootProject.name = 'osmdroid-parent' include ':osmdroid-android' include ':osmdroid-third-party' include ':osmdroid-mapsforge' +include ':osmdroid-geopackage' //sample apps include ':OpenStreetMapViewer'