Google Mapping with Convio CMS Toolkit

Google Maps can be used to create a compelling presentation of your organizations data. In this example, we will follow our ficticious traveler, Marcel, as makes his way around the Bay Area and chronicles his adventures on his CMS powered Web site.

For each of Marcel's travel entries, we will capture the following information and use the CMS content types to store and then render the data on a Google Map.

Information to capture for this content type:

  • Title
  • Date
  • Place Name
  • Place Location (lat/long)
  • Travelogue Body

Here are the steps we'll take to create the content type and then map the data:

  1. Create the Content Type
  2. Create a single display template
  3. Create a list display template
  4. Mashup the display template with the Google Maps API
  5. Create map markers
  6. Add the data to the map automatically

Or jump to the finished page to see this mashup in action [See it work]


1) Create the content type 

To do this in Convio CMS, we first create a Travelogue content type:

2) Create the single display template

Next, we create a single display template for a Travelogue item.  This is the page which provides the most detail about a single travelogue entry.

Which looks like this when published:

3) Create the list display template

Next, we will make a chronological list of Travelogue entries by creating a list display template which repeats the contents based on the number of Travelogue entries.  This can be as simple or as fancy as we want.

Which looks like this when published

That is nice, but what would really be cool would be to plot all of the Travelogue items on a map. To do that, we need to make a new List Display template that includes the Google Maps API code plus the data representing the various Travelogue entries.

4) Mashup the display template with the Google Maps API

To use the Google Maps API, you need to sign up for a Google Maps API key. You can then use the API by including the following "script" tag in your code:

<script src="http://maps.google.com/?file=api&key=[YOUR_KEY_HERE]&v=2.x" type="text/javascript"></script>


Google provides a handy function to determine if a web browser is Google Maps compatible or not. This is handy to use when wrapping our code to degrade gracefully in the event that it won't work for a particular browser:

<script type="text/javascript">
//<![CDATA[<br> if (GBrowserIsCompatible()) { <BR>
// Place all the Google Maps code in here

} else {
  alert("Sorry, your web browser is not compatible with Google Maps");
}
//]]>
</script>
<noscript>
<p>Sorry, you do not have Javascript enabled. You will not be able to enjoy the travelogue map.
</noscript>


OK, now let's write some code. To instantiate a Google Map, we call

new GMap2: // "mapDiv" specifies the ID of the DIV tag that will contain the map
var map = new GMap2(document.getElementById("mapDiv"));

We can add some controls:

map.addControl(new GLargeMapControl());
map.addControl(new GOverviewMapControl());
map.addControl(new GMapTypeControl());

Hmm, this is is getting large enough to be encapsulated in a function. I'll let it take a parameter that specifies the :

//
// function setUp
// Returns: the map
//
function setUp() {
  var map = new GMap2(document.getElementById("mapDiv"));
  map.addControl(new GLargeMapControl());
  map.addControl(new GOverviewMapControl());
  map.addControl(new GMapTypeControl());
  return map;
}


5) Create the map markers

That's better. Now what about putting some markers on the map? To do that, we need to define another function createMarker that creates a marker:

// function createMarker
// point: the location of the marker
// icon: the icon to use to show the marker
// html: the markup for the popup info window (optional)
// Returns: the marker
//
function createMarker(point, html) {
  var marker = new GMarker(point);
  // If there is something to show in the popup...
  if (html != null) {
    GEvent.addListener(marker, "click",
              &nbs p;       function() {
              &nbs p;       marker.openInfoWindowHtml(html);
              &nbs p;     });
  }
  return marker;
}


Now we can create some markers. Oh wait, no we can't. We don't have any data. Normally in a List Display Template, we render the data with presentation markup; however, since this time we will be using the Google Maps API for the presentation, we just need to render the data in a way that we can easily parse it in our code. We will use the built in browser DOM functions to accomplish that, but first, let's store our data in a structured way:

<div id="mapData" style="display:none">
<t:list>
 <div>
  <span>${title}</span>
  <span>${url}</span>
  <span>${firstPublicationDate}</span>
  <span>${location}</span>
  <span>${latitude}</span>
  <span>${longitude}</span>
  <span>${body}</span>
 </div>
</t:list>
</div>


Notice the style we applied to the outer DIV tag. This causes the browser to hide the DIV so the user doesn't see our structured data in the rendered web page.

6) Add the data to the map

Now we have some data. We need a function to read the data, convert it into markers and add it to the map:

// Define some "constants"
var TITLE = 0;
var URL = TITLE + 1;
var DATE = URL + 1;
var LOCATION = DATE + 1;
var LATITUDE = LOCATION + 1;
var LONGITUDE = LATITUDE + 1;
var BODY = LONGITUDE + 1;

// Define a class to represent a Travelogue
function Travelogue(data) {
  var fields = data.getElementsByTagName("SPAN");

  this.title = fields[TITLE].innerHTML;
  this.url = fields[URL].innerHTML;
  this.pubDate = fields[DATE].innerHTML;
  this.location = fields[LOCATION].innerHTML;
  this.latitude = fields[LATITUDE].innerHTML;
  this.longitude = fields[LONGITUDE].innerHTML;
  this.body = fields[BODY].innerHTML;

  this.getTitle = function () { return this.title; }
  this.getURL = function () { return this.url; }
  this.getPubDate = function () { return this.pubDate; }
  this.getLocation = function () { return this.location; }
  this.getLatitude = function () { return this.latitude; }
  this.getLongitude = function () { return this.longitude; }
  this.getBody = function () { return this.body; }

}

function loadData(map) {
  // Find the DIV containing the data
  var dataDiv = document.getElementById("mapData");
  if (dataDiv == null) { return; }

  // Get a list of all of the inner DIVs each of which contains one
  // data record
  var records = dataDiv.getElementsByTagName("DIV");
  if (records.length == 0) { return; }

  // Iterate over each record
  for (var r = 0; r < records.length; r++) {
    // Get the fields for this record
    var travelogue = new Travelogue(records[r]);

    // we need to format some HTML for the popup
    var html = getInfoWindowHTML(travelogue);

    // Create a Google Maps point
    var point = new GLatLng(travelogue.getLatitude(), travelogue.getLongitude());

    // create the marker
    var marker = createMarker(point, html);

    // add the new marker to the map
    map.addOverlay(marker);

  }
}


Now we are getting there! We need to get our code to run when the page is loaded. So first we'll need to update the setUp function to load the data. Then we need to get it to run when the page loads We can accomplish that (in a browser-safe manner) like so:

function setUp() {
  var map = new GMap2(document.getElementById('mapDiv'));
  map.addControl(new GLargeMapControl());
  map.addControl(new GOverviewMapControl());
  map.addControl(new GMapTypeControl());
  loadData(map);
  return map;
}
if (window.attachEvent) {
  window.attachEvent("onload", setUp);
  window.attachEvent("onunload", GUnload);
} else {
  window.addEventListener('load',setUp,false);
  window.addEventL istener('unload',GUnload,false);
}


Note the additional function registered to the unload event handlers GUnload. This is a Google Maps API call to clean up after you are done using the maps. It releases all the memory allocated to the map display code. Don't forget to include this.

Now we have a dynamic map displaying all of Marcel's travelogue posts in all of their geospatial beauty. We could call it a day and be satisfied with our accomplishments or we could make it even better. Wouldn't it be nice to use a marker icon that better reflected our website's personality? Of course it would be.

Google Map icons consist of an image and a shadow. The shadow is used to create the 3-d effect.

You can use your graphic skills to create your own images. Once you've created them and uploaded them to your website, you can tell Google to use your custom icons:

// Create a new Icon
var customIcon = new GIcon();
customIcon.image = 'http://www.oursite.org/assets/maps/icons/image.png';
customIcon.shadow = 'http://www.oursite.org/assets/maps/icons/shadow.png';

// Google needs to be told the image dimensions
customIcon.iconSize = new GSize(25,41);
customIcon.shadowSize = new GSize(55,41);

// This defines the anchor point of the info window w.r.t. the icon
customIcon.infoWindowAnchor = new GSize(55,41);

// create the marker using our custom marker icon
var marker = createMarker(point, customIcon, html);


If you wanted to use sequenced anchors (like this), you could create an entire set of custom icons (e.g. custom1.png, custom2.png) and then create a new GIcon for each travelogue.

That's it, now see it work!