BLOG

Google Maps UK Postcode Selector with jQuery

10 August 2009 by Stuart Cam

I've put together a web-based UK postcode selector using the KML features of Google Maps and jQuery:

jQuery + Google Maps UK Postcode Selector

Preview the page and download the project zip (231kb).

Firstly, I should give a little background on the UK postal addressing system. The UK postcode database has been put together with the help of public tax money and yet this data is not freely available. In order to obtain and use the Postcode Address File (PAF) from Royal Mail you need to part with a significant amount of money. This is a major pain point for web developers looking to create geolocated services and could be why the US has a lot more exciting applications - they have free access to their zip code data. Surely there must be a better way to manage this data?

After a significant amount of Googling I managed to track down a KML file of the UK postcode boundaries from Map Logic. Using this data and some undocumented Google Map API features it was possible to put together a fairly intuitive post code selector.

Google Maps can accept the address of a KML file (or KMZ - which is a zipped KML file with an alternate file extension). The basic idea is that you can load the KMZ file, intercept polygon load events and opening of the info window. The majority of the work is done using the Google Maps API and some jQuery:

function setUpMap(){

  if (GBrowserIsCompatible()) {

    var mapUrl = "http://blog.codebrain.co.uk/ukpostcodes.kmz";
    var gOverlays = [];
    var isMouseOverMap = false;
    var corner1 = new GLatLng(48.5,-11);
    var corner2 = new GLatLng(60,3.6);
    var allowedBounds = new GLatLngBounds(corner1, corner2);
    var startingLatLong = new GLatLng(52.5,-2.7);
    var startingZoomLevel = 6;

    var map = new GMap2(document.getElementById("map_canvas"));
    map.enableScrollWheelZoom();
    map.addControl(new GSmallMapControl());
    map.setCenter(startingLatLong, startingZoomLevel);

    var overlay = new GGeoXml(mapUrl);
    map.addOverlay(overlay);

    map.addMapType(G_PHYSICAL_MAP);
    map.setMapType(map.getMapTypes()[2]);

    $.event.special.hover.delay = 100;
    $.event.special.hover.delay = 600;

    GEvent.addListener(map,"addoverlay",function(overlay) {
      if (overlay.name && !overlay.isKnown) {
        gOverlays.push(overlay);
        overlay.isKnown = true;
        if(gOverlays.length == 192) {
          var hash = {};
          for(i=0;i<gOverlays.length;i++)
          {
            (function(){
              var overlay = gOverlays[i];
              if(hash[overlay.name]===undefined){
                hash[overlay.name] = "";
                var node = $('#' + overlay.name).next();
                var text = node.html();
                node.html($('<a href="#">' + text + '</a>')
                  .hover(function(){
                        GEvent.trigger(overlay, "click");
                      },function() {
                        map.closeInfoWindow();
                      }));
              }
            })();
          }
        }
      }
    });

    GEvent.addListener(map, "mouseover", function() {
      isMouseOverMap=true;
    });

    GEvent.addListener(map, "mouseout", function() {
      isMouseOverMap=false;
      map.closeInfoWindow();
    });

    GEvent.addListener(map,"infowindowprepareopen", function(iwtabs) {
      var tab = $(iwtabs[0]).get(0);
      var elem = tab.contentElem;
      var postalcode = elem.firstChild.innerHTML;
      if(isMouseOverMap){
        $("#side_bar").stop()
                .scrollTo($('#' + postalcode).parents(".regionTitle"),
                750,
                {onAfter:function(){
                      $('#' + postalcode).parents(".code")
                                 .effect("highlight", {}, 5000);
                         }
                    });
      }
    });

    GEvent.addListener(map, "move", function() {
      if (allowedBounds.contains(map.getCenter())) {
        return;
      }
      var C = map.getCenter();
      var X = C.lng();
      var Y = C.lat();
      var AmaxX = allowedBounds.getNorthEast().lng();
      var AmaxY = allowedBounds.getNorthEast().lat();
      var AminX = allowedBounds.getSouthWest().lng();
      var AminY = allowedBounds.getSouthWest().lat();
      if (X < AminX) {X = AminX;}
      if (X > AmaxX) {X = AmaxX;}
      if (Y < AminY) {Y = AminY;}
      if (Y > AmaxY) {Y = AmaxY;}
      map.setCenter(new GLatLng(Y,X));
    });
  }
}

$(document).ready(function(){

  function removeSelectedPostalCode(code){
    $("#" + code).check('off');
    $("#lst" + code).parent().remove();
  }

  function addSelectedPostalCode(obj){
    var code = $(obj).attr('ID');
    var name = $(obj).next().text();
    $("#checkeditems").append($('<div class="remove">' +
              '<a href="#" id="lst' + code + '">' + name + '</a>' +
              '</div>').click(function(){
                             removeSelectedPostalCode(code);
                      }));
  }

  $('div.code input:checkbox').click(function(){
    if(this.checked){
      addSelectedPostalCode(this);
    }
    else {
      var code = $(this).attr('ID');
      removeSelectedPostalCode(code);
    }
  });

  $("div.code input:checked").each(function(){
                    addSelectedPostalCode(this);
                  });

  setUpMap();

});

$(window).unload(function(){
  GUnload();
});

The Google Maps KML implementation is rather limited. I had originally wanted to alter the shading on a particular post code region when it was selected. I did have a go at using the GeoXML Parser API to address each enclosed region, but the browser crawled to a halt trying to parse the 700Kb uncompressed KML file into separate polygons!

There is a minor issue in addressing regions which have multiple enclosed areas - for example Kirkwall in the far north of Scotland. Unfortunately the Google API loads the polygon data in a fairly random order so I was unable to choose which region I'd like to navigate to when the region is selected in the text list.

Tags: , , , ,

Categories: Google Maps | JavaScript | jQuery

My Current C# Development Stack

07 July 2009 by Stuart Cam

I have been fan boy of the ALT.NET movement for quite some time. I attended a few of the Sydney ALT.NET meetings when they first started and I have to say that they were remarkably more engaging than the Sydney Dot Net User Group (SDNUG). I'd like to extend a thank you to Richard Banks for starting the meetings, I really enjoyed them. If you are in Sydney I recommend you check them out and join the LinkedIn group.

I will be moving back to Bristol, UK on the 10th July 2009 and I am really pleased to hear that an ALT.NET Bristol group is starting up. Socialising, beers and geekery - a perfect combination! If you are interested then don't forget to join the ALT.NET Bristol LinkedIn Group as well.

I have tried to look outside of Redmond for .NET solutions to common problems, despite the fact that the framework is owned and developed by Microsoft. It would be fair to say that the .NET framework has attracted a lot of interest from some very smart software developers who have collaborated to create viable alternatives to Microsoft offerings. The best part is that they are usually free, well supported and open source.

  • Visual Source Safe? I'll take Subversion, thanks anyway.
  • MSTest? I'll take NUnit, thanks anyway.
  • Team System? I'll take CruiseControl, thanks anyway.
  • MSBuild? I'll take NAnt, thanks anyway.
  • ...the list goes on

My current (preferred) development stack looks something like:

If you are a .NET developer staring at the list above and thinking to yourself "what on earth is this guy talking about", I'd suggest that you check the fizzy liquid in your glass isn't Microsoft Kool Aid and visit some of the links!

Tags: , , ,

Categories: .NET | C Sharp | jQuery


© Codebrain 2017. All Rights Reserved. Registered in England: 07744920. VAT: GB 119 4078 13