August 1, 2014

Maps, ItemizedOverlay, and You!

This post synthesizes a discussion of getting ItemizedOverlay to work held on the [android-developers] Google Group. Many thanks to the folk who chimed in on that thread (particularly MarcelP), and for everyone on the Android Google Groups who have been helping other developers in need.


If you have ever used the full-size edition of Google Maps, you are probably used to seeing things overlaid atop the map itself, such as “push-pins” indicating businesses near the location being searched. In map parlance — and, for that matter, in many serious graphic editors — the push-pins are on a separate layer than the map itself, and what you are seeing is the composition of the push-pin layer atop the map layer.

Android’s mapping allows you to create layers as well, so you can mark up the maps as you need to based on user input and your application’s purpose.

Any overlay you want to add to your map needs to be implemented as a subclass of Overlay. There is an ItemizedOverlay subclass available if you are looking to add push-pins or the like; ItemizedOverlay simplifies this process.

To attach an overlay class to your map, just call getOverlays() on your MapView and add() your Overlay instance to it:

[sourcecode language='java']
Drawable marker=getResources().getDrawable(R.drawable.marker);

marker.setBounds(0, 0, marker.getIntrinsicWidth(), marker.getIntrinsicHeight());
map.getOverlays().add(new SitesOverlay(marker));
[/sourcecode]

We will explain that marker in just a bit.

As the name suggests, ItemizedOverlay allows you to supply a list of points of interest to be displayed on the map — specifically, instances of OverlayItem. The overlay, then, handles much of the drawing logic for you. Here are the minimum steps to make this work:

  1. First, override ItemizedOverlay<OverlayItem> as your own subclass (in this example, SitesOverlay)
  2. In the constructor, build your roster of OverlayItem instances, and call populate() when they are ready for use by the overlay
  3. Implement size() to return the number of items to be handled by the overlay
  4. Override createItem() to return OverlayItem instances given an index
  5. When you instantiate your ItemizedOverlay subclass, provide it with a Drawable that represents the default icon (e.g., push-pin) to display for each item

The marker from the above code snippet is the Drawable used for the last bullet above.

You may also wish to override draw() to do a better job of handling the shadow for your markers. While the map will handle casting a shadow for you, it appears you need to provide a bit of assistance for it to know where the “bottom” of your icon is, so it can draw the shadow appropriately.

For example, here is an ItemizedOverlay subclass (SitesOverlay) that puts four pins on a map of Manhattan:

[sourcecode language='java']
private class SitesOverlay extends ItemizedOverlay {
private List items=new ArrayList();
private Drawable marker=null;

public SitesOverlay(Drawable marker) {
super(marker);
this.marker=marker;

items.add(new OverlayItem(getPoint(40.748963847316034, -73.96807193756104),
“UN”, “United Nations”));
items.add(new OverlayItem(getPoint(40.76866299974387, -73.98268461227417),
“Lincoln Center”, “Home of Jazz at Lincoln Center”));
items.add(new OverlayItem(getPoint(40.765136435316755, -73.97989511489868),
“Carnegie Hall”, “Where you go with practice, practice, practice”));
items.add(new OverlayItem(getPoint(40.70686417491799, -74.01572942733765),
“The Downtown Club”, “Original home of the Heisman Trophy”));

populate();
}

@Override
protected OverlayItem createItem(int i) {
return(items.get(i));
}

@Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
super.draw(canvas, mapView, shadow);

boundCenterBottom(marker);
}

@Override
protected boolean onTap(int i) {
Toast.makeText(NooYawk.this, items.get(i).getSnippet(), Toast.LENGTH_SHORT).show();

return(true);
}

@Override
public int size() {
return(items.size());
}
}
[/sourcecode]

If we open up a MapView and use that overlay, you would see something like this:

An ItemizedOverlay showing two Manhattan locations

An Overlay subclass can also implement onTap(), to be notified when the user taps on the map, so the overlay can adjust what it draws. For example, in full-size Google Maps, clicking on a push-pin pops up a bubble with information about the business at that pin’s location. With onTap(), you can do much the same in Android.

The onTap() method for ItemizedOverlay receives the index of the OverlayItem that was clicked. It is up to you to do something worthwhile with this event.

In the case of SitesOverlay, as shown above, onTap() just tosses up a short Toast with the “snippet” from the OverlayItem, returning true to indicate we handled the tap.