MapQuest Developer Blog

Archives for Joel Tulloch

  • Zoom Levels and Image Overlays

    In this post I will continue my discussion of overlays in the MapQuest JavaScript API 5.2. My last two posts covered a couple of methods for adding rollover functionality to overlays. Over the next couple of posts I will discuss some of the options that are available when working with image overlays.

    There are a few things that set image overlays apart from the other overlay types. The one that poses the biggest obstacle is image resolution. Since an image overlay is "pinned" to a map with Lat and Lng coordinates, there is a significant difference in resolution required to display an image properly at different zoom levels. If this becomes an issue for your application, one of the options that is available is the setImageOverlayLevels method provided by the API.

    The setImageOverlayLevels() method takes an array of MQImageOverlayLevel objects as an argument. You create an MQImageOverlayLevel object by passing a string representing the URL of the image and an integer between 1 and 16 representing the zoom level the image should be tied to. Although you can specify 16 different images, you don't need to, as the nearest image to the current zoom level will always be displayed. I have included a function here that I created as an example. The function can be used in your code to implement the use of MQImageOverlayLevel's in your application.

    Listing 1: The addImageUrls() function

      function addImageUrls(MAX_ZOOM, MIN_ZOOM, imageOverlay){   // Lowest resolution to highest.
        var SPAN = MAX_ZOOM-MIN_ZOOM+1;
        if(arguments.length > (SPAN+3)){
          arguments.length = SPAN+3;  // Ensure a maximum of 1 image per zoom level.
        }
        var incrementor = MIN_ZOOM;
        var incrementSize = Math.round(SPAN / (arguments.length - 3));
        var levels = new Array();
        var addImageLevel = function(url,lvl){
          var newLevel = new MQImageOverlayLevel(url,lvl);
          newLevel ? levels.push(newLevel) : alert('Error: Level ' +
            lvl + ' image initialization has failed.'); // No real error checking implemented here.
        };
        // In a production environment, something more
        // thorough would become necessary.
        for(var i=3;i<arguments.length;i++){
          if(incrementor > MAX_ZOOM){
            incrementor = MAX_ZOOM;
          }
          addImageLevel(arguments[i], incrementor);
          incrementor = incrementor+incrementSize;
        }
        imageOverlay.setImageOverlayLevels(levels);
        imageOverlay.setMaxZoomLevel(MAX_ZOOM);
        imageOverlay.setMinZoomLevel(MIN_ZOOM);
      }
    

    The function is called with the following arguments:

    Listing 2: Calling the addImageUrls() function

    	addImageUrls(MAX_ZOOM, MIN_ZOOM, imageOverlay,
    	/* and up to 16 string URLís */)
    

    The values for max and min zoom are used to determine which zoom level to place each image at, as well as to determine when the image won't be displayed at all (because of the incredible difference between zoom level 16 and zoom level 1, there is a good chance that the image overlay will only be applicable for a certain range of values). Next you pass your MQImageOverlay object, followed by a series of strings containing the URL's of the images. You can pass in anywhere from 2 to 16 values, ordered from lowest resolution to highest.

    To close, I've included two screenshots that show MQImageOverlayLevels in a sample application. Other than setting up the map and collecting the shape points, all of the code is contained within the sample function in Listing 1. For simplicity sake, I created two square images of separate colors. When the map is zoomed in beyond the half-way point, the brown image is displayed. When it is zoomed out, a blue image is displayed.

    Image 1: The Image, Zoomed In

    Image 2: The Image, Zoomed Out

  • Adding Rollover Functionality to Overlays: Part 2

    This method, although similar to the method I covered in my last post, uses the InfoWindow of the map rather than a rollover associated with a Point Of Interest (POI). The InfoWindow is populated with the title and content when the overlay is moused-over, and follows the cursor, similar to a tooltip.

    The biggest disadvantage with this method comes from the need to step outside of the API to attach a mousemove event. This means that the solution needs to take browser differences into consideration. In order to simplify the code, and because I generally use ASP.NET for most of my work, you will notice that I have utilized a few shortcuts provided by the Microsoft AJAX Library.

    The most important things to understand here are the loc object and the ScrollTest function. Sys.UI.DomElement.getLocation() is a function provided by the Microsoft AJAX Library which returns an object with two properties, x and y. These coordinates represent the location of the element relative to the window. The ScrollTest function returns an object representing the amount that the window has been scrolled. Using these two values, along with coordinates returned by the mousemove event object, the coordinates of the mouse in relation to the map can be discovered. These coordinates are used to place the InfoWindow on the map.

    Listing 1 - The AddRollover function
    
     // This solution uses the Microsoft AJAX Library.
     // Get it here: http://www.asp.net/ajax/downloads/
    
     // This function is necessary since different
     // browsers implement this in slightly different ways.
     function ScrollTest(){
         var elemental = document.documentElement;
         var lefty = (elemental.scrollLeft ?
                elemental.scrollLeft :
                document.body.scrollLeft);
         var toppy = (elemental.scrollTop ?
                elemental.scrollTop :
                document.body.scrollTop);
    
         return { top : toppy, left : lefty }
     }
    
     function AddRollover(roMap, roOverlay, roTitle, roContent){
         var mapDiv = $get('mapWindow');
    
         roOverlay.setAltColorAlpha(roOverlay.getAltColorAlpha() - 0.2);
         roOverlay.setAltFillColorAlpha(roOverlay.getAltFillColorAlpha() - 0.2);
    
         function MouseFollow(e) {
             var scrooll = ScrollTest();
             var loc = Sys.UI.DomElement.getLocation($get('mapWindow'));
             // $get() is a short form for getElementById()
             var point = new MQPoint(e.clientX + scrooll.left - loc.x,
                 e.clientY + scrooll.top - loc.y);
             roMap.openInfoWindow(point);
         }
    
         MQEventManager.addListener(roOverlay, "mouseover", function(){
             // Each map only has one InfoWindow, so we need to set these values
             // every time the mouse is over an overlay
             roMap.setInfoTitleHTML(roTitle);
             roMap.setInfoContentHTML(roContent);
    
             // $addHandler is simply a cross-browser method for attaching an event handler, provided
             // by the Microsoft AJAX Library
             $addHandler(mapDiv, 'mousemove', MouseFollow);
             roOverlay.setAltStateFlag(true);
         });
    
         MQEventManager.addListener(roOverlay, "mouseout", function(){
             $removeHandler(mapDiv, 'mousemove', MouseFollow);
             roMap.getInfoWindow().hide();
             roOverlay.setAltStateFlag(false);
         });
     }
    	
  • Adding Rollover Functionality to Overlays: Part 1

    Over the next couple of posts I'm going to look at the different rollover capabilities that are available for overlays in the MapQuest JavaScript API. To get started I'll post a simple module that can be imported into any application. To demonstrate, I have included links to an application that uses the module. The original Capture the Flag application can be seen here, and with the new functionality, here.

    The module accomplishes two things. First, it uses the altState fields of the overlay to change the alpha value of the overlay, making it more transparent. Second, it accepts a string as an argument, using that as a title which is displayed in a rollover window. The following image is a screenshot of the Capture the Flag application, shown with the mouse hovering over the overlay on the left.

    Capture The Flag Screenshot.

    As you can see from the included code (Listing 2), mouseover and mouseout event handlers are attached to the overlay. When the mouseover event occurs, the rollover window is displayed and the altStateFlag is set to true. The altStateFlag causes any of the altState fields that have been set to be enabled. In the case of this example, that is the altColorAlpha and altFillColorAlpha fields. On mouseout, the altStateFlag is set to false and the rollover window is removed. Note that although I haven't used them in this example, you can similarly set the altColor, altFillColor, and altBorderWidth of the overlay.

    Once the module has been imported into the application, calling the method is very simple. It accepts the current map, the overlay, and a title as arguments. In this example I have called the method using one of three different titles, depending on the color of the overlay.

    Listing 1 - Calling the jrtmq.addRollover method
    
    myBorders.setBorderWidth(3);
    myBorders.setColor(myColors.a);
    myBorders.setFillColor(myColors.b);
    myBorders.setColorAlpha(0.9);
    myBorders.setFillColorAlpha(0.5);
    myBorders.setShapePoints(LLList);
    switch(document.drawMenu.selectColor.value){
      case 'red':
        jrtmq.addRollover(myMap, myBorders, "The Red Zone!!!");
        break;
      case 'green':
        jrtmq.addRollover(myMap, myBorders, "The Green Zone!!!");
        break;
      case 'grey':
        jrtmq.addRollover(myMap, myBorders, "Neutral Zone");
        break;
    }
    
    myMap.addOverlay(myBorders);
      

    You can download the module as a .js file from here. Or simply create your own version of the function below and add it directly to your code.

    Listing 2 - The AddRollover Function
    
    function AddRollover(roMap, roOverlay, roText){
    
      var pricklyPoints = roOverlay.getShapePoints();
      var count = 0;
      var latTotal = 0;
      var longTotal = 0;
      var centerPoint = new MQLatLng();
      var theFlag;
      var icon = new MQMapIcon();
      var newLat;
      var newLong;
    
      // For this method I'm just averaging out the lat and long of the shape points to place
      // the label. There are more complex ways to do this if you're interested in the
      // centroid (weighted center) or some other means of placing the label in relation to the
      // shape. This, however, should work just fine for the current purposes.
    
      for(var i = 0;i<pricklyPoints.getSize();i++){
        count++;
        latTotal = latTotal + pricklyPoints.getAt(i).getLatitude();
        longTotal = longTotal + pricklyPoints.getAt(i).getLongitude();
      }
      newLat = latTotal / count;
      newLong = longTotal / count;
    
      centerPoint.setLatLng(newLat,newLong);
      theFlag = new MQPoi(centerPoint);
    
      // Get rid of the POI icon, as it's the overlay we're associating it with
      // that is important.
      icon.setImage('',0,0);
      icon.setShadow('');
      theFlag.setIcon(icon);
    
      if(!roText){
        theFlag.setInfoTitleHTML('Default Label');
      } else{
        theFlag.setInfoTitleHTML(roText);
      }
    
      // The AltColorAlpha and AltFillColorAlpha properties are available for any MQShapeOverlay.
      // They respond to the AltStateFlag and along with the AltColor and AltFillColor
      // properties allow you to very simply add a rollover response to any overlay.
      roOverlay.setAltColorAlpha(roOverlay.getAltColorAlpha() - 0.2);
      roOverlay.setAltFillColorAlpha(roOverlay.getAltFillColorAlpha() - 0.2);
    
      MQEventManager.addListener(roOverlay, "mouseover", function(){
    
        // While the mouse is hovering over the overlay we want the rollover window
        // to be displayed, and the AltStateFlag to be true, enabling any of the
        // Alt properties that were set for the overlay. In this case, just the alphas.
        theFlag.showRolloverWindow();
        roOverlay.setAltStateFlag(true);
      });
      MQEventManager.addListener(roOverlay, "mouseout", function(){
    
        // Because there is only one rollover window for each map,
        // the method to hide the window is the same, regardless of
        // what object opened the window.
        roMap.getRolloverWindow().hide();
        roOverlay.setAltStateFlag(false);
      });
    
      // Because this method simply borrows the rollover window from a POI,
      // we need to watch for the overlay to be removed so we can also remove
      // the POI.
      MQEventManager.addListener(roOverlay, "removed", function(){
        roMap.removePoi(theFlag);
      });
    
      roMap.addPoi(theFlag);
    }
    	
  • Capture the Flag: Using a JavaScript API to Enable Interactive Drawing in MapQuest

    Introduction

    The MapQuest Platform provides a powerful means for developers to integrate MapQuest technology into their web applications. In this article, I will demonstrate how to add interactive drawing to a map using the JavaScript-based API. In the resulting application, a user can set up a real-life game of Capture the Flag by drawing components of the game on an online map. You can see the application live here, and you can download the source in a .zip file here.

    Regardless of whether they have actually played Capture the Flag, most people can at least picture it in their heads. Traditionally, the game has been played primarily by children, with two teams pitted against each another in a quest to capture the opposing team's flag. Over the years, there have been innumerable variations of this game, in both the computer realm and in real life. In recent years, urban gaming has grown in popularity. Capture the Flag is just one of a number of games that are being played on an ever-increasing scale on the streets of cities throughout North America and Europe. The application that I show you in this article demonstrates how you can use the MapQuest API to facilitate planning this type of game.

    Starting From the Same Place

    Although the application is not overly complicated, I have set it up a bit differently than the introductory applications in the JavaScript API developer guide. To make things easier to understand, I will introduce a couple of the techniques that I used.

    To begin, I have split the application into three separate files: index.html, StyleSheet.css, and jrtmq.js. The focus of this article will be the code, which is contained almost exclusively within the jrtmq.js file. Within this file, I have created a namespace for all of the script, reducing the number of global symbols and allowing the <script> tags in the HTML file to contain just one line of code:

    Listing 1 - Called from the <script> tags
    
    	jrtmq.initMap();
    

    Because namespacing is not natively supported in JavaScript, you can use many different techniques to accomplish it. The approach I use is described in David Flanagan's book, JavaScript, The Definitive Guide. Essentially, this approach uses a JavaScript object to define the namespace. The first two lines of the file create that object.

    Listing 2 - Basics of a JavaScript namespace
    
      var jrtmq;
      if (!jrtmq) jrtmq = {};
    
      (function() {
        // The majority of the code is contained within this function.
        // ******* The Code *********
        // The following declaration assigns the InitMap function to the jrtmq
        // object. This becomes the public portion of the namespace, and is also
        // the only global symbol, reducing the potential for conflicts.
    
      jrtmq.initMap = InitMap;
    
      })();
    

    The majority of the code for this application is contained within the anonymous function which is called immediately after being loaded into the browser. The final line in that function assigns an initialization function, InitMap, to the jrtmq object that was created at the start of the file. When this method is called, using jrtmq.initMap(), it initializes the application. The remainder of the code (apart from some namespace-wide variables and the initialization function, startUp) resides within five separate objects: InfoBarTextChange, Utilities, ToolboxHandler, Draw, and InitFunc. The focus of this article is interactive drawing, and most of that functionality is contained within the Draw object.

    Working with Overlays

    The base class for all overlays within the JavaScript API is MQShapeOverlay. Although this application strictly uses MQLineOverlay and MQPolygonOverlay, most of the functionality is inherited from MQShapeOverlay. This means that most of the techniques and methods are the same, regardless of the type of overlay.

    The methods that actually do the drawing in the Capture the Flag application are Draw.borders and Draw.finish for polygons, and Draw.flags for points of interest (POIs). (Because of the much simpler nature of placing POIs, I won't discuss this method in the article.) Draw.borders allows the user to draw lines on the map using the MQLineOverlay class. These lines remain on the map while the user is drawing the shape. When the user signifies that the shape is complete, Draw.finish is called, placing an MQPolygonOverlay to replace the lines. I've used a simple HTML button to let users indicate that the shape is complete, but in a more complex application this could be done differently. Utilities.cancelDrawing, the final method in the process, cleans everything up and removes the lines.

    Draw.borders is called from an event listener. I will discuss events within the API later in this article. For now, it is sufficient to know that e is the event object that's passed into the method, and that the event we are handling is a click event (note that all events are lowercase). The event object contains a few different pieces of information, but the one we are concerned with is the latitude and longitude (Lat/Long) of the point that was clicked, e.ll.

    Listing 3 - Draw.borders
    
    	borders:function(e){
                var myLine = new MQLineOverlay();
    
                if(startEnd == "start"){
                    startEnd = "end";
                    ProcessLLFirst = e.ll;
                    UltimateLL.add(e.ll);
                } else{
    	      ProcessLLSecond = e.ll;
                    UltimateLL.add(e.ll);
                    var ProcessLL = new MQLatLngCollection();
                    ProcessLL.add(ProcessLLFirst);
                    ProcessLL.add(ProcessLLSecond);
                    myLine.setColor("#000000");
                    myLine.setShapePoints(ProcessLL);
                    myLine.setBorderWidth(1);
                    myMap.addOverlay(myLine);
                    Lines.add(myLine);
                    ProcessLLFirst = ProcessLLSecond;
                }
            },
    

    The main components of Draw.borders are the myLine object, the startEnd variable, two MQLatLng objects and two MQLatLngCollection objects.

    When the method is first called, startEnd is true. (Although I have not declared startEnd within this method, it is not actually a global variable. It is declared in the anonymous function that encloses all of our code, allowing it to act like a global variable for the purpose of this namespace.) The latitude and longitude of the click are added to both an MQLatLng object and an MQLatLngCollection, UltimateLL. The objects ProcessLLFirst and ProcessLLSecond are used for constructing the lines, and UltimateLL is used for constructing the MQPolygonOverlay by which they will eventually be replaced.

    On subsequent clicks, startEnd is false and e.ll is placed into ProcessLLSecond. The two "process" objects are then added to a new MQLatLngCollection, which is passed to myLine. This step is necessary because all of the MQShapeOverlay objects expect an MQLatLngCollection as the argument for their setShapePoints method. Setting the other attributes is fairly straightforward, and a list of all possible attributes can be found in the API developer guide. After the line is added to the map using the addOverlay method, myLine is added to an MQOverlayCollection, Lines, which will eventually be used to remove the lines from the map.

    Listing 4 - Draw.finish
    
    	finish:function(){
                if(UltimateLL.getAt(1)){
                    var myColors =
                      Utilities.colorGen(document.drawMenu.selectColor.value);
                    var myBorders = new MQPolygonOverlay();
                    var LLList = new MQLatLngCollection();
                    for(var i = 0;i<UltimateLL.getSize();i++){
                        LLList.add(UltimateLL.getAt(i));
                    }
                    myBorders.setBorderWidth(3);
                    myBorders.setColor(myColors.a);
                    myBorders.setFillColor(myColors.b);
                    myBorders.setColorAlpha(0.8);
                    myBorders.setFillColorAlpha(0.4);
                    myBorders.setShapePoints(LLList);
                    myMap.addOverlay(myBorders);
                    Utilities.cancelDrawing();
                }
            }
    

    The method that finishes off the drawing is Draw.finish. As I mentioned above, it is called when the user clicks the Finish button in the toolbox. The method first checks to make sure there are at least two objects present in the UltimateLL collection (preventing an accidental button click from messing things up). If the two objects are present, the method goes on to create a polygon using latitude and longitude coordinates that are contained within the collection. Adding properties to the polygon is fairly straightforward, and is the same as for all MQShapeOverlay objects. There are a couple of things in this method that I will point out, however, before I move on. The first is the Utilities.colorGen method. This method takes a string as an argument and returns an object that contains two string properties representing the border color and the fill color of the polygon.

    Listing 5 - Utilities.colorGen
    
            colorGen:function(convertee){
    
       // For the sake of simplicity, I hardcoded a few values
                // to represent the colors. The ColorGen function could
                // easily be modified to implement a more complicated
                // color selection system. Whatever the system, the function
                // should accept a parameter and return an object containing the
                // two color values.
    
                switch(convertee){
                    case 'red':
                        return { a:"#DF374E", b:"#FFCFCF" };
                    case 'green':
                        return { a:"#008F13", b:"#C7AFFF" };
                    case 'grey':
                        return { a:"#999999", b:"#ffffff" };
                }
            }
    
    

    The second thing I will point out is the LLList collection and the for loop. A problem arises when an object with a scope outside of the immediate function is used to populate the ShapePoints of the MQPolygonOverlay. If that object is then cleared (which is necessary to allow drawing multiple overlays) the ShapePoints for the MQPolygonOverlay object are also cleared, causing the overlay to disappear. Assigning the contents of the UltimateLL collection to a new collection one by one seems to eliminate this problem. Other than that, Draw.finish is fairly straightforward. When the polygon has been added to the map, the Utilities.cancelDrawing method is called, which is the universal method within this application for cleaning up loose ends. Before I discuss that method, though, I'm going to take a step back and talk about events within the API.

    The MQEventManager

    All of the events within the MapQuest JavaScript API are handled by the MQEventManager. Within the Capture the Flag application, after the user selects what they want to draw and the color they want to draw it in, they click the Draw button in the toolbar. This button calls Draw.init, passing it a string representing the object the user would like to draw. For the purpose of this application, there are two choices. Draw.init is the method responsible for initiating the drawing process.

    Listing 6 - Draw.init
    
            init:function(draWhat){
                if(!clean){
                    Utilities.cancelDrawing();
                }
                clean = false;
                myMap.enableDragging(false);
                if(draWhat == 'border'){
                    MQEventManager.addListener(myMap, "click", Draw.borders);
                } else if(draWhat == 'flag'){
                    MQEventManager.addListener(myMap, "click", Draw.flag);
                }
            }
    

    The first thing this method does is check the clean variable. This is just an error-checking variable that is set to false while drawing is in progress. If it is false at this point, something is wrong, so the method calls Utilities.cancelDrawing to clean up before it continues. myMap.enableDragging accepts a Boolean argument and is used to prevent the user from dragging the map, making drawing a lot easier.

    The biggest role that Draw.init plays is to add the appropriate event listener, depending on the user's selection. MQEventManager contains four methods, allowing you to add a listener, clear all of the listeners of a certain type, specify a single listener to remove, or trigger a custom event. Each of the first three methods are used at some point in this application. The arguments for the methods are fairly straightforward as well. The first argument is the object to attach the handler to, the second is the event to listen for, and the third is the callback function.

    I have mentioned the Utilities.cancelDrawing method a number of times now, because it is called in a few different situations. Its main purpose is to provide a "clean slate" after an overlay has been drawn, or if something goes wrong. It does this by reenabling drawing, resetting the necessary variables and collections, and removing any outstanding lines from the map. This leaves the application in essentially the place that it started but obviously doesn't touch any polygons or POIs that the user has intentionally added. When Utilities.cancelDrawing has been called, the drawing process is complete. Thus, the minimum functionality to allow interactive drawing is contained within the following methods: Draw.init, Draw.borders, and Draw.finish.

    	Listing 7 - Utilities.cancelDrawing
    
    	cancelDrawing:function(){
                myMap.enableDragging(true);
                MQEventManager.clearListeners(myMap, "click");
                if(Lines.getAt(0)){
                    startEnd = "start";
                    ProcessLLFirst = ProcessLLSecond = null;
                    UltimateLL.removeAll();
                    for(var i = 0; i < Lines.getSize(); i++){
                        myMap.removeOverlay(Lines.getAt(i));
                    }
                    Lines.removeAll();
                }
                if(!clean){
                    clean = true;
                }
            }
    

    Setting Up the Toolbox

    The last thing I will discuss before I bring it all together is the toolbox. The code for the toolbox is contained in two main areas: InitFunc.toolbar and the ToolboxHandler object.

    InitFunc.toolbar performs a simple task. It is called during the application's initialization sequence, and its sole purpose is to add event handlers to the various elements of the toolbox.

    Listing 8 - InitFunc.toolbar
    
            toolbar:function(){
                var boxLinks =
                  document.getElementById('toolBoxProper').getElementsByTagName('a');
                document.drawMenu.drawType[0].checked = true;
                for(var i = 0;i<boxLinks.length;i++){
                    boxLinks[i].onclick = ToolboxHandler.menu;
                }
                document.getElementById('bt1').onclick = ToolboxHandler.menu;
                document.getElementById('bt2').onclick = ToolboxHandler.menu;
                document.getElementById('bt3').onclick = ToolboxHandler.city;
                document.getElementById('rb1').onclick = ToolboxHandler.radio;
                document.getElementById('rb2').onclick = ToolboxHandler.radio;
    
            }
    

    I've used three separate event-handling methods, all of which are properties of the ToolboxHandler object. ToolboxHandler.menu handles all of the links in the toolbox as well as the two drawing buttons. It uses a switch statement and calls a method based on the ID of element that was clicked. The purpose of ToolboxHandler.radio is to discover whether the user has selected the borders or flags radio button. If the user has selected flags, the handler causes the neutral color to be disabled in the menu. It also causes the information section at the bottom to update, displaying help information related to the current selection.

    The following listing shows ToolboxHandler.city. Using hard-coded coordinates, this method allows the user to select a city from a menu and uses MQTilemap.setCenter() to center the map on that location.

    Listing 9 - ToolboxHandler.city
    
            city:function(){
                switch(document.cityMenu.selectCity.value){
                    case 'toronto':
                        var StartCoords = new MQLatLng(43.648366, -79.385019);
                        break;
                    case 'newyork':
                        var StartCoords = new MQLatLng(40.720409, -73.994637);
                        break;
                    case 'seattle':
                        var StartCoords = new MQLatLng(47.60629, -122.330624);
                        break;
                    case 'vancouver':
                        var StartCoords = new MQLatLng(49.260281, -123.113463);
                        break;
                }
                myMap.setCenter(StartCoords,10);
            }
    

    Putting It All Together

    As I said at the beginning of this article, the only thing we call directly from the HTML file is the jrtmq.initMap method. This method's primary responsibility is calling the initialization function, startUp, after the page has loaded and the required libraries are initialized.

    InitFunc.map initializes the map and adds the zoom control and the map view control, all of which are covered thoroughly in the developer guide.

    Listing 10 - Initialization Functions
    
        // Calls methods to initialize the map, the toolbar, and the text area
        // at the bottom of the page.
    
        function StartUp(){
            InitFunc.map();
            InitFunc.toolbar();
            InfoBarTextChange.welcome();
        }
    
        // The public function that is called directly from the page.
        // It waits for the page 'load' event and the anonymous callback
        // function then uses the MQInitDojo method to call the startUp function
        // once the Dojo Library has been initialized.
    
        function InitMap(){
            if(window.addEventListener){
                window.addEventListener('load', function() {
                  MQInitDojo(StartUp);}, true );
            } else{
                window.attachEvent("onload", function() {MQInitDojo(StartUp);});
            }
        }
    
        // Assign the public portion of our namespace to the jrtmq class.
        jrtmq.initMap = InitMap;
    

    I haven't covered every aspect of the Capture the Flag application here. To make it more than just an example, I have included greater functionality than could be explained in a single article. Most of the remaining functionality, however, is complementary to the drawing capabilities of the application, and should be fairly straightforward to figure out. The code is commented and organized into objects that are fairly self-explanatory. If you haven't done so already, now is probably a good time to download the files and take a look inside. To bring this all together and provide a bit of context, I will summarize the initialization process of the application. This is a summary of what happens when a user first navigates to the index.html page:

    The jrtmq.initMap method is called from the HTML document and starts the initialization sequence using the two main initialization methods, InitFunc.map and InitFunc.toolbar). During that sequence, the map is set up and the MQTileMap object is passed to the variable myMap, event listeners are assigned to all of the components in the toolbox, and the information bar is populated with a welcome message. The main elements of the toolbox can be divided into three sections: the drawing section, the utilities section, and the help section. All of the functionality of the application is initiated from the toolbox and handled by ToolboxHandler.menu, ToolboxHandler.radio, and ToolboxHandler.cities. Each section of the toolbox has an object associated with it that contains most of the functionality for that section: InfoBarTextChange (used mostly for the help section), Utilities, and Draw. Drawing is accomplished primarily with three methods: Draw.init, Draw.borders, and Draw.finish.

    Where to Go from Here

    In this application, there are a lot of areas in which you could add functionality: geocoding specific locations, adding a better color selection system, or simply improving the UI. The modularity of this code lets you apply the principles to any number of contexts or add functionality quite painlessly. As an example of this flexibility, I'll be writing a couple of blog posts in coming weeks to demonstrate adding rollover windows to overlays by adding a module to this application.

    And finally, here are a few links on urban gaming to get your imagination going: