Significant Performance Improvements using Google Maps Clustering Comment
I am very excited about the progress we’ve made with Sensorpedia recently. In just a few short weeks, our team of interns has interfaced dozens of sensor systems and we’ve worked together to iron out issues with the Sensorpedia API and web application.
Beta testers will notice significant performance updates with the latest version of the software. Personally, I have had fun this week experimenting with the open source Marker Clusterer library for the Google Maps API that I’ve now employed, enabling the display of literally hundreds of markers per layer. Use of the clustering library is straightforward and easy to add to your own mapping application. Here are a few snippets from the Sensorpedia application showing how we applied it in our application.
function initMap(){
sp.overviewControl = new GOverviewMapControl(new GSize(200,160));
sp.scaleControl = new GScaleControl();
sp.largeControl = new GLargeMapControl();
sp.typeControl = new GMapTypeControl();
if(GBrowserIsCompatible()){
sp.dataMap = new GMap2(document.getElementById("map"));
sp.dataMap.setCenter(new GLatLng(60, -150) , 4, G_NORMAL_MAP);
sp.dataMap.addControl(sp.largeControl);
sp.dataMap.addControl(sp.typeControl);
// I add the overview control but minimize it by default
sp.dataMap.addControl(sp.overviewControl);
sp.overviewControl.hide();
sp.dataMap.enableGoogleBar();
sp.dataMap.enableScrollWheelZoom();
}
// Our app automatically resizes with the window
// so this call is used to make sure the map
// tiles are properly refreshed
sp.dataMap.checkResize();
// The clustering library lets you set some parameters
// like gridSize and maxZoom that let you tweak the
// library's behavior
var mcOptions = {gridSize:25,maxZoom:9};
sp.mapManager = new MarkerClusterer(sp.dataMap,[],mcOptions);
// Because of the dynamic resizing of the map, the clustering
// library seemed to get confused, but it was fixed by an
// additional call to setCenter()
sp.dataMap.setCenter(new GLatLng(60, -150) , 4, G_NORMAL_MAP);
// The cluster manager also didn't always refresh properly when
// changing zoom levels, so I added a zoomend listener and
// manually call resetViewport()
GEvent.addListener(sp.dataMap,'zoomend',function(){
sp.mapManager.resetViewport();
});
}
Then when populating the map, I iterate over the entries for each feed and add the markers to the cluster manager in batch mode. We also improved performance by using lazy loading of the infowindow instead of binding the window up front. I accomplished this through the use of a click handler as seen in the following code.
// We create an array to push the markers on as they are created
var batch = [];
// Here 's' is the current sensor feed we're working with
$.each(s.entries,function(i,e){
// Get the location from the GeoRSS Point element
var pnt = e['georss:point'];
var locationSplit = pnt.split(" ");
var lat = locationSplit[0];
var lon = locationSplit[1];
var markerLocation = new GLatLng(lat,lon);
// Returns either default icon or icon specified in the Atom document
var markerIcon = getEntryIcon(e);
var entryMarker =
new GMarker(
markerLocation,
{ icon:markerIcon,title:i,id:'dm-'+id,'zIndexProcess':function(){return 777;}}
);
// Add a click handler that performs lazy loading of the infowindow for the marker
// The max width property is previously calculated as a function of the current window size
google.maps.Event.addListener(entryMarker, "click", function() {
var html = getSensorEntryWindowHTML(s,e);
if (html != null) { sp.dataMap.openInfoWindowTabsHtml(markerLocation,html,{maxWidth:infoWidth}); }
});
batch.push(entryMarker);
});
// Add all the created markers to the cluster manager
sp.mapManager.addMarkers(batch);
// Reset the viewport to repaint the map with the new entries
sp.mapManager.resetViewport();
Because I wanted the main sensor feed icons to remain unclustered, I add and manage those separately just using the usual addOverlay() and removeOverlay() functions. I am still experimenting with the clustering library to see if I can use multiple instances of the manager to independently cluster certain groups of markers. As it is now, all entry markers are clustered together as needed regardless of whether or not they are in the same sensor group. With multiple cluster managers, I could limit clustering to only markers within a group. To be honest, I’ve not convinced myself which approach I think is best from an HCI and usability standpoint. But I’m working to implement it so we can test both options with some users and get their feedback.
The following screenshot shows this clustering approach in action with several hundred sensors being displayed at once. I’ve uploaded some additional screenshots of the application with large numbers of icons to our Sensorpedia Flickr Pool.
The performance improvement has been significant after applying this clustering and lazy loading approach. I hope this example will spark some ideas for improving your own application. I’d love to hear your feedback, success stories, or additional tips and tricks.
