Embellishing Your Google Map with CSS3 and jQuery

Interactive maps are neat. It’s not hard to get a basic map embedded on your page, but Google provides a rich and easy to use API for embedding and decorating maps on your web page, so there should be no excuse for boring default maps.



There are two options for creating a Google map overlay: KML, or custom javascript. KML is an XML file format used by Google Earth and maps that can specify placemarks and other overlays. It is great for standardization and for efficiently displaying large amounts of data in an overlay, but it limits your metadata and customization options.
For the other option, Google provides an API to add placemarkers and other features in real time to a map. This is what we will be using in this article. Note that we are using version 3 of the API—many tutorials you will find on the internet are using an older version which has been deprecated.

The Basics

To get started, we need to load up jQuery and the Google Maps API library. Note that when including Google Maps you have to set the sensor parameter to true if you are detecting the user’s location (say via GPS). For the typical browser scenario though false is fine.
<!DOCTYPE html>
<html>
<head>
  <script
    src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js">
  </script>
  <script
    type="text/javascript"
    src="http://maps.google.com/maps/api/js?sensor=false">
  </script>
  <script>
    // New code goes here
  </script>
</head>
<body>
  <div id='map_canvas' style='height:500px; width: 700px'></div>
</body>
</html>
That doesn’t show a map yet, it just gets everything ready with the right libraries loaded and a div to place the map into. The rest of the article will assume this basic template.
Every page with a map on it will need to start by actually creating a map object. This is done by specifying some basic options, such as the initial location and zoom level, and the container to place the map into.
$(function() { // jQuery onload handler
  var melbourne = new google.maps.LatLng(-37.813611, 144.963056);
  var mapOptions = {
    zoom:      12,
    center:    melbourne,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  }
  var map = new google.maps.Map($("#map_canvas")[0], mapOptions);
});
From here, we can place various items on to the map. The most common is the “Placemark”, which you have no doubt seen on countless maps, but there are other options such as circles and images. Let’s put a marker on top of Flinders St Station, the hub of Melbourne’s train network.
// continuing on from above
var map = new google.maps.Map($("#map_canvas")[0], mapOptions);
var marker = new google.maps.Marker({
  position: new google.maps.LatLng(-37.818078, 144.966811),
  map:      map,
  title:    'Flinders St Station'
});
There are plenty of options for making your markers a little more snazzy. Let’s give this one a nicer icon and some gratuitous animation.
var marker = new google.maps.Marker({
  position: new google.maps.LatLng(-37.818078, 144.966811),
  map:      map,
  title:    'Flinders St Station',
  icon:     'http://google-maps-icons.googlecode.com/files/train.png'
});
marker.setAnimation(google.maps.Animation.BOUNCE); // Also try DROP
That bounce is quite annoying. Please do remove it again before continuing. I believe I have made my point: you can customize your placemarks.

Behaviour

The Google Maps API exposes a large number of events that we can react to in our program. They aredocumented against each type of object you can place on a map. For this example we will stick to the plain vanilla click event, but there is plenty of scope for interesting interactions with other mouse events and drag/drop.
google.maps.event.addListener(marker, 'click', function() {
  alert("clicked Flinders St");
});
This is the hook we need to create interesting interactions that go above and beyond the standard “info window” pop-up that is the standard dialog used on Google Maps. Using the manipulation and animation functions provided with jQuery, it is trivial to put together something shiny. We will use a semi-transparent overlay that slides into the map to provide extra detail about the clicked placemark. This requires some new HTML:
<div class='map'>
  <div id='map_canvas' style='height:500px; width: 700px'></div>
  <div id='placeDetails'>
    <h1>Flinders St Station</h1>
    <p>
      This is a pretty major train station.
    </p>
  </div>
</div>
It also requires some styling for the new “info window”. There are quite a few lines of CSS in the following snippet, but don’t panic it should be pretty simple to follow. The important parts are labeled with comments.
<style>
  .map {
    width: 700px;
    /* The following are required to allow absolute positioning of the
     * info window at the bottom right of the map, and for it to be hidden
     * when it is "off map"
     */
    position: relative;
    overflow: hidden;
  }
  #placeDetails {
    /* Place the div off the bottom right of the map */
    position: absolute;
    width: 300px;
    bottom: 0;
    right: -320px;
    padding-left: 10px;
    padding-right: 10px;
    /* Semi-transparent background */
    background-color: rgba(0,0,0,0.8);
    color: white;
    font-size: 80%;
    /* Rounded top left corner */
    border-top-left-radius: 15px;
    -moz-border-radius-topleft: 15px;
    -webkit-border-top-left-radius: 15px;
  }
  /* Fit the text nicely inside the box */
  h1 {
    font-family: sans-serif;
    margin-bottom: 0;
  }
  #placeDetails p {
    margin-top: 0;
  }
</style>
Now we have a structure that we can wire up using jQuery. The first step is to make the window simply slide in and out in response to the click event on our placemark.
var currentPlace = null;
var info = $('#placeDetails');
google.maps.event.addListener(marker, 'click', function() {
  if (currentPlace) {
    info.animate({right: '-320px'});
    currentPlace = null;
  } else {
    info.animate({right: '0'});
    currentPlace = marker;
  }
});
This is a great start, but we need to abstract it out somewhat if it is to scale to more than one placemark.

JSON to the rescue

Let’s add Southern Cross Station to the map, the other major rail hub in Melbourne. To do this we will separate our data from our code by moving the definition of a placemark out into a hash. This is always a good idea that leads to understandable and maintainable code.
var places = [
  {
    "title": "Flinders St Station",
    "description": "This is a pretty major train station.",
    "position": [ -37.818078, 144.966811 ]
  },
  {
    "title": "Southern Cross Station",
    "description": "Did you know it used to be called Spencer St Station?",
    "position": [ -37.818358, 144.952417 ]
  }
]
var currentPlace = null;
var info = $('#placeDetails');
$(places).each(function() {
  var place = this;
  var marker = new google.maps.Marker({
    position: new google.maps.LatLng(place.position[0], place.position[1]),
    map:      map,
    title:    place.title,
    icon:     'http://google-maps-icons.googlecode.com/files/train.png'
  });
  google.maps.event.addListener(marker, 'click', function() {
    $('h1', info).text(place.title);
    $('p',  info).text(place.description);
    if (currentPlace == marker) {
      info.animate({right: '-320px'});
      currentPlace = null;
    } else {
      info.animate({right: '0'});
      currentPlace = marker;
    }
  });
});
This is a neat extraction—it is now easy to see and add new places, as well as adapt to any extra meta-data that may be available (such as a phone number for the place). Being able to deal with arbitrary extra meta-data is a strength of this approach which is not easy to do using KML. Note how easy it is to change the title and description of the info window using the jQuery text function.
The other benefit is that the underlying data for the map can now be served up from elsewhere, such as a server process generating JSON on the fly. Move the places array out into a new file places.json (without the var places = part), and we can fetch it easily with jQuery.
$.getJSON('places.json', function(places) {
  $(places).each(function() {
    // As above
  });
});
You may have trouble running this part if you are testing directly off the filesystem due to ajax security policies. The easiest way around it is to serve up both files via a webserver, which is an easy one line if you have ruby installed, otherwise putting it under your apache www root may be comfortable for you.
ruby -rwebrick -e'WEBrick::HTTPServer.new(:Port => 3000, :DocumentRoot => Dir.pwd).start'

Finishing touches

You now have the framework needed to go forth and make your own pretty maps. For bonus points, we can clean up the user experience a bit. When you click one station, then click another, it is currently a bit … bland. There is also no indication on the map as to which marker is selected. Let’s slide the window out again before switching the data, and change over the icon of the currently selected item.
var icons = {
  'train':          'http://google-maps-icons.googlecode.com/files/train.png',
  'train-selected': 'http://dl.dropbox.com/u/3120508/train-selected.png'
}
google.maps.event.addListener(marker, 'click', function() {
  var hidingMarker = currentPlace;
  var slideIn = function(marker) {
    $('h1', info).text(place.title);
    $('p',  info).text(place.description);
    info.animate({right: '0'});
  }
  marker.setIcon(icons['train-selected']);
  if (currentPlace) {
    currentPlace.setIcon(icons['train']);
    info.animate(
      { right: '-320px' },
      { complete: function() {
        if (hidingMarker != marker) {
          slideIn(marker);
        } else {
          currentPlace = null;
        }
      }}
    );
  } else {
    slideIn(marker);
  }
  currentPlace = marker;
});

Comments

Popular posts from this blog

Create Desktop Application with PHP

Insert pandas dataframe into Mongodb

Add and delete columns dynamically in an HTML table