Search code examples
androidgoogle-mapsosmdroidmbtiles

OSMdroid Loading Multiple Offline MBTIles Via ListView


I am using OSMdroid with Offline MBTiles. I need to be able add users to select multiple MBtiles from a ListView. ListView windows is easy. However, I need help getting different MBtiles loaded based on MBtiles selected from ListView. My MBTiles are loaded from SDCard.

import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapController;
import org.osmdroid.views.MapView;

import android.app.Activity;
import android.os.Bundle;
import android.widget.RelativeLayout;

public class OfflineMapDemoActivity extends Activity {

    private String MapName;

    public String getMapName(){
        return MapName;
    }
    public void setMapName(String s){
        MapName = s;
    }

    // layout
    private RelativeLayout mapLayout;

    // MapView
    private MapView mapView;
    private MapController mapController;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        // init Layout
        setContentView(R.layout.main);
        this.mapLayout = (RelativeLayout) findViewById(R.id.mapLayout);

        // init Offline Map
        MapName="World.sqlitedb";
        this.mapView = new OfflineMapView(this, MapName);
        this.mapController = mapView.getController();

        // set Zoom Countrol
        this.mapView.setBuiltInZoomControls(true);
        // set Touch Control
        this.mapView.setMultiTouchControls(true);
        // zoom to 15
        this.mapController.setZoom(15);
        //add mapview
        this.mapLayout.addView(this.mapView, new RelativeLayout.LayoutParams(
                    android.view.ViewGroup.LayoutParams.FILL_PARENT,
                    android.view.ViewGroup.LayoutParams.FILL_PARENT));

        // scroll to 24082456, 120558472
        GeoPoint geoPoint = new GeoPoint(24082456, 120558472);
        this.mapController.setCenter(geoPoint);

    }
}

I create a global string variable to hold the MapName where the Listview class can set via public method setMapName().

By the way, is there a method to read the MBTiles center automatically instead of hard coding like this?

    // scroll to 24082456, 120558472
    GeoPoint geoPoint = new GeoPoint(24082456, 120558472);
    this.mapController.setCenter(geoPoint);

Which method should I use everytime user switches to OSMdroid map from ListView to load the selected the offline MBtiles map from list view? Above on Create method only do load the MBTiles map once when loaded for the first time? Here is the list view code.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.SimpleAdapter;

public class MyTwoListItemsActivity extends ListActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ArrayList<Map<String, String>> list = buildData();
        String[] from = { "mapname", "selected" };
        int[] to = { android.R.id.text1, android.R.id.text2 };

        SimpleAdapter adapter = new SimpleAdapter(this, list,
            android.R.layout.simple_list_item_2, from, to);
        setListAdapter(adapter);
    }

    private ArrayList<Map<String, String>> buildData() {
        ArrayList<Map<String, String>> list = new ArrayList<Map<String, String>>();
        list.add(putData("Map1", "Map1"));
        list.add(putData("Map2", "Map2"));
        list.add(putData("Map3", "Map3"));
        return list;
    } 

    private HashMap<String, String> putData(String name, String purpose) {
        HashMap<String, String> item = new HashMap<String, String>();
        item.put("mapname", mapname);
        item.put("selected", selected);
        return item;
    }

} 

How do I integrate switching between MyTwoListItemsActivity and OfflineMapDemoActivity?


Solution

  • Small world, I just did exactly what you are looking for. It required a patch for osmdroid.

    Assuming the patch is applied, you need to make your own extension of MapTileProviderBase (see MapTileProviderBasic as a reference). You'll want to create a constructor that takes in a file array and then pass that to the FileArchiveProvider. From here, once the user has selected the files they want to use for offline maps, then pass that list to an instance of your custom tile provider, then call MapView.setTileProvider.

    Also note that /sdcard/osmdroid works just fine for this purpose, however if your device is on kitkat and the files are in /storage/extSdCard (such as samsung devices), you'll need also need this

    I'll eventually submit a pull request for some of the adapters i made to make this process simpler.

    Edit: Your direct question, "How do I integrate switching between MyTwoListItemsActivity and OfflineMapDemoActivity?", is more of an android specific question on how transfer data from one activity to another. Intents and shared preferences are the most likely candidates for this application. You may want to consider using some kind of popup, menu, action bar, dialog, etc to flip map sources. The construction of a MapView is expensive and preventing reinitialization would be a good idea.

    Edit: here's the code I used.

    Caveat 1) you need to know the Tile Source name ahead of time for each archive. Zip files are usually the map source name, like OpenStreetMaps. Sqlite has a column for it, etc. If your tiles don't show up, this is the most likely cause.

    Create a tile source. For simplicity's sake, I choose to name the files to exactly map the file source names. Adjust to meet your needs.

    public class FileBasedTileSource extends XYTileSource {
    
        public FileBasedTileSource(String aName, ResourceProxy.string aResourceId, int aZoomMinLevel, int aZoomMaxLevel, int aTileSizePixels, String aImageFilenameEnding, String[] aBaseUrl) {
            super(aName, aResourceId, aZoomMinLevel, aZoomMaxLevel, aTileSizePixels, aImageFilenameEnding, aBaseUrl);
        }
    
        public static ITileSource getSource(String name) {
            if (name.contains(".")) {
                name = name.substring(0, name.indexOf("."));
            }
            return new FileBasedTileSource(name,
                ResourceProxy.string.mapbox, 0, 18, 256, ".png", new String[]{
                "http://localhost"});
        }
    }
    

    Then you would create it with

    ITileSource src=FileBasedTileSource.getSource("MyTileSource.gemf");
    

    Next you'll need the following class, which i'll create a pull request for. This is the tile provider.

    public class OfflineTileProvider extends MapTileProviderArray implements IMapTileProviderCallback {
    
        /**
         * Creates a {@link MapTileProviderBasic}.
         * throws with the source[] is null or empty
         */
        public OfflineTileProvider(final IRegisterReceiver pRegisterReceiver, File[] source
        )
            throws Exception {
            super(FileBasedTileSource.getSource(source[0].getName()), pRegisterReceiver);
            IArchiveFile[] f = new IArchiveFile[source.length];
            for (int i=0; i < source.length; i++)
                f[i]=ArchiveFileFactory.getArchiveFile(source[i]);
    
            mTileProviderList.add(new MapTileFileArchiveProvider(pRegisterReceiver, getTileSource(), f));
    
        }
    }
    

    Then create an instance as follows:

    OfflineTileProvider provider = new OfflineTileProvider(new SimpleRegisterReceiver(mContext), myUserSelectedFiles[]));
    mMapView.setTileProvider(provider);
    mMapView.setUseDataConnection(false);
    mMapView.invalidate();
    

    That should create an instance and update the map view.

    Additional notes: IO exceptions are more than possible