BoundedMapView – A MapView with limits

In the source I attached to my previous post I included another class I recently created which is based on a patch from Marc Kurtz and Zoran Nikolic (see here for details). Actually, it’s more than just based on their code: I copied quite a bit of their work, so credits where credit’s due.

Anyway, essentially the class allows you to set limits to the area that can be viewed. This is useful if you supply an offline map database that only contains a specific area, a city for example. The class itself is called BoundedMapView.java. A class that illustrates the usage is below the break (for information on MBTileProvider see the previous post as well).
Continue reading

Using MBTiles in osmdroid

A couple of days ago I posted an example of getting osmdroid to work with an offline MBTiles database. The example is quite elaborate, mostly because 80% of it is comment, not code. I found three issues with this:

  1. The amount of text between the lines of actual code is so big, that it makes it hard to follow, but …
  2. This is necessary because the steps don’t really speak for themselves and …
  3. Even if you remove all the comments, you still end up with more lines of code than you should

So I thought I’d remedy this by extending the framework. As a result I updated RouteMapActivity.java and created three new classes:

  1. MBTileProvider.java
  2. MBTileModuleProvider.java
  3. MBTileSource.java

Below you’ll find the code to these classes. Please note that you have to make sure that they are put in the correct package for things to work. You can also download the sources.
Continue reading

Integrating osmdroid part 2 – Using MBTiles as offline data source

Yesterday I wrote an article about how to integrate the osmdroid library into an Eclipse project. You may have noticed I didn’t mention anything about actually creating an Activity that actually does something with the freshly installed library.

Let’s change that :-). Below you’ll find a heavily commented example on how to instantiate a MapView using an offline MBTiles map source. Since creating an mbtiles file is also a project on its own, I’ll see if I can write something up about that process soon.

Questions? Let me know!

package com.example.yourproject;

import java.io.File;

import org.osmdroid.DefaultResourceProxyImpl;
import org.osmdroid.ResourceProxy;
import org.osmdroid.tileprovider.MapTileProviderArray;
import org.osmdroid.tileprovider.modules.IArchiveFile;
import org.osmdroid.tileprovider.modules.MBTilesFileArchive;
import org.osmdroid.tileprovider.modules.MapTileFileArchiveProvider;
import org.osmdroid.tileprovider.modules.MapTileModuleProviderBase;
import org.osmdroid.tileprovider.tilesource.XYTileSource;
import org.osmdroid.tileprovider.util.SimpleRegisterReceiver;
import org.osmdroid.views.MapController;
import org.osmdroid.views.MapView;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.Menu;

public class RouteMapActivity extends Activity {

  @SuppressWarnings("unused")
  private static final String TAG = "RouteMapActivity";

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

    /**
    * This whole thing revolves around instantiating a MapView class, way,
    * way below. And MapView requires a ResourceProxy. Who are we to deny
    * its needs? Let's create one!
    *
    * It would have been nice if this was taken care of in the MapView
    * constructor. Interestingly MapView *has* a constructor that creates a
    * new DefaultResourceProxyImpl but unfortunately that one doesn't allow
    * us to specify the parameters we *do* need to set ...
    */
    DefaultResourceProxyImpl resProxy;
    resProxy = new DefaultResourceProxyImpl(this.getApplicationContext());

    /**
    * A class that implements the ITileSource interface knows how to
    * convert an InputStream or a file path into a Drawable. It doesn't do
    * much more than that. The real 'sourcery' is performed by
    * MapTileFileArchiveProvider which will be introduced shortly.
    *
    * What we need is really a BitmapTileSourceBase instance, but this
    * class is defined as abstract. XYTileSource is not and comes closest
    * to what we want.
    *
    * Comment: I don't quite get why BitmapTileSource base is abstract; it
    * doesn't contain any abstract methods.
    */
    XYTileSource tSource;
    tSource = new XYTileSource("mbtiles",
    ResourceProxy.string.offline_mode,
    8, 15, 256, ".png", "http://who.cares/");

    /**
    * Don't think the name SimpleRegisterReceiver is particularly well
    * chosen. SimpleReceiverRegistrar would have been better because the
    * only thing SimpleRegisterReceiver does, is wrap the methods
    * Context.registerReceiver(..) and Context.unregisterReceiver(..). Have
    * a look at the source if you don't believe me ;-).
    *
    * So why does it exist then?? Don't know, but it's quite possible to
    * just ignore this step and state the Activity implements
    * IRegisterReceiver and replace the 'simpleReceiver' variable with
    * 'this' further down (no additional implementation required).
    */
    SimpleRegisterReceiver sr = new SimpleRegisterReceiver(this);

    /**
    * The following looks complicated, but really only creates an
    * iArchiveFile[]. Apparently Marc Kurtz and Nicolas Gramlich, the
    * authors of MapTileFileArchiveProvider, figured it might be useful to
    * support multiple files/sources. I guess that might make sense if
    * you're providing separate files for, for example, cities.
    *
    * They also provided quite a bit of logic in MapTileFileArchiveProvider
    * for handling SD Card inserts and ejects. Additionally, if files are
    * not explicitly specified they can be dynamically loaded from the
    * /mnt/sdcard/osmdroid directory, which is a nice feature.
    */
    String packageDir = "/com.example.yourproject";
    String p = Environment.getExternalStorageDirectory() + packageDir;
    File f = new File(p, "TileDatabase.mbtiles");
    IArchiveFile[] files = { MBTilesFileArchive.getDatabaseFileArchive(f) };

    MapTileModuleProviderBase moduleProvider;
    moduleProvider = new MapTileFileArchiveProvider(sr, tSource, files);

    /**
    * So at this point we have a MapTileModuleProvider that provides
    * MapTileModules: a MapTileModule looks at one or more sources, which
    * are *not* ITileSources but IArchiveFiles and provides MapTiles. What,
    * then, does MapTileProviderArray do? Well, it just adds another layer
    * to the complexity cake: this makes it possible to set multiple
    * MapTileModuleProviders, such as an on- and offline source. I'm sure
    * it's useful for someone, but for simple applications it's probably
    * too much.
    */
    MapTileModuleProviderBase[] pBaseArray;
    pBaseArray = new MapTileModuleProviderBase[] { moduleProvider };

    MapTileProviderArray provider;
    provider = new MapTileProviderArray(tSource, null, pBaseArray);

    /**
    * Are we there yet??? Create the MapView already!
    */
    MapView mapView = new MapView(this, 256, resProxy, provider);
    mapView.setBuiltInZoomControls(true);

    // Zoom in and go to Amsterdam
    MapController controller = mapView.getController();
    controller.setZoom(12);
    controller.animateTo(new LatLonPoint(52.373444, 4.892229));

    // Set the MapView as the root View for this Activity; done!
    setContentView(mapView);

  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.activity_route_map, menu);
    return true;
  }

}

Integrating osmdroid into your Android app

The library osmdroid is a great replacement for your standard Google Maps. Now why’d you wanna go and replace Google Maps? Well, until recently Google didn’t support offline usage of their data. Most of the time this isn’t an issue, but if you want to take your (m)app abroad and avoid heavy roaming charges, you’re looking at offline maps. The good news is that osmdroid supports this. Even better is dat there’s a data type that can be used both by osmdroid and the iOS map-framework route-me: that format is called MBTiles. So if you’re building a cross-platform app, there are still some benefits in using the library over Google’s upcoming offline maps support. Also, one of the nice things about osmdroid is that it uses pretty much the same API as Google’s MapView, which, in theory, should make it a drop-in replacement. As with any library, however, there are always a few bumps in getting things to work. In the remainder of this post, I’ll detail the steps necessary for getting up and running in no time!

To source, or not to source …

Basically there are two ways of using the framework: you can choose to include the pre-built .jar which can be obtained from the downloads section) or you can choose to include the library from source.

Whichever route you choose, osmdroid depends on SLF4J (Simple Logging Facade for Java) which can be found here. As they state on their website: just add the file to the build path. The easiest way to accomplish this is by finding the “libs” directory in your Eclipse project (you are using Eclipse, right!?) and copying the file there. Refresh the directory contents and you should be good to go!

The same applies if you’re using the osmdroid jar: just copy it to the “libs” directory and refresh.

However, if you’re like me and like to be able to browse the sources while using the library, you’ll want to import osmdroid as a separate project and refer to it from your main project. Considering the fact that the documentation for this, otherwise excellent, library is quite terse, I highly recommend this option. This wiki page is the original source for how to install the sources under Eclipse, but you’ll probably find these steps a bit easier to follow:

Compiling osmdroid from source requires a few additional libraries. These are:

  • android.jar (part of the Android SDK, we’ll get to this in a second)
  • httpmime.jar (in debian this file is part of the package libhttpmime-java and located in /usr/share/java)
  • slf4j-android-1.5.8.jar (you should have already downloaded this :-))

Ensure that you have found the above dependencies and proceed to download the sources via subversion:

http://osmdroid.googlecode.com/svn/trunk/ osmdroid

This will create a new directory osmdroid in your current directory. Switch to Eclipse and right-click in the package explorer and select “Import…” –> “Existing files into workspace”. Select the directory you just downloaded the sources to as “root directory” and make sure that you only tick the “osmdroid-android” project.

As you’ll probably have noticed, Eclipse has identified quite a number of unresolved dependencies. Let’s go fix that. First add the variable ANDROID_SDK_PLATFORM to your classpath by selecting:

Window --> preferences --> Java --> Build path --> Classpath variables

Once you’ve added a new variable, the result should look similar like the image below (the path should, obviously be changed to match your installation).

We’ve already taken the first step in resolving them, but there’s a few additional things to do. Right click the project in the package explorer and select

Build path --> Configure build path --> Libraries

Here add “httpmime.jar” and “slf4j-android-1.5.8.jar” via “Add External JARs” and “android.jar” via “Add Variable” (select ANDROID_SDK_PLATFORM which you defined earlier and click “Extend” to select the jar). Then finally add JUnit4 via the “Add library” button. The result should look similar to this:

If you’ve followed these steps, you should now be able to compile the library, but I suppose that doesn’t help you much. So go back to your Android project and right click it from the package explorer. Here too navigate to the “Java Build Path” but this time select the “Projects” tab. Here you can add osmdroid-android as a required project. Doing so will allow you to refer to the lib’s classes from your own code.

Still, if you don’t want your app to crash on load, there is one final click to be made: check the box in front of osmdroid-android in the “Order and Export” tab and you’re done!!