Chapter 4. Geolocation and Mapping APIs

Chapter 3 introduced us to using the W3C Geolocation API to collect location information from the user’s browser with JavaScript code. Although that is the whole point of this book, it is not extremely useful to collect geolocation information unless you, the developer, are going to do something with it. One effective application for collecting a user’s location is to place that point on a map. Let’s face it, mapping a location (or multiple locations) is a common thing to do in the world of GIS.

There are many solutions available for web-mapping applications—Google Maps JavaScript API V3, Bing Maps AJAX Control, Version 7.0, Esri ArcGIS JavaScript API 2.2, Yahoo Maps AJAX API, and OpenStreetMap API v0.6 to name a few (more than a few actually). Most of the APIs available to do web mapping are very similar in nature. Because of this, I have decided to focus on just a couple of the APIs available, and leave it up to you to pick the API that best suits your needs.

After giving you a taste of these APIs for use in your applications, we can then explore what to do with the information you have collected so that it can be referenced by other applications or replotted in the future. This is as important as being able to map the geolocations being collected, since most GIS applications are going to be interested in more than a single user’s point information. To this end, we will look at different ways to save our geolocation information so that it can be consumed by these other applications.

A Google Maps Example

The Google Maps JavaScript API lets you embed Google Maps in your own web pages. Version 3 of this API is especially designed to be faster and more applicable to mobile devices, as well as traditional desktop browser applications.[9] With this API, it is easy for a developer to embed a map that functions just like the http://maps.google.com/ web page, and to customize its look and functionality to suit the needs of the application being built. It is a very popular API for building web applications, currently being used in over 150,000 websites.[10]

The Google Maps API, Briefly

For the purposes of this book, I have included all of the code for the Google Maps application into a single HTML file to make it easier to read. If I were to create an actual application, I would break the Cascading Style Sheet (CSS) rules and JavaScript into their own files (perhaps multiple files should the application be more complex) as a better programming practice.

Take a look at the code in Example 4-1, which contains everything needed to create a simple Google Map application.

Example 4-1. A simple Google Map
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>A Simple Google Map</title>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
    <meta charset="utf-8"/>
    <style type="text/css">
      html { height: 100% }
      body { height: 100%; margin: 0; padding: 0 }
      #map { height: 100% }
    </style>
    <script type="text/javascript" 
        src="http://maps.google.com/maps/api/js?sensor=false"></script>
    <script type="text/javascript">
      var map;

      /* This is called once the page has loaded */
      function InitMap() {
        /* Set all of the options for the map */
        var options = {
          zoom: 4,
          center: new google.maps.LatLng(38.6201, -90.2003),
          mapTypeId: google.maps.MapTypeId.ROADMAP,
          mapTypeControl: true,
          mapTypeControlOptions: {
            style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
            position: google.maps.ControlPosition.BOTTOM_CENTER
          },
          panControl: true,
          panControlOptions: {
            position: google.maps.ControlPosition.TOP_RIGHT
          },
          zoomControl: true,
          zoomControlOptions: {
            style: google.maps.ZoomControlStyle.LARGE,
            position: google.maps.ControlPosition.LEFT_CENTER
          },
          scaleControl: true,
          scaleControlOptions: {
            position: google.maps.ControlPosition.BOTTOM_LEFT
          },
          streetViewControl: true,
          streetViewControlOptions: {
            position: google.maps.ControlPosition.LEFT_TOP
          }
        };

        /* Create a new Map for the application */
        map = new google.maps.Map(document.getElementById('map'), options);
      }

      /* A utility object for simple event handlilng */
      var Utils = { };

      Utils.addEvent = (function() {
        return function addEvent(eventObj, event, eventHandler) {
          if (eventObj.addEventListener) {
            eventObj.addEventListener(event, eventHandler, false);
          } else if (eventObj.attachEvent) {
            event = 'on' + event;
            eventObj.attachEvent(event, eventHandler);
          } else {
            eventObj['on' + event] = function() { eventHandler() };
          }
        };
      }());

      Utils.removeEvent = (function() {
        return function removeEvent(event) {
          if (event.preventDefault) {
            event.preventDefault();
            event.stopProgagation();
          } else {
            event.returnValue = false;
            event.cancelBubble = true;
          }
        };
      }());

      Utils.addEvent(window, 'load', InitMap);
    </script>
  </head>
  <body>
    <div id="map"></div>
  </body>
</html>

The code in Example 4-1 produces a map like that shown in Figure 4-1. I will step through this code in more detail in a moment, but there are several things that should be noted right away:

  • The application is written in HTML5.

  • The Google Maps JavaScript API is included in the application by calling it from Google’s site.

  • There are a couple of utility JavaScript functions that aid in cross-browser compliant event handling.

  • A Google Map is created by specifying the container, a <div> element, to hold the map, and a set of options that allow the developer to customize the look of the map’s controls.

A simple Google Map in Chrome
Figure 4-1. A simple Google Map in Chrome

Specifying a DOCTYPE in the application guarantees that browsers will render in standards-compliant mode, making it more cross-browser friendly. I chose HTML5 as it will soon be the industry standard, but any true DOCTYPE may be used.

The Google Maps JavaScript API is included in the application with the following line:

<script type="text/javascript" 
    src="http://maps.google.com/maps/api/js?sensor=false"></script>

This makes the latest version of the API available for the application. The parameter sensor is set to false to indicate that the map is not using a sensor to determine the user’s location.

Note

When geolocation is added to Example 4-1, the value of the sensor parameter will be changed to true so that the API knows that a location will be gathered by a “sensor,” like the GPS locator in a phone.

Lastly, before looking at the Google Map JavaScript API specific code, the <meta> element specifying a viewport is recognized only by the iPhone right now, and tells it to set the application to full-screen and not allow the user to resize the application. Other smartphones may take advantage of this element in the future.

The Utils variable is nothing more than an object that holds cross-browser event handling functions that are used to create a more flexible application. By using the Utils.addEvent() method, this code can be plugged into an existing application and the developer does not have to worry about overwriting an existing onload function that may already be present. If a JavaScript library like jQuery or Dojo is being used in the application, then it will most likely have built-in methods that also take the hassle out of cross-browser event handling.

A Map is created by instantiating a new google.maps.Map object and specifying the element that will contain the map. The element is referenced using the document.getElementById() DOM method. The Map object also takes an options object that controls everything else about the map.

Map Options

By default, the Google Maps JavaScript API provides controls that enable basic map navigation and type switching. In addition, all devices have keyboard handling on by default. The default controls can be disabled using the Map’s disableDefaultUI property, and individual controls can be manipulated using their corresponding properties.

In Example 4-1, the following controls were configured for the map:

zoom

The default zoom level was set to 4 in the example. The zoom property can range from 0 to 21+, where 0 is a view of the whole world, and 21 is down to individual buildings.

center

Defines the center of the map by a pair of coordinates.

mapType

The Google Maps JavaScript API makes the following map types available: ROADMAP, SATELLITE, HYBRID, and TERRAIN.

mapTypeControl, panControl, zoomControl, scaleControl, streetViewControl

These controls are toggled on or off with values of true and false. In addition, each has specific configuration options along with a position property.

For more information on options available for the Map object, visit the Google Maps JavaScript API V3 Developer’s Guide and API Reference .

Adding Geolocation to Google Maps

As we saw in Chapter 3, there are three main components needed in order to add geolocation to the Google map in Example 4-1: a call to getCurrentPosition(), a successCallback function to do something with the position when we get it, and an errorCallback function in case something goes wrong. We will create a function called getLocation() to handle checking for the navigator.geolocation object and for making our initial call for a location. This function will take advantage of a global variable called browserSupport that will eventually let our errorCallback function know if the error is from the API or a lack of browser support. I am doing this so that all of our error handling is in one function instead of having error alerts spread throughout the code. This way, if I choose to do something more robust with my error handling other than simply alert the user to a problem, all of the error code is in one place.

Example 4-2 illustrates this new functionality implemented into our Google Map example. Note that changes and additions to the code are highlighted in bold for easier identification.

Example 4-2. Adding geolocation to a Google Map
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Adding Geolocation to a Google Map</title>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
    <meta charset="utf-8"/>
    <style type="text/css">
      html { height: 100% } – 
      body { height: 100%; margin: 0; padding: 0 }
      #map { height: 100% }
    </style>
    <script type="text/javascript" 
        src="http://maps.google.com/maps/api/js?sensor=true"></script>
    <script type="text/javascript">
      var map;
      var browserSupport = false;
      var attempts = 0;

      /* This is called once the page has loaded */
      function InitMap() {
        /* Set all of the options for the map */
        var options = {
          zoom: 4,
          center: new google.maps.LatLng(38.6201, -90.2003),
          mapTypeId: google.maps.MapTypeId.ROADMAP,
          mapTypeControl: true,
          mapTypeControlOptions: {
            style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
            position: google.maps.ControlPosition.BOTTOM_CENTER
          },
          panControl: true,
          panControlOptions: {
            position: google.maps.ControlPosition.TOP_RIGHT
          },
          zoomControl: true,
          zoomControlOptions: {
            style: google.maps.ZoomControlStyle.LARGE,
            position: google.maps.ControlPosition.LEFT_CENTER
          },
          scaleControl: true,
          scaleControlOptions: {
            position: google.maps.ControlPosition.BOTTOM_LEFT
          },
          streetViewControl: true,
          streetViewControlOptions: {
            position: google.maps.ControlPosition.LEFT_TOP
          }
        };

        /* Create a new Map for the application */
        map = new google.maps.Map(document.getElementById('map'), options);
        
        /* Add Geolocation */
        getLocation();
      }

      /* 
       * If the W3C Geolocation object is available then get the current 
       * location, otherwise report the problem
       */
      function getLocation() {
        /* Check if the browser supports the W3C Geolocation API */
        if (navigator.geolocation) {
          browserSupport = true;
          navigator.geolocation.getCurrentPosition(plotLocation, 
              reportProblem, { timeout: 45000 });
        } else
          reportProblem();
      }

      /* Plot the location on the map and zoom to it */
      function plotLocation(position) {
        attempts = 0;

        var point = new google.maps.LatLng(position.coords.latitude, 
            position.coords.longitude);
        var marker = new google.maps.Marker({
          position: point
        });

        marker.setMap(map);
        map.setCenter(point);
        map.setZoom(15);
      }

      /* Report any errors using this function */
      function reportProblem(e) {
        /* Is this a support issue or an API issue? */
        if (browserSupport) {
          switch (e.code) {
            case e.PERMISSION_DENIED:
              alert('You have denied access to your position. You will ' +
                  'not get the most out of the application now.');
              break;
            case e.POSITION_UNAVAILABLE:
              alert('There was a problem getting your position.');
              break;
            case e.TIMEOUT:
              /* Three changes to get the location before a true timeout */
              if (++attempts < 3) {
                navigator.geolocation.getCurrentPosition(plotLocation, 
                    reportProblem);
              } else
                alert('The application has timed out attempting to get ' +
                    'your location.');
                break;
            default:
              alert('There was a horrible Geolocation error that has ' +
                  'not been defined.');
          }
        } else
          alert('Geolocation is not supported by your browser.');
      }

      /* A utility object for simple event handlilng */
      var Utils = { };

      Utils.addEvent = (function() {
        return function addEvent(eventObj, event, eventHandler) {
          if (eventObj.addEventListener) {
            eventObj.addEventListener(event, eventHandler, false);
          } else if (eventObj.attachEvent) {
            event = 'on' + event;
            eventObj.attachEvent(event, eventHandler);
          } else {
            eventObj['on' + event] = function() { eventHandler() };
          }
        };
      }());

      Utils.removeEvent = (function() {
        return function removeEvent(event) {
          if (event.preventDefault) {
            event.preventDefault();
            event.stopProgagation();
          } else {
            event.returnValue = false;
            event.cancelBubble = true;
          }
        };
      }());

      Utils.addEvent(window, 'load', InitMap);
    </script>
  </head>
  <body>
    <div id="map"></div>
  </body>
</html>

The first thing to note in this example is that sensor=true when we make the call to the Google JavaScript API, since we are using geolocation in this example. The getLocation() function is called right after our map object is instantiated.

Next, we define our two callback functions: plotLocation() and reportProblem(). plotLocation() will be passed a Position object that will contain all of the geolocation information, while reportProblem() will be passed a PositionError object that will contain an error code and message.

The plotLocation() function creates a LatLng object based on the passed latitude and longitude of the Position object, and from that LatLng object a Marker object is created. The Marker is placed on the map, and then the map is centered and zoomed to the current geolocation.

The reportProblem() function, meanwhile, simply alerts the user to the specific error the application has, based either on the browserSupport variable, or the PositionError code that is passed to the function. If the error is a timeout, the application will make three attempts at getting the current position of the user before giving up and reporting a problem.

Adding Geolocation for Other Browsers

The code in Example 4-2 works for browsers that support the W3C Geolocation API, but what about browsers that do not? Remember back in Chapter 1 when I discussed other browser solutions, and in particular geo-location-javascript? There are severe limitations to the geolocation functionality that this JavaScript library gives, but it is one solution that attempts cross-browser compatibility. Our Google Maps example is simple enough that we can use this library and not worry too much about the lack of functionality. Example 4-3 shows implementing a cross-browser geolocation solution using the geo-location-javascript library. Again, changes and additions to the code are highlighted in bold for easier identification.

Example 4-3. Adding geolocation for other browsers to a Google Map
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Adding Geolocation for Other Browsers to a Google Map</title>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
    <meta charset="utf-8"/>
    <style type="text/css">
      html { height: 100% }
      body { height: 100%; margin: 0; padding: 0 }
      #map { height: 100% }
    </style>
    <script type="text/javascript" 
        src="http://maps.google.com/maps/api/js?sensor=true"></script>
    <script type="text/javascript" src="gears_init.js"></script>
    <script type="text/javascript" src="geo.js"></script>
    <script type="text/javascript">
      var map;
      var browserSupport = false;

      /* This is called once the page has loaded */
      function InitMap() {
        /* Set all of the options for the map */
        var options = {
          zoom: 4,
          center: new google.maps.LatLng(38.6201, -90.2003),
          mapTypeId: google.maps.MapTypeId.ROADMAP,
          mapTypeControl: true,
          mapTypeControlOptions: {
            style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
            position: google.maps.ControlPosition.BOTTOM_CENTER
          },
          panControl: true,
          panControlOptions: {
            position: google.maps.ControlPosition.TOP_RIGHT
          },
          zoomControl: true,
          zoomControlOptions: {
            style: google.maps.ZoomControlStyle.LARGE,
            position: google.maps.ControlPosition.LEFT_CENTER
          },
          scaleControl: true,
          scaleControlOptions: {
            position: google.maps.ControlPosition.BOTTOM_LEFT
          },
          streetViewControl: true,
          streetViewControlOptions: {
            position: google.maps.ControlPosition.LEFT_TOP
          }
        };

        /* Create a new Map for the application */
        map = new google.maps.Map(document.getElementById('map'), options);
        
        /* Add Geolocation */
        getLocation();
      }

      /* 
       * The browser will now use whatever geolocation API is available to
       * it; hopefully it will be the W3C Geolocation object that is used to 
       * get the current location. If there is no geolocation support at all,
       * then report the problem. 
       */
      function getLocation() {
        /* Check if the browser supports any geolocation API */
        if (geo_position_js.init()) {
          browserSupport = true;
          geo_position_js.getCurrentPosition(plotLocation, 
              reportProblem);
        } else
          reportProblem();
      }

      /* Plot the location on the map and zoom to it */
      function plotLocation(position) {
        var point = new google.maps.LatLng(position.coords.latitude, 
            position.coords.longitude);
        var marker = new google.maps.Marker({
          position: point
        });

        marker.setMap(map);
        map.setCenter(point);
        map.setZoom(15);
      }

      /* Report any errors using this function */
      function reportProblem() {
        /* Is this a support issue or an API issue? */
        if (browserSupport)
          alert('Could not locate your device.');
        else
          alert('Geolocation is not supported by your browser.');
      }

      /* A utility object for simple event handlilng */
      var Utils = { };

      Utils.addEvent = (function() {
        return function addEvent(eventObj, event, eventHandler) {
          if (eventObj.addEventListener) {
            eventObj.addEventListener(event, eventHandler, false);
          } else if (eventObj.attachEvent) {
            event = 'on' + event;
            eventObj.attachEvent(event, eventHandler);
          } else {
            eventObj['on' + event] = function() { eventHandler() };
          }
        };
      }());

      Utils.removeEvent = (function() {
        return function removeEvent(event) {
          if (event.preventDefault) {
            event.preventDefault();
            event.stopProgagation();
          } else {
            event.returnValue = false;
            event.cancelBubble = true;
          }
        };
      }());

      Utils.addEvent(window, 'load', InitMap);
    </script>
  </head>
  <body>
    <div id="map"></div>
  </body>
</html>

Calls to gears_init.js and geo.js load the libraries we are using for geolocation in this example. All of the Map functionality remains the same as in Example 4-2.

Instead of checking for navigation.geolocation, in this example, the geo-location-javascript API is initialized and will return whether or not the browser supports any of the geolocation APIs that geo-location-javascript does. A simpler call to getCurrentPosition() is made, without the timeout set, but otherwise the getLocation() function is very similar to this same function in Example 4-2.

Nothing changed between the two geolocation examples in the plotLocation() function, however there are big changes in the reportProblem() function. First, note that there is no PositionError object passed to the function—geo-location-javascript does not have this functionality. The error handling is very simplistic, and this is one of the biggest drawbacks of this API. As I said earlier, this works adequately because we are using a simple example. However, should the geolocation needs be more complex, a lot of additional coding will be needed to get the application working correctly.

An ArcGIS JavaScript API Example

Esri’s ArcGIS API for JavaScript allows the developer to take advantage of all the mapping, editing, geocoding, and geoprocessing services that Esri offers. With this API, a developer is able to embed a map that functions, like those on http://www.arcgis.com/, and to customize its look and functionality to satisfy the needs of the application being built. The JavaScript API is hosted on ArcGIS Online and is freely available for use. Many sites use this API for their GIS needs, especially when their desktop and server GIS needs are met using Esri enterprise software. At the time of this writing, the current version of the API is 2.2.

The ArcGIS JavaScript API, Briefly

Again, for this book, I have included all of the code for the ArcGIS JavaScript Map application into a single HTML file to make it easier to read. In a production application, I would break the CSS and JavaScript into their own files.

Take a look at the code in Example 4-4, which contains everything needed to create a simple ArcGIS JavaScript Map application.

Example 4-4. A simple Esri ArcGIS Map
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=7"/>
    <meta http-equiv="viewport" 
        content="initial-scale=1, maximum-scale=1, user-scalable=no"/>

    <title>A Simple Esri ArcGIS Map</title>

    <link rel="stylesheet" href="http://serverapi.arcgisonline.com/jsapi/arcgis/ \
        2.2/js/dojo/dijit/themes/claro/claro.css"/>
    <style type="text/css">
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
        width: 100%;
      }

      #map {
        height: 100%;
        width: 100%;
      }    
    </style>

    <script type="text/javascript">
      var djConfig = { parseOnLoad: true };
    </script>
    <script type="text/javascript" 
        src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.2">
    </script>
    <script type="text/javascript">
      dojo.require('esri.map');

      var map;
      var initialExtent = {
        xmin: -119.3324,
        ymin: 26.3156,
        xmax: -72.3568,
        ymax: 55.0558,
        /* 
         * Web Mercator (102113), or WGS 84 (4326) - these are the 
         * only two that support continuous pan across the date line 
         */  
        spatialReference: { wkid: 4326 }
      };
      var startExtent;
      var basemap;

      function initApp() {
        var startExtent = new esri.geometry.Extent(initialExtent);

        map = new esri.Map('map', { 
          extent: startExtent, 
          wrapAround180: true
        });

        basemap = new esri.layers.ArcGISTiledMapServiceLayer(
            'http://server.arcgisonline.com/ArcGIS/rest/services/' + 
            'ESRI_StreetMap_World_2D/MapServer');
        map.addLayer(basemap);
      }

      dojo.addOnLoad(initApp);
    </script>
  </head>
  <body class="claro">
    <div id="map"></div>
  </body>
</html>

The code in Example 4-4 produces a map like that shown in Figure 4-2. I will step through this code in more detail in a moment, but there are several things that should be noted right away:

  • The application is written in HTML5.

  • The Esri ArcGIS API for JavaScript is included in the application by calling it from ArcGIS Online.

  • The Dojo Toolkit, with its immense functionality, is also included in the API call to ArcGIS Online.

  • An ArcGIS JavaScript Map is created by specifying the container, a <div> element, to hold the map, and a set of inline options that define aspects of the map being created.

As with the Google Map examples, I chose to write this application in HTML5, as it will soon be the industry standard. Plus this will give it additional functionality for creating more impressive maps in the future. Any true DOCTYPE may, of course, be used and the application will run fine.

A simple Esri ArcGIS Map in Chrome
Figure 4-2. A simple Esri ArcGIS Map in Chrome

The Esri ArcGIS JavaScript API and Dojo Toolkit are included in the application with the following line:

<script type="text/javascript" 
    src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.2">
</script>

The version of the API you wish to use is specified in the querystring of the API call, in this case the latest version: 2.2.

Once again, I am using a <meta> element to specify the viewport, telling it to set the application to full-screen and not allow the user to resize the application—this is an iPhone recognizable element. There is a second <meta> element, however, that is used for Internet Explorer browsers, telling them to interpret and display the application as Internet Explorer 7 would. This element should change as usage of the IE7 browser finally disappears.

A Map is created by instantiating a new esri.Map object and specifying the element that will contain the map. The element is referenced by its id value. The Map object also accepts an “options” object that controls initial extent and other map values. For example, the wrapAround180 property, which is new to the 2.2 API, tells the map whether or not to continuously pan across the date line. In all previous versions of the JavaScript API, the map would not scroll across the date line like other web-mapping applications do.

For more information on options available for the Map object, or API details in general, visit the ArcGIS API for JavaScript Resource page.

Adding Geolocation to Esri Maps

You probably noticed the similarities between the map applications in Example 4-1 and Example 4-4 or, more specifically, the way in which a map was created with each API. While the applications themselves are fairly similar, the way geolocation is added to both of them is nearly identical. To add W3C Geolocation API code to the ArcGIS JavaScript application, we add the same basic code that we did for the Google Map.

First, we will create a function getLocation() to handle checking for the navigator.geolocation object and for making the call to getCurrentPosition(). This function will once again take advantage of a global variable called browserSupport that will eventually let our errorCallBack function know if the error is from the API or a lack of browser support. Example 4-5 shows the geolocation functionality added to our Esri ArcGIS Map example. Changes and additions to the original code are highlighted in bold.

Example 4-5. Adding geolocation to an Esri ArcGIS Map
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=7"/>
    <meta http-equiv="viewport" 
        content="initial-scale=1, maximum-scale=1, user-scalable=no"/>

    <title>Adding Geolocation to an Esri ArcGIS Map</title>

    <link rel="stylesheet" href="http://serverapi.arcgisonline.com/jsapi/arcgis/ \
        2.2/js/dojo/dijit/themes/claro/claro.css"/>
    <style type="text/css">
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
        width: 100%;
      }

      #map {
        height: 100%;
        width: 100%;
      }    
    </style>

    <script type="text/javascript">
      var djConfig = { parseOnLoad: true };
    </script>
    <script type="text/javascript" 
        src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.2">
    </script>
    <script type="text/javascript">
      dojo.require('esri.map');

      var map;
      var initialExtent = {
          xmin: -119.3324,
          ymin: 26.3156,
          xmax: -72.3568,
          ymax: 55.0558,
          /* 
           * Web Mercator (102113), or WGS 84 (4326) - these are the 
           * only two that support continuous pan across the date line 
           */  
          spatialReference: { wkid: 4326 }
      };
      var startExtent;
      var basemap;
      var browserSupport = false;
      var attempts = 0;

      function initApp() {
        var startExtent = new esri.geometry.Extent(initialExtent);

        map = new esri.Map('map', { 
            extent: startExtent, 
            wrapAround180: true
          });

        basemap = new esri.layers.ArcGISTiledMapServiceLayer(
                'http://server.arcgisonline.com/ArcGIS/rest/services/' + 
                'ESRI_StreetMap_World_2D/MapServer');
        map.addLayer(basemap);

        /* Add Geolocation */
        dojo.connect(map, 'onLoad', function() { 
          getLocation();
        });
      }

      /* 
       * If the W3C Geolocation object is available then get the current 
       * location, otherwise report the problem 
       */
      function getLocation() {
        /* Check if the browser supports the W3C Geolocation API */
        if (navigator.geolocation) {
          browserSupport = true;
          navigator.geolocation.getCurrentPosition(plotLocation, 
              reportProblem, { timeout: 45000 });
        } else
          reportProblem();
      }

      /* Plot the location on the map and zoom to it */
      function plotLocation(position) {
        attempts = 0;

        var pointsLayer = new esri.layers.GraphicsLayer();

        map.addLayer(pointsLayer);

        var point = new esri.geometry.Point(position.coords.longitude, 
            position.coords.latitude, new esri.SpatialReference({ 
              wkid: 4326 
            }));
        pointsLayer.add(
          new esri.Graphic(
            point,
            new esri.symbol.SimpleMarkerSymbol().setColor(
                new dojo.Color([255, 0, 0, 0.5]))
          )
        );
        map.centerAndZoom(point, 13);
      }

      /* Report any errors using this function */
      function reportProblem(e) {
        /* Is this a support issue or an API issue? */
        if (browserSupport) {
          switch (e.code) {
            case e.PERMISSION_DENIED:
              alert('You have denied access to your position. You will ' +
                  'not get the most out of the application now.');
              break;
            case e.POSITION_UNAVAILABLE:
              alert('There was a problem getting your position.');
              break;
            case e.TIMEOUT:
              /* Three changes to get the location before a true timeout */
              if (++attempts < 3) {
                navigator.geolocation.getCurrentPosition(plotLocation, 
                    reportProblem);
              } else
                alert('The application has timed out attempting to get ' +
                    'your location.');
                break;
            default:
              alert('There was a horrible Geolocation error that has ' +
                  'not been defined.');
          }
        } else
          alert('Geolocation is not supported by your browser.');
      }

      dojo.addOnLoad(initApp);
    </script>
  </head>
  <body class="claro">
    <div id="map"></div>
  </body>
</html>

In this example, the call to the getLocation() function is inside an anonymous function that will be called on an onLoad event from the Map. Next, we define our two callback functions: plotLocation() and reportProblem(). The reportProblem() function is exactly like its counterpart in Example 4-2, so there is no need to go into it again. plotLocation(), however, is much changed as different APIs handle adding points differently.

The plotLocation() function first creates an esri.layers.GraphicsLayer called pointsLayer, which is where the new point will be placed, and adds this layer to the map. It then creates a new esri.geometry.Point, point, with the coordinates passed from the Position object. Next, it adds a new graphic on the pointsLayer layer, at point, with an esri.symbol.SimpleMarkerSymbol. Finally, the map is centered and zoomed to the current geolocation.

Support for Other Browsers

The code in Example 4-5 will provide geolocation support for browsers that implement the W3C Geolocation API. Once again, we need to rewrite our code to utilize the geo-location-javascript library to give us cross-browser support for our geolocation application. Example 4-6 shows an implementation of a cross-browser geolocation application using geo-location-javascript within the Esri ArcGIS JavaScript API. I have again highlighted the changes in the code in bold so they are easier to see.

Example 4-6. Adding geolocation for other browsers to an Esri ArcGIS Map
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=7"/>
    <meta http-equiv="viewport" 
        content="initial-scale=1, maximum-scale=1, user-scalable=no"/>

    <title>Adding Geolocation for Other Browsers to an Esri Map</title>

    <link rel="stylesheet" href="http://serverapi.arcgisonline.com/jsapi/arcgis/ \
        2.2/js/dojo/dijit/themes/claro/claro.css"/>
    <style type="text/css">
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
        width: 100%;
      }

      #map {
        height: 100%;
        width: 100%;
      }    
    </style>

    <script type="text/javascript">
      var djConfig = { parseOnLoad: true };
    </script>
    <script type="text/javascript" 
        src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.2">
    </script>
    <script type="text/javascript" src="gears_init.js"></script>
    <script type="text/javascript" src="geo.js"></script>
    <script type="text/javascript">
      dojo.require('esri.map');

      var map;
      var initialExtent = {
          xmin: -119.3324,
          ymin: 26.3156,
          xmax: -72.3568,
          ymax: 55.0558,
          /* 
           * Web Mercator (102113), or WGS 84 (4326) - these are the 
           * only two that support continuous pan across the date line 
           */  
          spatialReference: { wkid: 4326 }
      };
      var startExtent;
      var basemap;
      var browserSupport = false;

      function initApp() {
        var startExtent = new esri.geometry.Extent(initialExtent);

        map = new esri.Map('map', { 
            extent: startExtent, 
            wrapAround180: true
          });

        basemap = new esri.layers.ArcGISTiledMapServiceLayer(
                'http://server.arcgisonline.com/ArcGIS/rest/services/' + 
                'ESRI_StreetMap_World_2D/MapServer');
        map.addLayer(basemap);

        /* Add Geolocation */
        dojo.connect(map, 'onLoad', function() { 
          getLocation();
        });
      }

      /* 
       * The browser will now use whatever geolocation API is available to
       * it; hopefully it will be the W3C Geolocation object that is used to 
       * get the current location. If there is no geolocation support at all,
       * then report the problem. 
       */
      function getLocation() {
        /* Check if the browser supports any geolocation API */
        if (geo_position_js.init()) {
          browserSupport = true;
          geo_position_js.getCurrentPosition(plotLocation, 
              reportProblem);
        } else
          reportProblem();
      }


      /* Plot the location on the map and zoom to it */
      function plotLocation(position) {
        attempts = 0;

        var pointsLayer = new esri.layers.GraphicsLayer();

        map.addLayer(pointsLayer);

        var point = new esri.geometry.Point(position.coords.longitude, 
            position.coords.latitude, new esri.SpatialReference({ 
              wkid: 4326 
            }));
        pointsLayer.add(
          new esri.Graphic(
            point,
            new esri.symbol.SimpleMarkerSymbol().setColor(
                new dojo.Color([255, 0, 0, 0.5]))
          )
        );
        map.centerAndZoom(point, 13);
      }

      /* Report any errors using this function */
      function reportProblem() {
        /* Is this a support issue or an API issue? */
        if (browserSupport)
          alert('Could not locate your device.');
        else
          alert('Geolocation is not supported by your browser.');
      }

      dojo.addOnLoad(initApp);
    </script>
  </head>
  <body class="claro">
    <div id="map"></div>
  </body>
</html>

Calls to gears_init.js and geo.js load the libraries we are using for geolocation in this example. The Map functionality itself remains the same as in Example 4-5.

Instead of checking for navigator.geolocation, the geo-location-javascript init() function is called, which returns whether or not the browser supports any geolocation API. A simpler call to getCurrentPosition() is made, without the timeout set, but otherwise the getLocation() function is very similar to this same function in Example 4-5.

Nothing changed between the two Esri geolocation examples in the plotLocation() function, but there are, obviously, big changes in the reportProblem() function because of the lack of a PositionError object with geo-location-javascript. This is what we saw back in Example 4-3.

For more complicated cross-browser geolocation needs, additional and more complex coding will be required to get the job done. Hopefully the geo-location-javascript library will eventually add more functionality to its code base so that it better mirrors the W3C Geolocation API methods and properties. Until that day, it is up to application developers to write this functionality themselves. It is either that, or everyone needs to stop using outdated legacy browsers and phones—but unfortunately I do not see that happening for a few years still.



[10] Mapping Success: Google Maps Case Studies. http://maps.google.com/help/maps/casestudies/.

Get HTML5 Geolocation now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.