Tobias Knell
16.06.2010Routing / Driving directions on Android – Part 2: Draw the route
After you got the route from wherever, you probably want to draw it on a MapView. But how to do it? That’s what I will show you now.
Create a suiting Overlay
We basically need a Overlay that takes two Geopoints and maybe a color in which the lines should be drawn. So here We have:
public class RouteOverlay extends Overlay {
private GeoPoint gp1;
private GeoPoint gp2;
private int color;
public RouteOverlay(GeoPoint gp1, GeoPoint gp2, int color) {
this.gp1 = gp1;
this.gp2 = gp2;
this.color = color;
}
Now all that’s left now for our Overlay is to override the draw() method and draw the line as we need it:
@Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
Projection projection = mapView.getProjection();
Paint paint = new Paint();
Point point = new Point();
projection.toPixels(gp1, point);
paint.setColor(color);
Point point2 = new Point();
projection.toPixels(gp2, point2);
paint.setStrokeWidth(5);
paint.setAlpha(120);
canvas.drawLine(point.x, point.y, point2.x, point2.y, paint);
super.draw(canvas, mapView, shadow);
}
Back in the Activity, just iterate over the GeoPoints that you got from google maps and add each of them to the MapView:
private void drawPath(List geoPoints, int color) {
List overlays = mapView.getOverlays();
for (int i = 1; i < geoPoints.size(); i++) {
overlays.add(new RouteOverlay(geoPoints.get(i - 1), geoPoints.get(i), color));
}
}
Get location updates from the location manager
So now we have the geopoints and also the overlays, but we’ve only got the last known location of the user! The app doesn’t even updates his location!
What we need to achieve this is a listener from the location manager. That’s quite simple and we also got the LocationManager ready in onCreate, so we just have to add this little line to it:
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);
The first number here is the timespan in milliseconds in which we want want to receive the updates, the second one is the distance in meters that the user has to move before we get them. In our app we don’t have to update the route all the time, so we go with 5 minutes and 5 kilometers.
Be very careful with the values here, because the whole gps thing consumes a lot of energy! (And if the values are way to small it also blocks the whole app)
Also don’t forget to remove the listener if the MapView isn’t visible:
@Override
protected void onPause() {
//remove the listener
locationManager.removeUpdates(this);
super.onPause();
}
@Override
protected void onResume() {
//add the listener again
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);
super.onResume();
}
Now we have to let our MapActivity implement LocationListener and react to the updates:
public class RouteActivity extends MapActivity implements LocationListener {
@Override
public void onLocationChanged(Location location) {
drawUserPosition(location);
}
private void drawUserPosition(Location location) {
GeoPoint currentLocation;
currentLocation = new GeoPoint((int) ( location.getLatitude() * 1E6), (int) ( location
getLongitude() * 1E6));
OverlayItem currentLocationOverlay = new OverlayItem(currentLocation, getString(R.string.your_location),
getString(R.string.current_location));
mapOverlays.clear();
if (locationOverlays.size() > 1) {
// remove the old user position if there is one
locationOverlays.removeOverlay(1);
}
//add new user position
locationOverlays.addOverlay(currentLocationOverlay, this.getResources().getDrawable(R.drawable.someImage));
mapOverlays.add(locationOverlays);
//.
//. calculate / set the mapcenter, zoom to span
//. see in previous posts
//.
RouteThread rt = new RouteThread(currentLocation, synyxGeoPoint, routeHandler);
rt.start();
}
Make it threaded
But we’re not quite finished yet, because you can’t just put the internet connection and parsing into the main thread! This would block the ui for quite a long time if the users internet connection isn’t so fast.
So what we have to do is to create a handler as an inner class of our Activity that takes messages from the thread which gets us the geopoints:
private class RouteHandler extends Handler {
public void handleMessage(Message msg) {
boolean error = msg.getData().getBoolean("error", false);
if (!error) {
// set the geopoints (we can't just add the overlays
// to the map here, because it's on a different thread
geoPoints = (List<GeoPoint>) msg.obj;
post(updateRoute);
} else {
// maybe you want to show an error message here to
// notice the user that the route can not be displayed
// because there's no connection to the internet
}
}
}
Send the geopoints from the RouteThread:
Message msg = new Message();
msg.obj = decodePoly(encoded);
handler.dispatchMessage(msg);
(If you have further data to send, use a Bundle object and add it to the Message.)
And now we need the main thread to update the overlays if there are new geopoints available. Again, we need the handler to accomplish that, because it can start runnables into the same thread the handler was created in.
First we create a runnable in the Activity:
final Runnable updateRoute = new Runnable() {
public void run() {
// draw the path and then invalidate the mapview so that it redraws itself
drawPath(geoPoints, Color.GREEN);
mapView.invalidate();
}
};
And all that is left to do is to call it from inside the handler:
post(updateRoute);
Anyway, this is how we solved the routing in our app. If you have a question, or have suggestions how we could make it better, please leave us a comment!