Hacking Together a Custom Google Map

Developing a customized map for Google Maps is really a three-step process. The first step is putting together a suitable XML document to house your map data. The second step is creating an HTML web page that creates the map and handles the majority of the work in running the application. And the third step is creating an XSLT stylesheet that is responsible for formatting content to be displayed in an information window when a marker is clicked on the map. You've already completed the first step, and now it's time to knock out the second two.

Displaying the Custom Map

Earlier in the lesson I let you in on the fact that interacting with the Google Maps API requires a decent understanding of the JavaScript programming language. Knowing this, it shouldn't come as too much of a surprise that the HTML document that actually displays a custom Google Map contains a fair amount of JavaScript code. Fortunately, even if you don't know much about JavaScript I've already primed you with the how and why that a typical mapping application uses the Google Maps API. With that in mind, take a look at Listing 15.2, which contains the complete code for the condomap.html web page.

Listing 15.2. The HTML Document that Houses the Customized Google Map
 1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 2:   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 3: <html xmlns="http://www.w3.org/1999/xhtml">
 4:   <head>
 5:       <title>Condominium Map</title>
 6:       <script src="http://maps.google.com/maps?file=api&v=1&key=
 7:       ABQIAAAASyb3gcwJHvHRgYeL6xQGZRScAy-eqpGBgb_U5UIf4tD_
 8:       qtSUMBQD201ZWuwo7NWFLUKzESpdimx61w" type="text/javascript"></script>
 9:   </head>
10:   <body style="text-align:center">
11:     <p style="font:bold 14pt arial; color:maroon">Condominium Map</p>
12:     <div  style="width:700px; height:450px"></div>
13:     <script type="text/javascript">
14:     //<![CDATA[
15:     // Initialize the map and icon variables
16:     var map = new GMap(document.getElementById("map"));
17:     map.addControl(new GLargeMapControl());
18:     map.addControl(new GMapTypeControl());
19:     var point = new GPoint(-86.853171, 36.071689);
20:     map.centerAndZoom(point, 6);
21:     var baseIcon = new GIcon();
22:     baseIcon.iconSize = new GSize(48, 40);
23:     baseIcon.shadowSize = new GSize(75, 32);
24:     baseIcon.iconAnchor = new GPoint(24, 38);
25:     baseIcon.infoWindowAnchor = new GPoint(24, 20);
27:     function createMarker(proj, point, description) {
28:       // Create the custom icon
29:       var icon = new GIcon(baseIcon);
30:       if (proj.getAttribute("status") == "active") {
31:         icon.image = "mapicon_forsale.png";
32:         icon.shadow = "mapicon_forsale_sh.png";
33:       }
34:       else {
35:         icon.image = "mapicon_sold.png";
36:         icon.shadow = "mapicon_sold_sh.png";
37:       }
39:       // Create the marker and register the info window listener function
40:       var marker = new GMarker(point, icon);
41:       GEvent.addListener(marker, "click", function() {
42:         marker.openInfoWindowXslt(description, "projects.xsl"); });
43:       return marker;
44:     }
46:     // Open and process the condo XML document
47:     var request = GXmlHttp.create();
48:     request.open("GET", "condos.xml", true);
49:     request.onreadystatechange = function() {
50:       if (request.readyState == 4) {
51:         // Get the nodes
52:         var xmlDoc = request.responseXML;
53:         var projs = xmlDoc.documentElement.getElementsByTagName("proj");
54:         var locations = xmlDoc.documentElement.getElementsByTagName(
55:           "location");
56:         var descriptions = xmlDoc.documentElement.getElementsByTagName(
57:           "description");
59:         // Iterate through the nodes, creating a marker for each
60:         for (var i = 0; i < projs.length; i++) {
61:           var point = new GPoint(parseFloat(locations[i].getAttribute(
62:              "long")), parseFloat(locations[i].getAttribute("lat")));
63:           var marker = createMarker(projs[i], point, descriptions[i]);
64:           map.addOverlay(marker);
65:         }
66:       }
67:     }
68:     request.send(null);
69:     //]]>
70:     </script>
71:   </body>
72: </html>

Roughly the first part of this web page should be somewhat familiar to you thanks to the earlier primer on the Google Maps API. Most of this code follows the general template you saw earlier regarding how a map is created (line 16), controls are added (lines 17 and 18), a default view is established (lines 19 and 20), and so on. Even the code that creates a custom icon is somewhat similar to the icon code you saw earlier in the tutorial except in this case the status attribute (of the <proj> tag) in the XML document is checked to determine which icon to use (lines 30 through 37). This is the code that results in a different marker being displayed on the map based upon the status (active or completed) of each real estate project. You'll notice that two marker images are set for each status condition: one for the marker icon and one for its shadow. Figure 15.6 shows the four marker icons used in this sample application.

Figure 15.6. The condominium map sample mapping application relies on four custom marker icon images.

The figure shows how a marker that appears on a Google Maps map actually consists of two icons: the marker image and a shadow image with transparency. When combined, these two icons provide a clever visual trick that makes the markers appear to rise off of the map in 3D.

Getting back to the code for the mapping HTML document, each custom icon is used to create a custom marker in the createMarker() function that also establishes the information window and links it to the projects.xsl stylesheet (lines 40 to 43). This code is a little tricky in that a special listener function is created to automatically display an information window in response to the user clicking a marker. It isn't terribly important for you to understand every nuance in this codethe main thing to note is that this is where the projects.xsl stylesheet is getting connected to the information window.

The last big chunk of code in the HTML document is where the XML document is opened, read, and processed. The XML file is opened on line 48, and then its relevant nodes (proj, location, and description) are grabbed on lines 53 through 57. A loop is then entered that cycles through the projects, creating a marker on the map for each one via calls to the previously mentioned createMarker() function (lines 60 through 65).

The HTML document has now created the custom map in Google Maps, complete with unique markers and information windows ready to spring into action when the markers are clicked. The last thing to address is the XSLT stylesheet that makes the information windows worth looking at.

Styling a Custom Information Window

The XSLT stylesheet for the condominium map application has only one responsibility: format the name and address of the real estate project and display it next to the thumbnail image of the project. Considering the power of XSLT, this is a fairly simple responsibility because it only requires performing some basic transforming of XML content into styled HTML content. Listing 15.3 contains the code for the projects.xsl stylesheet.

Listing 15.3. The projects.xsl XSLT Stylesheet that Transforms a Project Description into HTML Code
 1: <?xml version="1.0"?>
 3: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/
 4: Transform">
 5:   <xsl:template match="/">
 6:     <xsl:apply-templates select="description" />
 7:   </xsl:template>
 9:   <xsl:template match="description">
10:     <table style="width:320px; height:140px; text-align:left">
11:       <tr>
12:         <td colspan="2" style="font-family:arial; font-weight:bold;
13:         color:maroon">
14:           <xsl:value-of select="name" />
15:         </td>
16:       </tr>
17:       <tr style="vertical-align:top">
18:         <td style="font-family:arial">
19:           <div style="font-size:10pt">
20:             <xsl:value-of select="address" /><br />
21:             <xsl:value-of select="address2" />
22:           </div>
23:         </td>
24:         <td>
25:         <div>
26:           <xsl:apply-templates select="img" />
27:         </div>
28:         </td>
29:       </tr>
30:     </table>
31:   </xsl:template>
33:   <xsl:template match="img">
34:     <img>
35:       <xsl:attribute name="width">166px</xsl:attribute>
36:       <xsl:attribute name="height">125px</xsl:attribute>
37:       <xsl:attribute name="src"><xsl:value-of select="." /></xsl:attribute>
38:     </img>
39:   </xsl:template>
40: </xsl:stylesheet>

The stylesheet begins by immediately matching up the <description> tag and applying the description template (lines 5 to 7). The <description> tag serves as the container for the more specific project description tags (<name>, <address>, <address2>, and <img>), so it makes sense to start out by processing it first. Within the description template, the project name is first transformed into HTML so that it appears as a row in a table in a bold maroon font (lines 11 through 16). The table cell for the name is given a column span (colspan) of 2 to indicate that it spans both of the cells in the next row. This is necessary so that the name remains aligned to the left edge of the information window.

The second row in the table consists of two cells; the first cell contains the address of the project while the second cell contains the thumbnail image. The address of the project is transformed so that it appears aligned to the top of the cell (lines 18 to 23). The thumbnail image is placed in the next cell with no special formatting (lines 24 to 28), although the <img> tag does use its own template to get the job done (lines 33 through 39). The main point of this template is to specify the width and height of the image, which is consistent for all of the project thumbnails, along with specifying the actual source file for the image.

This wraps up the XSLT stylesheet for the condominium map example. You're finally ready to try out everything in a web browser and see how the finished product works.

Testing Out the Finished Map

This sample application represents one of the most complete examples. It's admittedly somewhat of a challenge to merge several different technologies into a single example without it getting overly complex but this example remained reasonably manageable while combining XML, JavaScript, and XSLT in a single application. Hopefully you'll find the end result to be worth the effort. Figure 15.7 shows the condominium map example upon first being loaded as viewed in Internet Explorer.

Figure 15.7. The condominium map sample application starts out with all of the project markers in view.

Not surprisingly, the application starts out with all the project markers in view on the map. This is no accident, by the wayI carefully selected the initial viewing area and zoom level of the map (line 20 in Listing 15.2) so that you could see all of the projects. Don't forget that what makes your custom Google Maps application so cool is that you can still use all of the familiar navigational features built into Google Maps. You can drag the map around to view other areas, as well as zoom in and out on the projects and their surroundings. You can also switch back and forth between Map, Satellite, and Hybrid views. Perhaps most importantly, you can click any of the project markers to get information about each specific project. Figure 15.8 shows the condominium map zoomed in very tight with one of the project information windows open.

Figure 15.8. Clicking on a marker in the example opens up an information window containing the project name, address, and thumbnail image.

This map reveals how close two of the projects are located to each other. Hopefully it also reveals how valuable this tool could be to potential customers who are searching for real estate in a certain neighborhood or area of a city. It's important to keep in mind the practical implications of a custom mapping application, and what value it brings to other people who might end up using it.