package org.osmdroid.samplefragments.tileproviders; import android.content.DialogInterface; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.InputDevice; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; import org.mapsforge.map.android.graphics.AndroidGraphicFactory; import org.osmdroid.R; import org.osmdroid.api.IGeoPoint; import org.osmdroid.api.IMapView; import org.osmdroid.config.Configuration; import org.osmdroid.events.MapListener; import org.osmdroid.events.ScrollEvent; import org.osmdroid.events.ZoomEvent; import org.osmdroid.gpkg.GeoPackageMapTileModuleProvider; import org.osmdroid.gpkg.GeoPackageProvider; import org.osmdroid.samplefragments.BaseSampleFragment; import org.osmdroid.tileprovider.tilesource.XYTileSource; import org.osmdroid.tileprovider.util.StorageUtils; import org.osmdroid.views.MapView; import java.io.File; import java.io.FileFilter; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.osmdroid.samplefragments.events.SampleMapEventListener.df; /** * One way for viewing geopackage tiles to the osmdroid view * <p> * created on 1/12/2017. * * @author Alex O'Ree * @since 5.6.3 */ public class GeopackageSample extends BaseSampleFragment { TextView textViewCurrentLocation; GeoPackageProvider.TileSourceBounds tileSourceBounds; XYTileSource currentSource = null; GeoPackageProvider geoPackageProvider=null; @Override public String getSampleTitle() { return "Geopackage tiles"; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); Log.d(TAG, "onCreate"); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.map_with_locationbox, container, false); mMapView = (MapView) root.findViewById(R.id.mapview); if (Build.VERSION.SDK_INT >= 12) { mMapView.setOnGenericMotionListener(new View.OnGenericMotionListener() { /** * mouse wheel zooming ftw * http://stackoverflow.com/questions/11024809/how-can-my-view-respond-to-a-mousewheel * @param v * @param event * @return */ @Override public boolean onGenericMotion(View v, MotionEvent event) { if (0 != (event.getSource() & InputDevice.SOURCE_CLASS_POINTER)) { switch (event.getAction()) { case MotionEvent.ACTION_SCROLL: if (event.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0.0f) mMapView.getController().zoomOut(); else { mMapView.getController().zoomIn(); } return true; } } return false; } }); } textViewCurrentLocation = (TextView) root.findViewById(R.id.textViewCurrentLocation); return root; } @Override public void addOverlays() { super.addOverlays(); //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<File> 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 android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder( getContext()); // set title alertDialogBuilder.setTitle("No Geopackage files found"); // set dialog message alertDialogBuilder .setMessage("In order to render map tiles, you'll need to either create or obtain .gpkg files. See http://www.geopackage.org/ for more info. Place them in " + Configuration.getInstance().getOsmdroidBasePath().getAbsolutePath()) .setCancelable(false) .setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { } }); // create alert dialog android.app.AlertDialog alertDialog = alertDialogBuilder.create(); // show it alertDialog.show(); } else { Toast.makeText(getContext(), "Loaded " + maps.length + " map files", Toast.LENGTH_LONG).show(); geoPackageProvider = new GeoPackageProvider(maps, this.getContext()); mMapView.setTileProvider(geoPackageProvider); List<GeoPackageMapTileModuleProvider.Container> tileSources = geoPackageProvider.geoPackageMapTileModuleProvider().getTileSources(); //here we're keeping track of the current tile source so we can reference it later, primarly for //displaying the bounds of the tile set 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 //ideally this should populate a spinner so that the user can select whatever tile source they want if (tileSources.get(i) != null && !tileSources.get(i).tiles.isEmpty()) { currentSource = (XYTileSource) geoPackageProvider.getTileSource(tileSources.get(i).database, tileSources.get(0).tiles.get(i) ); mMapView.setTileSource(currentSource); sourceSet = true; break; } } if (!sourceSet) { Toast.makeText(getContext(), "No tile source is available, get your geopackages for 'tiles' tables", Toast.LENGTH_LONG).show(); } else { Toast.makeText(getContext(), "Tile source set to " + mMapView.getTileProvider().getTileSource().name(), Toast.LENGTH_LONG).show(); //this part will attempt to zoom to bounds of the selected tile source tileSourceBounds = geoPackageProvider.getTileSourceBounds(); if (tileSourceBounds != null) { mMapView.zoomToBoundingBox(tileSourceBounds.bounds, true); mMapView.getController().setZoom(tileSourceBounds.minzoom); } } } mMapView.setMapListener(new MapListener() { @Override public boolean onScroll(ScrollEvent event) { Log.i(IMapView.LOGTAG, System.currentTimeMillis() + " onScroll " + event.getX() + "," + event.getY()); updateInfo(); return true; } @Override public boolean onZoom(ZoomEvent event) { Log.i(IMapView.LOGTAG, System.currentTimeMillis() + " onZoom " + event.getZoomLevel()); updateInfo(); return true; } }); updateInfo(); } @Override public void onDestroy(){ super.onDestroy(); this.currentSource=null; if (geoPackageProvider!=null) geoPackageProvider.detach(); } private void updateInfo() { StringBuilder sb = new StringBuilder(); IGeoPoint mapCenter = mMapView.getMapCenter(); sb.append(df.format(mapCenter.getLatitude()) + "," + df.format(mapCenter.getLongitude()) + ",zoom=" + mMapView.getZoomLevel()); if (currentSource != null) { sb.append("\n"); sb.append(currentSource.name() + "," + currentSource.getBaseUrl()); } if (tileSourceBounds != null) { sb.append("\n"); sb.append(" minzoom=" + tileSourceBounds.minzoom); sb.append(" maxzoom=" + tileSourceBounds.maxzoom); sb.append(" bounds=" + df.format(tileSourceBounds.bounds.getLatNorth()) + "," + df.format(tileSourceBounds.bounds.getLonEast()) + "," + df.format(tileSourceBounds.bounds.getLatSouth()) + "," + df.format(tileSourceBounds.bounds.getLonWest())); } textViewCurrentLocation.setText(sb.toString()); } /** * simple function to scan for paths that match /something/osmdroid/*.map to find database files * * @return */ protected static Set<File> findMapFiles() { Set<File> maps = new HashSet<>(); List<StorageUtils.StorageInfo> 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<? extends File> scan(File f) { List<File> 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; } }