Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art
HomeF.A.Q.
Community
Writing Recipes
Login
Drawing multiple location markers on a MapView
Contributed by Johan Pelgrim 2011-07-24 04:09:31 (updated 2011-10-03 11:28:19)
In Published Edition? Yes
Minimum Version 1.5
2
Votes
Problem

You have several GeoPoints which you want to display on a Google MapView.

Solution

Implement the ItemizedOverlay abstract class and add various OverlayItems to it.

Discussion

Introduction

If you want to draw multiple location markers in your MapView you can of course take the approach of implementing the Overlay interface and do all the resource gathering and drawing in an overridden draw() method, as was done in the Draw a location marker on a Google MapView recipe. This can become cumbersome and hard to maintain. If you want to do core drawing of lines and shapes you cannot avoid overriding the draw() method, but when it comes down to drawing several simple location markers and handling user clicks on those marker (to name something) the Google Maps API has introduced the ItemizedOverlay. This abstract class is meant to maintain a list of Overlay items and display it as an aggregated Overlay on the MapView. ItemizedOverlay itself implements the Overlay interface. Besides that it implements sorting north-to-south for drawing, creating span bounds, drawing a marker for each point, and maintaining a focused item. It also matches screen-taps to items, and dispatches focus-change events to an optional listener. This looks like the right candidate to display a couple of location markers on our MapView.

Adding the ItemizedOverlay to your MapView

Let's begin with the skeleton Google Maps project which is described in Getting ready for Google Maps development, or create your own and check this recipe's check-list at the end of the recipe to make sure you are good-to-go.

Add an inner class to your MapActivity which extends ItemizedOverlay and implement the abstract methods and the default constructor. The ItemizedOverlay uses your implementations of the createItem and size() methods to get hold of all the overlay items in your implementation and do the aggregation.


    private class MyItemizedOverlay extends ItemizedOverlay<OverlayItem> {

        public MyItemizedOverlay(Drawable defaultMarker) {
            super(defaultMarker);
        }

        @Override
        protected OverlayItem createItem(int i) {
            return null;
        }

        @Override
        public int size() {
            return 0;
        }
	}

The defaultMarker is a Drawable which is drawn on every OverlayItem we add to our ItemizedOverlay. Whenever you add a drawable to an OverlayItem you must set its bounding rectangle via the setBounds method. Or you can use one of the two convenience methods boundCenterBottom or boundCenter which sets the bounding rect to the center-bottom respectively the center of the drawable. Note: a call to boundCenterBottom basically results in this call to setBounds (given marker is an instance of Drawable: marker.setBounds(-marker.getIntrinsicWidth()/2, -marker.getIntrinsicHeight(), marker.getIntrinsicWidth() /2, 0);. Typically the constructor is rewritten like this:

        public MyItemizedOverlay(Drawable defaultMarker) {
            super(boundCenterBottom(defaultMarker));
        }

We want to add several OverlayItem instances so we add a List to this inner type and modify the createItem(int i) and size() methods to use our new list.


        private List<OverlayItem> mOverlays = new ArrayList<OverlayItem>();
        
        @Override
        protected OverlayItem createItem(int i) {
            return mOverlays.get(i);
        }

        @Override
        public int size() {
            return mOverlays.size();
        }
        

So far so good. Now we add a convenience method to add OverlayItems to our internal list.

        public void addOverlayItem(OverlayItem overlayItem) {
            mOverlays.add(overlayItem);
            populate();
        }

The populate() method is a utility method which perform all processing on a new ItemizedOverlay. We provide Items through the createItem(int) method. Rule of thumb is to call this as soon as we have data in our ItemizedOverlay, before anything else gets called.

We're basically done with our inner class. Let's add some statements to our onCreate method of the surrounding MapActivity to add some OverlayItemss to our implementation of ItemizedOverlay

Using MyItemizedOverlay in onCreate

Let's expand our onCreate method and create an instance of our MyItemizedOverlay inner type.

Drawable makerDefault = this.getResources().getDrawable(R.drawable.marker_default);
MyItemizedOverlay itemizedOverlay = new MyItemizedOverlay(makerDefault);

Now let's add some overlay items. When creating an OverlayItem we must provide three things to the constructor. A GeoPoint and two Strings, one for the title and one for an additional snippet of text. Let's add an OverlayItem for the city of Amsterdam.

    GeoPoint point = new GeoPoint(52372991, 4892655);
    OverlayItem overlayItem = new OverlayItem(point, "Amsterdam", null);
    itemizedOverlay.addOverlayItem(overlayItem);

Let's add another convenience method to our MyItemizedOverlay inner type which basically takes two int values for latitude and longitude and a String for a title.

public void addOverlayItem(int lat, int lon, String title) {
    GeoPoint point = new GeoPoint(lat, lon);
    OverlayItem overlayItem = new OverlayItem(point, title, null);
    addOverlayItem(overlayItem);
}

We can now rewrite our addition of the Amsterdam OverlayItem and add two more, one for London and one for Paris.

    itemizedOverlay.addOverlayItem(52372991, 4892655, "Amsterdam");
    itemizedOverlay.addOverlayItem(51501851, -140623, "London");
    itemizedOverlay.addOverlayItem(48857522, 2294496, "Paris");

The next step is to add our itemized overlay to the MapViews overlays. We get a handle to the list over overlays with a call to getOverlays().

mapView.getOverlays().add(itemizedOverlay);

Finally we manipulate the MapViews MapController to show the right area and zoom-level on our MapView. We set the center to a GeoPoint of Dunkerque, which appears to be a nice center. There is no getCenter() convenience method in the ItemizedOverlay class, but this is something you can easily implement yourself if you want to. We can set the zoom-level to a fixed level, but the ItemizedOverlay class does have some nice methods to calculate the span which covers all it's overlay items. We use this to call zoomToSpan on the MapController instance.

MapController mc = mapView.getController();
mc.setCenter(new GeoPoint(51035349, 2370987)); // Dunkerque, Belgium
mc.zoomToSpan(itemizedOverlay.getLatSpanE6(), itemizedOverlay.getLonSpanE6());

We're done! When you fire up your app you should see something like this.

Extra exercise: Draw an alternate marker Search Google for some nice 100 by 100 pixel markers and place them in your ./res/drawable directory. Add these drawables as an extra argument to your addOverlayItem convenience method. When you create your OverlayItem instance use the setMarker(Drawable drawable) method to assing a different marker drawable. Remember to set the bounds by calling the boundCenterBottom or boundCenter convenience methods or do the math yourself and call setBounds. Good luck! (The accompanying source code has the solution if these hints are not sufficient).

Do something when the user clicks your marker Finally the ItemizedOverlay class has some nice features to handle taps and focus changes on your overlay items. In this final section we will implement the onTap(int index) method to show a Toast message which displays our overlay item's title. Of course you can do whatever you want when a user taps your marker, show a dialog or another activity, draw a view on the map with addView, etc. As you will see this could not be simpler!

@Override protected boolean onTap(int index) {

   Toast.makeText(MainActivity.this, getItem(index).getTitle(), Toast.LENGTH_LONG).show();
   return true;

}

We return true to indicate we have handled the tap-event. If we return false the onTap is executed for all the overlay items in our ItemizedOverlay

Again, when taking your app for a spin, you should see something like this when you tap near your Paris location marker.

Comments (0)
Leave a comment
Edit History (11)
There are no (moderator-approved) comments on this recipe yet.