Wednesday, November 24, 2010

Visualizing data as circles in Google Maps

Datasets that include geographic or spatial data (“geodata”) are often much easier to digest on a map.  When each data point also includes a measure of size or catchment, it could be helpful to represent the point as a circle on the map, with the radius indicating its relative size.

Here’s an example showing the UK car industry, which was created using the Google Maps API (screenshot):

google_map_circles

Source (zip) – all data is fictitious; runs locally in IE

Notice that the circle radius (number of employees) and colour (status) are used to represent different aspects of each car plant.

This makes use of the latest Google Maps API (v3), which includes the ability to draw overlays.  The big winner for me was that all of this, including loading XML and looking up postcodes, could be done with pure javascript – no hosting or server-side code required.

The body of the HTML is just a single DIV element:

  1. <body>
  2.   <div id="map"></div>
  3. </body>

The rest is all javascript.  Firstly, we need to import the Google Maps and jQuery libraries – jQuery isn’t compulsory, but it makes AJAX and XML handling a lot easier.

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>

Loading the XML file

We can use jQuery’s .ajax() method to load the XML file, followed by calls to .find() and .each() to iterate over the elements.

$.ajax({
    type: "GET",
    url: "data.xml",
    dataType: ($.browser.msie) ? "text" : "xml",
    success: function(data) {
        var xml = toXmlDom(data);
            
        // Iterate over postcodes
        $(xml).find('project').each(function(){
            var postcode = $(projectXml).find('postcode').text();
            // ...
        });
    }
});

I’m loath to explain why we need the toXmlDom() function and conditional logic on $.browser.ie, but bascially IE returns plain text from the .ajax() call, so this needs to be converted to an XML DOM object before using .find().

Close your eyes for a second :-)

function toXmlDom(data) {
    if (typeof data != "string") return data;

    // Convert text to xml dom (IE hack)
    var xml = new ActiveXObject("Microsoft.XMLDOM");
    xml.async = false;
    xml.loadXML(data);
    return xml;
}

Displaying the map

This just involves replacing the DIV in our HTML body with a “map” object.

var mapCenter = new google.maps.LatLng(52, -2);
var map = new google.maps.Map(document.getElementById('map'), {
    'zoom': 7,
    'center': mapCenter,
    'mapTypeId': google.maps.MapTypeId.ROADMAP
});

The tricky bit here is getting the initial latitude/longitude and zoom right – sites like www.getlatlon.com will give you a start.

Looking up a postcode

For simplicity, the XML file refers to UK postcodes.  Since the Google Maps API doesn’t include suitable geocoding support (licensing issues), we need to call another service first to get the respective Lat/Long co-ordinates.

$.ajax({
    url: 'http://www.geopostcode.org.uk/api/exact/' + postcode.replace(' ','+') + '.json',
    dataType: 'json',
    success: function(response) {
        var lat = response.wgs84.lat;
        var lon = response.wgs84.lon;
        // ...
    }
});

Notice that .ajax() method again, which simplifies the asynchronous HTTP request to www.geopostcode.corg.uk.  By default, the results of the AJAX call are cached by the browser.

Drawing a circle

This is done in two stages: create a Marker, then draw a Circle which is bound to its position (read the full API).

// Create marker
var marker = new google.maps.Marker({
  map: map,
  position: new google.maps.LatLng(53.1, -2.44),
  title: 'The armpit of Cheshire'
});

// Add circle overlay and bind to marker
var circle = new google.maps.Circle({
  map: map,
  radius: 10000,    // metres
  fillColor: '#AA0000'
});
circle.bindTo('center', marker, 'position');

The Marker and Circle are both google.maps.MVCObject instances, so their values can be tied together using bindTo().  So, for example, setting draggable:true on the Marker would mean that the Circle would follow it around the map.

I suggest you download the source code and see how it all hangs together.

5 comments:

  1. hi tat was the nice post... can u assist me how do the same thing in android phone map view

    ReplyDelete
  2. Ha! struggled for a while had everything working except a circle and came here and your circle code worked right from my copy/paste. Thanks. Now I gotta figure out how to send it miles for the radius.

    ReplyDelete
  3. Hello This was exactly what i was looking to do. Congrats i am jealous. iT seems the source code link does not open anymore.

    ReplyDelete
    Replies
    1. Hi Jacques, thanks for your comment! The attachment still opens for me, maybe the server was down.

      Delete
  4. Thank you did come right downloading the files seem our firewalls were blocking it

    ReplyDelete