andrew.hedges.name / blog

Add an interactive legend to a MarkerManager managed Google Map

November 22, 2009, 11:22 am · Filed under: Google, JavaScript, Web Development

There’s plenty of help out there for adding a legend to a Google Map. There are resources for working with MarkerManager as well. I can’t believe this is new ground, but there didn’t seem to be anything related to getting an interactive legend to play nice with MarkerManager. Now there is!

On a recent site, the client specified the (quite reasonable) dual requirements of showing different pins at different zoom levels and having a legend on the map that could be used to toggle pins of different types.

Having used the excellent, open source MarkerManager library before, I immediately thought of it for managing the zoom levels. I hadn’t implemented an interactive legend before, so I went a-Googling and found documentation and examples for adding custom map controls. So far, so good.

I stumbled around a bit about how to get the MarkerManager class to filter pins by both zoom level and type until I buckled down and just read the source of the class. Turns out, the solution was pretty simple (which is always a good sign).

In the end, I created a custom map control (LegendControl) that adds and removes groups of pins to and from the MarkerManager instance’s internal grid. It seems so obvious in hindsight!

E.g.

In case you’re confused about what problem I’m trying to solve, interact with the map below. Notice how you get different pins by zooming in and out and how you can turn whole classes of pins on and off.

SoC

Implementing MarkerManager to show and hide large numbers of pins by zoom level is a snap. And, it’s relatively trivial to write a couple of functions to show/hide individual pins by type. In fact, my client supplied me with functions they’d used before on other sites.

I decided from the outset to build the legend as a proper map control because, well, that’s what it is. I’m glad I did because it enforced separation of concerns. The resulting code is more tidy than if I’d just started hacking away.

I won’t go into detail about creating custom controls. You’re welcome to check out the script yourself to see how it’s done.

Now for the How

If you go to the map in a new window and view source, you’ll see a bunch of stuff that’s typical in a Google Maps implementation. For example, the following is the function I use to set up the map, itself:

function initMap() {
  var map, mgr;
  
  // set up your basic map
  map = new GMap2(document.getElementById('map'));
  map.setCenter(new GLatLng(-36.848, 174.756), 13);
  map.addControl(new GLargeMapControl());
  map.addControl(new GScaleControl());
  map.addControl(new GMapTypeControl());
  
  // create markermanager instance
  mgr = new MarkerManager(map);
  
  // add markers to MarkerManager and set up markerGroups
  initMarkers(mgr);
  
  // add legend
  map.addControl(new LegendControl({
    mgr          : mgr,
    markerGroups : markerGroups,
    legendValues : legendValues
  }));
}

See that last bit? That creates our legend control. But, I guess you figured that out, right?

Unfortunately, the way it’s currently written, LegendControl requires certain variables to be in the global scope. Yucky, I know. I’m open to feedback about how to tweak things to better encapsulate the control by passing in the necessary objects and options.

The variables that need to be in the global scope are as follows:

map
Instance of GMap2
mgr
Instance of MarkerManager
legendValues
Array of object literals, each with the following properties: type, description, and pinSrc
markerGroups
Object literal with arrays of markers keyed by type
Update: I’ve refactored the code to eliminate the need for variables to be in the global scope. Amazing what a good night’s sleep can do!

The other only trickiness required by LegendControl is that your markers (instances of GMarker) must have 2 properties in addition to the ones the class adds by default. These are zoomMin and zoomMax and they correspond to the minimum and maximum zoom levels at which the marker should be displayed.

So, by way of example, my function for creating individual markers looks like the following:

/**
 * Create a marker
 * @param object obj Object literal specifying marker attributes
 * @return GMarker
 */
function createMarker(obj) {
  var marker;
  
  marker = new GMarker(new GLatLng(obj.lat, obj.lng), {
    title   : obj.name,
    icon    : MapIconMaker.createMarkerIcon({
      width      : PIN_WIDTH,
      height     : PIN_HEIGHT,
      primaryColor : colors[obj.type]
    })
  });
  
  marker.zoomMin = obj.zoomMin;
  marker.zoomMax = obj.zoomMax;
  
  GEvent.addListener(marker, 'click', function() {
    marker.openInfoWindowHtml('
' + obj.name + '
'); }); return marker; }

Again, go to the map in a new window and view source to see the full implementation. It will make more sense than me spending another 500 words trying to explain it. :-)

I hope you found this helpful. As I said above, any suggestions for reducing the need for variables in the global scope are appreciated.

Update 9 Feb 2010: There has been a bit of interest in Legend Control, so I have put it up on Google Code under the MIT license. The script is free to use, modify, and distribute in both personal and commercial projects. Enjoy!

Short URL to this article: