Monday, 22 September 2008

Displaying Tracklogs on Google Maps (Part I)

The last couple of blogs where about how to store GPS information on an Android phone. Now it is time to pull the information out again and display it on top of a Google Map layer.

First let's start displaying a map on the phone. Earlier versions of the Android documentation had a dedicated tutorial on displaying maps, but the tutorial link that is still in the documentation is broken. Things have changed a bit since the early versions, so I guess Google will put out a new tutorial in due course. But it means that one has to work from the documentation alone.

Anyway, displaying a map is displaying a MapView, which can only be done inside a MapActivity. The reason is that the MapActivity handles all the back-ground threads needed to do (p)re-loading of Map tiles at runtime, significantly more work that a normal Activity has to do.

As with all views, MapViews can be constructed entirely programmatically or declaratively via XML. Here for simplicity's sake, we use the second approach and mix it with a bit of programming. Displaying a naked Google Map is as easy as installing the view:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.google.android.maps.MapView android:id="@+id/map"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="myKey"
android:clickable="true" />
<LinearLayout android:id="@+id/zoom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true" />
</RelativeLayout>

Here we have already done more than the minimum, as in addition to the map view we have already defined a view element that will hold the zoom elements of the map. We store this as a resource with the name mapview.xml in the layout folder.

Now we need a MapActivity to display this view. The one thing we have to override here is the onCreate() method, which can be as simple as this:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mapview);
return;
}


This displays us the basic map, but has nothing to do with our tracks so far. The next steps are first to center the map on our tracklog. A very simple (expensive and possibly buggy) way is to get all the trackpoints and just center the map on the first of them.

Now our onCreate method gets a bit longer:

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mapview);
view = (MapView)findViewById(R.id.map);
Cursor cursor = getContentResolver().query(GPSDataContentProvider.CONTENT_URI, null, null, null, null);
cursor.moveToFirst();
Double lat = cursor.getDouble(1);
Double lon = cursor.getDouble(2);
cursor.close();

view.getController().setCenter(MapUtils.geopointFromLatLong(lat, lon));
view.getController().setZoom(12);
}


We get the tracklog data via a ContentProvider, which in its unfinished rudimentary form was covered earlier and will be covered in more detail later. Here we get the first point, convert it with a utility function into a GeoPoint and center the map on it, also setting the zoom level. Now we get a map centered on top of the beginning of the track, but no tracklog. That will come in the next installment.

Oh, one more thing. Just like me, you might run into the trouble that all your MapView displays is an empty map with a grid overlay. That is because by default your application does not have permission to access the internet. To enable Internet access you will have to declare this with a directive in the AndroidManifest.xml file, with this line:

<uses-permission android:name="android.permission.INTERNET" />


For a real application, you will also have to consider that there are additional licensing restrictions on the use of Google Maps and its API, as well as the obligation to put a copyright notice into your application. The details for this are on the Android Terms and Conditions Page.

4 comments:

Marcus said...

There seems to be another issues with a section of code:

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mapview);
view = (MapView)findViewById(R.id.map);
Cursor cursor = getContentResolver().query(GPSDataContentProvider.CONTENT_URI, null, null, null, null);
cursor.moveToFirst();
Double lat = cursor.getDouble(1);
Double lon = cursor.getDouble(2);
cursor.close();

view.getController().setCenter(MapUtils.geopointFromLatLong(lat, lon));
view.getController().setZoom(12);
}


Everything seems to be being identified apart from the "MapUtils" line, it does not recognise that anywhere.

As I cannot compile the application without the MapUtils fixed I cannot post the LogCat.

Thanks,
Marcus

Marcus said...

I have now managed to get the database to store the GPS coordinates. However, I am now onto the next stage of your tutorial, Displaying track logs on a MapView. I have the mapview working (with an api key) and i can zoom in and out of the map, however, I now have an issue with the piece of code you provided (copied below) and wondered if it was fully working? or as you said in the blog, it is a rough draft.:

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mapview);
view = (MapView)findViewById(R.id.map);
Cursor cursor = getContentResolver().query(GPSDataContentProvider.CONTENT_URI, null, null, null, null);
cursor.moveToFirst();
Double lat = cursor.getDouble(1);
Double lon = cursor.getDouble(2);
cursor.close();

view.getController().setCenter(MapUtils.geopointFromLatLong(lat, lon));
view.getController().setZoom(12);
}


Everything seems to be being identified apart from the "MapUtils" line, it does not recognise that anywhere.

Ludwig said...

As the name MapUtils suggests, it is nothing but a helper class with a few relatively simple methods in it. The method in question geopointFromLatLong just constructs a GeoPoint from the input. In this case it was just something like


static public GeoPoint geopointFromLatLong(double lat, double lon) {
return new GeoPoint((int)(lat*1E6),(int)(lon*1E6));

Marcus said...

Ludwig,

The additional method seems to correct the initial error, however for some unknown reason I am receiving a NullPointerException on the line:

cursor.moveTofirst();

My initial thoughts were that the GPS coordinates were not writing to the database, however I have opened up the .db file and the coordinates are being inserted.