GEOG5870/1M: Web-based GIS A course on web-based mapping

Code Efficiency

In theory we can now create maps with numerous markers (although there are limits to how many can be usefully applied before they need to be clustered – that is, grouped together; which we'll look at later). However, you will notice a degree of redundancy in the JavaScript code: we have two sets of lines (for each marker) that are almost identical. If we were to add a large number of markers, the code would be very repetitive, a clear sign that we should generalise the code used. The following code shows a revised version. We have added an additional function called addMarker(). This takes three parameters: a position (which must be a LatLng object), a title and some informational text. Having got these, the function creates a marker and an infowindow in the same manner as in the previous example, substituting in the parameters of the function where previously we had hard-coded values. Here, then, is where all that complexity over using the same name for our markers and infowindows plays out: having got that working, we can now build this kind of function.

var map;
var myCentreLat = 53.8;
var myCentreLng = -1.6;
var initialZoom = 10;

function infoCallback(infowindow, marker) {
   return function() {
      infowindow.open(map, marker);
   };
}

function addMarker(myPos,myTitle,myInfo) {
   var marker = new google.maps.Marker(
      {position: myPos, map: map, title: myTitle}
   );
   var infowindow = new google.maps.InfoWindow({content: myInfo});
   google.maps.event.addListener
      (marker, 'click', infoCallback(infowindow, marker));
}

function initialize() {
   var latlng = new google.maps.LatLng(myCentreLat,myCentreLng);
   var myOptions = {
      zoom: initialZoom,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
   };
   map = new google.maps.Map
      (document.getElementById("map_canvas"), myOptions);

   // First marker
   var info =
      "<div class=infowindow>
      <h1>Leeds</h1><p>Population: 715,402</p></div>";
   addMarker(
      new google.maps.LatLng(53.7996388,-1.5491221),"Leeds",info
   );

   // Second marker
   var info =
      "<div class=infowindow>
      <h1>Bradford</h1><p>Population: 467,665</p></div>";
   addMarker(
      new google.maps.LatLng(53.7938530,-1.7524422),"Bradford",info
   );

}

Here's the code for download.

The main initialize() function has also been updated. Instead of the full process of creating a marker that we used previously, we have replaced this with a line that creates some content to be displayed in the infowindow, and then a call to our new addMarker() function:

   var info =
      "<div class=infowindow>
      <h1>Leeds</h1><p>Population: 715,402</p></div>";
   addMarker(
      new google.maps.LatLng(53.7996388,-1.5491221),"Leeds",info
   );

We have removed several lines of identical code, although the lines creating the two markers are still quite similar, suggesting that the process can be further generalised. The next example shows a further revision of this code to make it even more generalisable and compact:

var map;
var myCentreLat = 53.8;
var myCentreLng = -1.6;
var initialZoom = 10;

/*
 * The data that we want to map */
var markerData = [
   {'name': 'Leeds',
   'lat': 53.7996388,
   'lng': -1.5491221,
   'pop': 715402},
   {'name': 'Bradford',
   'lat': 53.7938530,
   'lng': -1.7524422,
   'pop': 467665}
];

function infoCallback(infowindow, marker) {
   return function() {
      infowindow.open(map, marker);
   };
}

function addMarker(myPos,myTitle,myInfo) {
   var marker = new google.maps.Marker(
      {position: myPos, map: map, title: myTitle}
   );
   var infowindow = new google.maps.InfoWindow({content: myInfo});
   google.maps.event.addListener
      (marker, 'click', infoCallback(infowindow, marker));
}

function initialize() {
   var latlng = new google.maps.LatLng(myCentreLat,myCentreLng);
   var myOptions = {
      zoom: initialZoom,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
   };
   map = new google.maps.Map
      (document.getElementById("map_canvas"), myOptions);

for (id in markerData) {
   var info = "<div class=infowindow><h1>" +
      markerData[id].name + "</h1><p>Population: " +
      markerData[id].pop + "</p></div>";
   addMarker(new google.maps.LatLng(
      markerData[id].lat,markerData[id].lng), markerData[id].name,info);
 }
}

Here's the code for download.

There are two significant changes. Firstly, we have added a new array called markerData near the top of the code, and secondly we have added a loop structure that uses this data near the end of the code. The data array is constructed using the [] syntax to declare an array. As shown it consists of two entries. Each entry is an object, declared using the JSON {} notation as a series of key:value pairs. Four properties are defined in each object – a name, a latitude, a longitude and a population count. Note that we (may) want to use the population as a numeric value so it is written as a number (without a ',' separator for the thousands).

Notice that while the new loop is a for loop, it is a different type to that illustrated in the introduction to JavaScript material:

for (id in markerData) {
   var info = "<div class=infowindow><h1>" +
      markerData[id].name + "</h1><p>Population: " +
      markerData[id].pop + "</p></div>";
   addMarker(new google.maps.LatLng(
      markerData[id].lat,markerData[id].lng), markerData[id].name,info);
}

In this style, the generic form is for (variable in object) {statement}. The loop cycles through each element of object (often an array) and carries out statement. In each iteration, variable will allow us to access members of the object. In this case, id acts as an index value for entries in the array markerData. Note, however, that what you get out of variable can change depending on the type of object you are looping through: for an array of objects like this, we get an index number; for other kinds of collections we may get actual objects out. Whatever we get out, we do not need to know or worry about how many entries there are in the object – the for loop looks after this for us.

This approach allows us to refer to values using standard notation, for example markerData[id].name is the value of the property name for the idth element of the array markerData. In the body of the loop, we construct a version of the variable info, joining together strings with values from the data array. We then call the addMarker() function, supplying a new LatLng object (declared using lat and lng values from our data array), the name value and the new info string.

Adding additional markers can now be done solely by extending the markerData array with new entries. We can also revise the page structure slightly to move the data into a separate file, thus separating the page layout (the HTML document), the map construction code (the main JavaScript file) and the map content (the marker data). This has been done in the following files:

The first file shown is google_eg10.html, the HTML page that hosts our map. It is largely the same as our previous examples, with the important exception that we have added another script tag in the HEAD section, to load the file markerdata_google_eg10.js.

The HTML file google_eg10.html:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://maps.google.com/maps/api/js"></script>
<script type="text/javascript" src="markerdata_google_eg10.js"></script>
<script type="text/javascript" src="map_setup_google_eg10.js"></script>
<link rel="stylesheet" media="all" href="map_style_eg10.css" type="text/css">
<title>Google Maps example 10</title>
</head>

<body onload="initialize()">

<h1>Google Maps examples</h1>
<p>
[<a href="../index.html">Personal home page</a> |
<a href="index.html">Geog5870 work</a> ]</p>
<div class="gmap" id="map_canvas" style="height: 400px; width: 600px"></div>
<p>This example uses generalised methods to display a set of markers</p>
<p>Data contained in marker information show counts from the 2001 Census<p>
<ul>
<li>Source: 2001 Census: Standard Area Statistics (England and Wales)
<li>Census output is Crown copyright and
is reproduced with the permission of the Controller
of HMSO and the Queen's Printer for Scotland.
</ul>
</body>
</html>

We have also modified the page text to include citation information for the data used, following the wording shown on the census citation page.

The second file is map_setup_google_eg10.js. Again, this is largely the same as in our previous example; the only difference is that we have removed the declaration of the markerdata array from this file.

The main JavaScript file map_setup_google_eg10.js
var map;
var myCentreLat = 53.8;
var myCentreLng = -1.6;
var initialZoom = 10;

function infoCallback(infowindow, marker) {
   return function() {
      infowindow.open(map, marker);
   };
}

function addMarker(myPos,myTitle,myInfo) {
   var marker = new google.maps.Marker(
      {position: myPos, map: map, title: myTitle}
   );
   var infowindow = new google.maps.InfoWindow({content: myInfo});
   google.maps.event.addListener
      (marker, 'click', infoCallback(infowindow, marker));
}

function initialize() {
   var latlng = new google.maps.LatLng(myCentreLat,myCentreLng);
   var myOptions = {
      zoom: initialZoom,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
   };
   map = new google.maps.Map
      (document.getElementById("map_canvas"), myOptions);

for (id in markerData) {
   var info = "<div class=infowindow><h1>" +
      markerData[id].name + "</h1><p>Population: " +
      markerData[id].pop + "</p></div>";
   addMarker(new google.maps.LatLng(
      markerData[id].lat,markerData[id].lng), markerData[id].name,info);
}

Instead, this array is declared in a separate file, markerdata_google_eg10.js. The format is the same as before; here it has been extended to include three other markers (and thus shows all districts in West Yorkshire).

The JavaScript file containing the data markerdata_google_eg10.js
/*
 * The data that we want to map
*/
var markerData = [
   {'name': 'Leeds',
   'lat': 53.7996388,
   'lng': -1.5491221,
   'pop': 715402},
   {'name': 'Bradford',
   'lat': 53.7938530,
   'lng': -1.7524422,
   'pop': 467665},
   {'name': 'Calderdale',
   'lat': 53.7420418,
   'lng': -1.9952690,
   'pop': 192405},
   {'name': 'Kirklees',
   'lat': 53.5933432,
   'lng': -1.8009509,
   'pop': 388567},
   {'name': 'Wakefield',
   'lat': 53.6829650,
   'lng': -1.4990970,
   'pop': 315172}
];

The final file shown is a revised version of map_style.css. This has been expanded to include some styles for the infowindow class.

The CSS file map_style_eg10.css
body {
   background-color: white;
   color: black;
   font-size: small;
   font-family: Arial,Helvetica,san-serif;
}

.gmap {
   margin: 5px;
   border: thin solid black; float: right;
}

.infowindow{
   border: thin solid black;
}

.infowindow h1{
   border: thin solid gray;
   background-color: #eeeeee;
}

The first new entry draws a border around the window, and the second one illustrates the way in which we can supply an alternate style for H1 heading elements inside a division of a given class (in this case, infowindow). These styles are probably not what you would want in practice, and are simply shown to illustrate the way that styles can be applied.


Tasks

  1. Modify your example to include the changes illustrated in this page:


[ Next: Changing the marker appearance ]
[Course Index]