osmdroidgemfilemapnik

How can I prevent Mapnik from mixing tile sources in my OSMdroid based app


I have made a map app for personal use, in which I can choose one of three map sources: OpenTopo, Mapnik or HikeBike. It is primarily intended for off-line use, and to that end I have downloaded a complete set of OpenTopo bitmap tiles (.png), covering the zoom levels 11 through 15 for the local areas that interest me the most. The Mapnik and HikeBike map modes, on the other hand, I only intend to use when on-line, mostly for tour planning, etc.

The OpenTopo tile archives are stored as .gemf files directly in /storage/extSdCard/osmdroid/ (which is the path returned by querying Configuration.getInstance().getOsmdroidBasePath().getAbsolutePath();).

The problem I have now is the fact that the two "auxiliary" map modes (Mapnik and HikeBike) don't fully "respect" the tile sources that have been set for them to use, even if connected to the internet. Instead, whenever the locality and zoom levels required by the mapview are covered by the locally available OpenTopo tiles, Mapnik/HikeBike annoyingly prefer to display those tiles.

I have put map.setUseDataConnection(true) in onCreate(), which I hoped would make fetching (missing) tiles on-line the default, granted a network connection.

I would appreciate advice on (1) how to make the vector-based map modes discriminate between their own correct tiles and the OpenTopo static bitmaps, as expected. And (2) how to entice them to dynamically download any missing tiles if actually on-line.

Here are some potentially relevant snippets out of the 1100+ lines of code in my MapsActivity.java file:

// ===========================================================
//                      (Some) Constants
// ===========================================================

private static final String tileSourceName_MPNK = "Mapnik";
private static final String tileSourceName_HKBK = "HikeBikeMap";
private static final String tileSourceName_OPTP = "OpenTopoMap";

// Pre-select the default (initial) tile source here
public static String activeTileSourceName = tileSourceName_OPTP;


// ===========================================================
//                      (Some) Fields
// ===========================================================

private SharedPreferences mPrefs;
MapView map = null;     
SqliteArchiveTileWriter tileWriter = null;

// Some state booleans
boolean registeredNetworkReceiver = false;
boolean wiffiUp = false;


// ===========================================================
//                    (parts of) onCreate
// ===========================================================

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

    mContext = this;

    // Load/initialize the osmdroid configuration
    final Context ctx = getApplicationContext();
    Configuration.getInstance().load(ctx, PreferenceManager.getDefaultSharedPreferences(ctx));
    final DisplayMetrics dm = ctx.getResources().getDisplayMetrics();

    mPrefs = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);

    // Inflate and create the map
    setContentView(R.layout.activity_maps);
    map = findViewById(R.id.map);

    // Set initial map type (tile source)
    map.setTileSource(TileSourceFactory.getTileSource(activeTileSourceName));

    ...

    // Use custom [+][-] buttons
    map.getZoomController().setVisibility(
            CustomZoomButtonsController.Visibility.SHOW_AND_FADEOUT);

    // Scale tiles relative to the current screen's DPI. Should help with readability in "the field"
    map.setTilesScaleFactor(1.2f);          // Compromise scale, better than dpi

    // Store the startup zoom level we just set, so it survives first pass through onResume()
    final SharedPreferences.Editor edit = mPrefs.edit();
    edit.putFloat(PREFS_ZOOM_LEVEL_DOUBLE, (float) map.getZoomLevelDouble());
    edit.apply();

    ....

    // If/when we have a network connection: default to using (missing?) on-line tiles.
    map.setUseDataConnection(true);

    // Register a wiffi networkReceiver
    registerReceiver();
}


// ===========================================================
//                    (parts of) onPause
// ===========================================================

@Override
public void onPause() {

    // Save the current location
    final SharedPreferences.Editor edit = mPrefs.edit();
    edit.putString(PREFS_TILE_SOURCE, map.getTileProvider().getTileSource().name());

}

// ===========================================================
//                    (parts of) onResume
// ===========================================================

@Override
protected void onResume() {
    super.onResume();

    // Set zoom to default level if first pass, else to the latest level stored onPause().
    final float zoomLevel = mPrefs.getFloat(PREFS_ZOOM_LEVEL_DOUBLE, 1);
    map.getController().setZoom(zoomLevel);

}


// ===========================================================
//                    (parts of) onDestroy
// ===========================================================

@Override
protected void onDestroy() {
    unregisterReceiver(networkReceiver);
    ....

    super.onDestroy();
}


// ===========================================================
//            networkReceiver --> onReceive
// ===========================================================

/**
The idea behind the following function is to force a map refresh when switching from offline to online. If the display is not refreshed there may be no attempt to get better tiles */

private final BroadcastReceiver networkReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                NetworkInfo networkInfo = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
                if (networkInfo != null && networkInfo.getDetailedState() == NetworkInfo.DetailedState.CONNECTED) {
                    Log.d("Network", "Internet YAY !!!!!!!!!!!!!!!!!!");
                    toaster("Internet YAY !!!!!!!!!!!!!!!!!!");
                    wiffiUp = true;
                } else if (networkInfo != null && networkInfo.getDetailedState() == NetworkInfo.DetailedState.DISCONNECTED) {
                    Log.d("Network", "No internet :(");
                    toaster("No internet :(");
                    wiffiUp = false;
                }
                map.invalidate();    // i.e. refresh screen
            }
        }
        catch(NullPointerException e) {
            e.printStackTrace();
        }
    }
};


// ===========================================================
//                      registerReceiver
// ===========================================================

private void registerReceiver() {
    if (registeredNetworkReceiver) {
        return;
    }
    registeredNetworkReceiver = true;
    IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
    MapsActivity.this.registerReceiver(networkReceiver, filter);
}


// ===========================================================
//                      onOptionsItemSelected
// ===========================================================

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id. map1_menu:
            map.setTileSource(TileSourceFactory.getTileSource(tileSourceName_OPTP));
            toaster(" OpenTopo map ");
            return true;

        case R.id. map2_menu:
            map.setTileSource(TileSourceFactory.getTileSource(tileSourceName_MPNK));
            map.invalidate();
            toaster(" Mapnik map ");
            return true;

        case R.id. map3_menu:
            map.setTileSource(TileSourceFactory.getTileSource(tileSourceName_HKBK));
            map.invalidate();
            toaster(" BikeHike map ");
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}

Edit 2: Here is screenshot showing the structure of the /storage/extSdCard/osmdroid directory:

storage extSdCard.png


Solution

  • I solved this problem by firstly moving just the cached tiles from "/storage/extSdCard/osmodroid/tiles" to a new directory "/storage/extSdCard/osmXtra/tiles", then basically stripping out the guts of onCreate() and putting it in a separate function onCreateCommonCode(String tileSourceName_X) which takes the TileSource name as it's parameter.

    This function now runs once on startup in onCreate() with the default OpenTopoMaps as input, but also (with the appropriate TileSource-parameter) whenever the user commands a switch of map type. A simple "if TileSourceThis then TilePathThat" test selects the correct directory to search in for either archive files or the tile cache.

    Perhaps not the most elegant solution, but apart for some minor oddities, like how mLocationOverlay.enableFollowLocation() behaves after a map switch, I am satisfied with how it works now.