1

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

moof-moof
  • 21
  • 6
  • Maybe share a code showing how are you setting up tile sources in your app (and the overall configuration) and how you switch the tile sources during run time so we can check if we see some problems there. I would guess you either have multiple tile sources in map at the same time or there might be some problems with caching setup (https://github.com/osmdroid/osmdroid/wiki/Tile-Caching). But it is impossible to say only from the description. – Josef Adamcik Nov 07 '19 at 09:12
  • 1
    Of course. I have added code examples. – moof-moof Nov 07 '19 at 11:48
  • I remember seeing this sort of stuff a few years back. Basically the underlying code for the file based rendering in your case the .gemf stuff has priority and has no real concept of what your tile source is and what tile source it holds so it's tiles are always used. Your best bet is probably to try and get the file tile providers removed when your in online only mode. Your going to have to dig down into the source to try and work out how to remove them or construct stuff without them. – Ifor Nov 10 '19 at 11:48
  • @ifor - Thanks for the pointers. I recently realised that what you are describing is probably what is happening, and so tried a strategy of actively resetting the tileprovider paths on the fly. I think I have got everything (mostly) working now and will post my "solution" in a little while. – moof-moof Nov 10 '19 at 12:14

1 Answers1

1

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.

moof-moof
  • 21
  • 6