Book Image

Instant HTML5 Geolocation How-to

By : Benjamin Otto Werdmulle
Book Image

Instant HTML5 Geolocation How-to

By: Benjamin Otto Werdmulle

Overview of this book

We don't just surf the Web from our desktops any more – we take it with us, everywhere we go. Modern devices contain sophisticated hardware and software to determine the user's location. Apps such as Foursquare and Google Maps use this to create new kinds of functionality. Now, you can do this too with the HTML5 Geolocation API. "Instant HTML5 Geolocation How-to" is a simple guide to adding location information to your web applications. The practical, easy-to-follow recipes are designed to help you learn the ins and outs of the API. You'll learn how to use it, how it works, and how to save and display geographic information on the web. Beginning with a solid grounding in how the Geolocation API works and when to use it, you will learn how to determine, store, display, and track the user's location via a series of clear recipes. You will learn the different ways location is determined on different devices, including desktops and laptops that don't have GPS units. You'll also learn how to selectively use these different behaviours, based on the speed, accuracy, and battery life requirements of your application. You'll also get some hints about using MySQL databases to store sets of location data. "Instant HTML5 Geolocation How-to" will teach you everything you need to know about retrieving the location information your application needs, across multiple devices and platforms.
Table of Contents (7 chapters)

Tracking and updating the user's location (Intermediate)


While recording an individual location when the page loads is useful, in some situations you may wish to record the user's location continuously. The Geolocation API provides two functions for handling this use case—watchPosition and clearWatch—and in this section, we'll use them to modify our application to automatically resave the user's position when he/she moves.

Getting ready

This section doesn't require any PHP programming; the modifications are all on the front end, using JavaScript. However, ensure that you have a compatible device with a GPS receiver and mobile data capability available to test your code. Most modern smartphones should suffice; we tested with the Chrome browser on a Samsung Galaxy S2, and in Safari on an iPhone 5.

How to do it...

Perform the following steps to track and update the user's location:

  1. Take a copy of index.php, and save it as live.php. We're going to modify it to use watchPosition and save multiple map points as follows:

    <?php 
    // Load our common library file, and fail if it isn't present
    require_once('lib.php');
    
    ?>
    <!doctype html>
    <html>
    <head>
    <title>
    Continuous location detector
    </title>
    
    <script type="text/javascript" src="//code.jquery.com/jquery-1.9.1.min.js"></script>
      <script type="text/javascript" 
      src="//maps.googleapis.com/maps/api/
      js?v=3.exp&sensor=true"></script>
    
    <script language="javascript">

    This function is called when the Geolocation API successfully retrieves the user's location. Note that we are now saving our map points to an array:

    function savePosition(point) {
    
      // Save the current latitude and longitude as properties
      // on the window object
      window.latitude = point.coords.latitude;
      window.longitude = point.coords.longitude;
    
      // Send the retrieved coordinates to 
      callback.php via a POST
      // request, and then set the page 
      content to "Location saved"
      // once this process is complete 
      (or "We couldn't save your
      // location" if it failed for some reason)
      $.ajax({
        url: 'callback.php',
        type: 'POST',
        data:   {
          latitude: window.latitude,
          longitude: window.longitude
        },
        statusCode: {
          500: function() {
            $('#location_pane').html
            ('<p>We couldn\'t save your location.</p>');
          }
        }
      }).done(function() {
      // Let the user know the location's 
      been saved to the database
      $('#location_pane').html('<p>Location saved.</p>');
      // Center the map on the user's current location
      var currentLocation = new 
      google.maps.LatLng(window.latitude, window.longitude);
      window.googleMap.setCenter(currentLocation);
      // Create a marker at the user's current 
      location and save it
      // to our array of map points
      window.geopath.push(new google.maps.LatLng(window.latitude, window.longitude));
    }).fail(function() {
    $('#location_pane').html('<p>We couldn\'t save your location.</p>');
    });
    }

    This function is called when there is a problem retrieving the user's location (but the Geolocation API is supported in his/her browser):

    function errorPosition(error) {
      switch(error.code) {
        // Error code 1: permission to access the user's 
        location
        // was denied
        case 1: $('#location_pane').html('<p>No location was 
        retrieved.</p>');
        break;
        // Error code 2: the user's location could not be 
        determined
        case 2: $('#location_pane').html('<p>We couldn\'t find 
        you.</p>');
        break;
        // Error code 3: the Geolocation API timed out
        case 3: $('#location_pane').html('<p>We took too long 
        trying to find your location.</p>');
        break;
      }
    }
  2. And finally, add a function to prevent automatic updating of user location:

    function stopWatching() {
      if (navigator.geolocation) {
        navigator.geolocation.clearWatch
        (window.watchLocationID);
        $('#watchingButton').hide();
      }
    }

    Following this, we move onto the body of the page, incorporating a new "stop watching" button:

    </script>
    </head>
      <body>
        <div id="location_pane">
        <p>
          Waiting for location ...
        </p>
        </div>
        <p>
          <button onclick="stopWatching()" 
          id="watchingButton">Stop watching position</button>
        </p>
          <div id="map_pane" style="width: 500px; 
          height: 500px"></div>
          <!-- We're including the Geolocation API code 
          at the bottom of the page
          so that page content will have loaded first -->
          <script language="javascript">
          // Set initial viewing options for the map
          var mapOptions = {
            zoom: 15,
            mapTypeId: google.maps.MapTypeId.HYBRID
          };
    
          // Initialize the map as a googleMap property 
          on the window object
          window.googleMap = new 
          google.maps.Map(document.getElementById('map_pane'), 
          mapOptions);
          // Load any previous points into a JSON array, 
          which itself is written
          // to the page using PHP. We're hardcoding 
          the user ID to 1, as in
          // callback.php.
          var jsonPoints = 
          <?=json_encode(getPreviousLocations(1));?>;
          window.polyLine = new google.maps.Polyline({
          strokeColor: '#ff0000',
          strokeOpacity: 1.0,
          strokeWeight: 3
        });
          window.polyLine.setMap(window.googleMap);
          window.geopath = window.polyLine.getPath();
    
          // If jsonPoints isn't empty, iterate through and 
          create new map points
          // for each geolocation point
          if (jsonPoints.length > 0) {
            jsonPoints.forEach(function(point) {
              window.geopath.push(new google.maps.LatLng
              (point.latitude, point.longitude))
            });
          }
          // First, check if geolocation support is available
          if (navigator.geolocation) {
            // If Geolocation API support is available:
            // Attempt to get the current position, and 
            // watch the user's location; 
            instantiate the savePosition 
            // function if the location was saved, 
            or errorPosition if 
            // it was not. Note that we don't ever want 
            low-accuracy
            // location measurements in this context.
            window.watchLocationID = 
            navigator.geolocation.watchPosition(savePosition, 
            errorPosition, {enableHighAccuracy: true});
          } else {
            // If the browser doesn't support the 
            Geolocation API, tell the user.
            $('#location_pane').html
            ('<p>No geolocation support is available.</p>');
          }
        </script>
      </body>
    </html>

How it works...

The Geolocation API watchPosition method uses a very similar syntax to the getCurrentPosition method we used earlier. Its parameters are the same:

  • A callback function to call on success

  • A callback function to call on failure

  • An array of options

The available options, in turn, are also the same:

  • enableHighAccuracy: This is a Boolean value, It enables high accuracy mode (default: off).

  • timeout: This is a long value. It is the threshold beyond which the API times out (in milliseconds; the default is no limit).

  • maximumAge: This is a long value. It is the maximum age of a cached location that we'll accept, in milliseconds (default: 0).

However, rather than initiating a single check for the user's location, it instead establishes a "watch" that will load the callback functions as appropriate whenever the device has detected that the user has moved. Because you need to reference the watch process, watchPosition returns an identifier.

The identifier is obtained when the method is called, as follows:

window.watchLocationID = navigator.geolocation.watchPosition(savePosition, errorPosition, {enableHighAccuracy: true});

Note that we've used the enableHighAccuracy option, while not failing back to a lower-accuracy location method. That's because in a situation where you're continually retrieving the location, low-quality data will not be useful. Because the device falls back to estimating location through environmental factors, visibly erroneous data may be included in your dataset, sometimes hundreds of meters or more away from the user's actual location, making it harder to track the user's path. It's better to fail if a GPS signal cannot be found.

Sometimes, the user may wish to switch tracking off, while remaining on the page. To do this, we've created a simple button. This will trigger the clearWatch method, which takes the watch process ID as a single parameter as follows:

navigator.geolocation.clearWatch(window.watchLocationID);

In our implementation, we've also hidden the button using the jQuery hide() method once it has been pressed.

Finally, our savePosition function, which is called whenever a new location is successfully received, must display the new location.

In our previous implementation, we simply created a variable for the new Marker object to display a single point. However, we also created a window.points array to keep the Marker objects that were reloaded from the database. In order to display the full set of points that are displayed as the user moves around, we'll need to add each newly created Marker in the array too.

To do this, we can just push new Marker objects to the array as follows:

window.points.push(new google.maps.Marker({
  position: currentLocation,
  map: window.googleMap,
  title: 'Detected location'
}));

However, a series of individual points isn't necessarily the best way to display this data.

There's more...

Unfortunately, at the time of writing this book, there's no way to prevent the mobile device from switching its screen and GPS unit off. Consider the following screenshot from a walk I took around the Inner Sunset neighborhood in San Francisco:

Contrary to what the map might indicate, this was a continuous walk—I didn't suddenly leap two blocks in a single bound! However, while I was holding my device and ensuring that its screen didn't turn black on some of the streets (indicated by the vertical lines you can see here), on the others I chose to slip my device into my pocket, as an ordinary user might. The GPS receiver was deactivated automatically when the screen went black, in order to conserve battery life. As a result, no location points were saved for these portions of my journey.

We can further enhance our application by replacing the individual points on the map with a continuous line. This would also help smooth over any gaps in the dataset.

To achieve this, we need to replace the portions of the code that set points on the map using the Google Maps API. Google provides another element, PolyLines, which allows you to arbitrarily add coordinates to a continuous line. I've included the complete code as livepath.php.

In the portion of the page where we're setting up the map, just after we've loaded the jsonPoints JSON array, we need to create a polyline. We'll make it bright red (HTML color #ff0000) so that we can see it easily:

window.polyLine = new google.maps.Polyline({
  strokeColor: '#ff0000',
  strokeOpacity: 1.0,
  strokeWeight: 3
});

Now, we'll attach it to our existing map. window.polyLine will be the global variable that stores the polyline:

window.polyLine.setMap(window.googleMap);

And finally, we'll establish window.geopath as the array of points that make up the polyline. We're saving that as a global variable too, for convenience. You'll see why momentarily.

window.geopath = window.polyLine.getPath();

Now, let's load our JSON array of previously saved points onto it:

// If jsonPoints isn't empty, iterate through and create new map 
// points for each geolocation point
if (jsonPoints.length > 0) {
  jsonPoints.forEach(function(point) {
    window.geopath.push(new google.maps.LatLng
    (
      point.latitude, point.longitude
    ))
  });
}

Finally, recall the AJAX done() function at the top of the page, which is called once a new location has been successfully processed. We can replace the Google Maps code in this function with a simple line to add the latest coordinates to our PolyLine:

// Create a marker at the user's current location and save it
// to our array of map points
window.geopath.push(new google.maps.LatLng(window.latitude, window.longitude));

Now, whenever a new location is detected, it will be added to the line rather than to the map as a new, individual point.

The Geolocation API is an important addition to any modern web developer's arsenal. Together with other new APIs, HTML 5 itself and the new breed of mobile devices, the web doesn't just allow you to build high quality network applications; it allows you to build new kinds of context-aware applications that have never been created before. Happy building.