Video icon 64
Learn to Code. Start your free trial today.
Advertisement

HTML5 Apps: Positioning with Geolocation

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

At the heart of every location-based application is positioning and geolocation. In this tutorial you will learn the geolocation capabilities of HTML5 and the basic principles needed to take advantage of them in your next HTML5 app!

Introduction

Three years ago, the smartphone “boom” was still an uncertain possibility. Today, we are surrounded by a society of smart phone addicts who live as if their lives cannot continue without instant, mobile access to the web, e-mail, and apps for every occasion. Mobile devices have come a long way in just three short years, and now support a wide range of useful features beyond making phone calls. For location-challenged individuals like myself, geolocation is one of the most useful additions. Many iPhone and Android devices have been fitted with GPS hardware that can produce reliable geographical data, and in this article we will discuss the geolocation capabilities of HTML5. My aim is to help you take advantage of geolocation technology in your next project by walking you through the following topics:

  • Browser geolocation and how it works
  • How Asynchronous function calls work
  • How to use the getCurrentPosition API
  • What's in geolocation data
  • How to fall back on IP address based geolocation when browsers don't provide native support
  • How to display a map using geolocation data
  • How to use the watchPosition and clearWatch APIs

Browser geolocation and how it works

To detect the location of a client device in the past, one would typically have to inspect the client IP address and make a reasonable guess as to the where that device was located. However, as part of the HTML5 efforts, the W3C has developed a set of APIs to effectively allow the client-side device (i.e. your iPhone 3G+, Android 2.0+ phones, or even your conventinal desktop browsers) to retrieve geographic positioning information with JavaScript. When we look at the web browsers landscape, the native support for geolocation is beginning to find its way into mainstream applications at a slow and steady pace. One substantial application that uses geolocation is Google Maps. As I'm writting this article, the geolocation support is fully available in only a few browsers (see below), but it looks like most modern browsers are adopting this useful functionality:

  • Firefox 3.5+: Yes
  • Chrome 5 beta: Yes
  • iPhone Safari: Yes
  • Opera 10.5 dev build: Yes
  • Safari: No
  • Internet Explorer: No

For those that are interested in reading on W3C geolocation specs here is the link to follow. One section worth talking about here is section 6.2.8 titled: "The geolocation API must be agnostic to the underlying sources of location information". It's suggesting that the underlying technology on the device may achieve geolocation via either GPS, cell tower triangulation or possibly other techniques. The geolocation API won't identify how location information was determined. Not knowing how geolocation was achieved is usually not a problem, but you may notice that the accuracy of location information may vary greatly depending on the technique used.

Asynchronous functions

An Asynchronous function is one that is executed outside of the main program flow. In the context of JavaScript, it means our code will call a function and then continue to execute statements before the function returns. When following this model, our code should look something like this:

// the function we want to call
do_something(handle_response_to_do_something);

// the function that will handle the response of the above function call
function handle_response_to_do_something(response)
{
    alert(response);
}

Since the response to do_something() function is given to us asynchronously, we must nominate a function that will receive a response and use it. Therefore we must define another function, handle_response_to_do_something(), that displays the result in a dialog.

You may now be asking why we need to know this? Well, because when our application tries to query its location, the user of the device must agree to share his location information with our application. If this action was not an asynchronous one, then everything inside the browser would freeze until the user responds, and that is obviously less than dersirable.

html5 geolocation API

getCurrentPosition API

Let's talk about the geolocation API. The first API that we are interested in working with is the getCurrentPosition() API. When called, it must immediately return and then asynchronously acquire a new Position object. If successful, this method must invoke its associated successCallback argument with a Position object as an argument. If the attempt fails, and the method was invoked with a non-null errorCallback argument, this method must invoke the errorCallback with a PositionError object as an argument.

So let's start by creating our HTML5 page which is nice and easier than setting up an XHTML 1.0 page. All we need to start with is this:

<!DOCTYPE html>
<html>
  <body>
    <div>

    </div>
  </body>
</html>

On line 1, we use the new <!DOCTYPE html> DTD to define this as an HTML5 page. Next, we will place a button on our page for users to initiate the process of detecting their location.

<!DOCTYPE html>
<html>
  <body>
    <div>
      <button id=”btnInit” >Find my location</button>
    </div>
  </body>
</html>

Now we will wire the button up with JavaScript to initiate the process of querying the current location. Note how the script tags do not require type attribute thanks to some new HTML5 sugar.

<!DOCTYPE html>
<html>
  <head>
    <script src="js/jquery-1.4.2.min.js"></script>
    <script>
        jQuery(window).ready(function(){
            jQuery("#btnInit").click(initiate_geolocation);
        });

        function initiate_geolocation() {
            navigator.geolocation.getCurrentPosition(handle_geolocation_query);
        }

        function handle_geolocation_query(position){
            alert('Lat: ' + position.coords.latitude + ' ' +
                  'Lon: ' + position.coords.longitude);
        }
    </script>
  </head>
  <body>
    <div>
      <button id="btnInit" >Find my location</button>
    </div>

  </body>
</html>

In the above code we should note the use of the jQuery JavaScript framework to help us get started quickly, otherwise we'd have to write extra code to detect page readiness that has nothing to do with geolocation. With the first script tag, we load jQuery framework and then use it immediately at the start of the second script tag to assign a click event to the button we defined earlier. We also wrote the initiate_geolocation() function to execute when the button is pressed and the handle_geolocation_query(position) function to asynchronously get the geolocation data through the position argument.

At this point if you run the above code in a browser that supports geolocation (namely Firefox 3.5+ and iPhone Safari) you should see your Latitude and Longitude coordinates displayed to you. This works if the user agrees to share his location information with the website/web application, but what if he doesn't what to share that information? or what if there is a problem getting that information?

It would be nice to be notified of such problems. Fortunately, our code can detect these issues by defining another function that is executed only when there is a problem retrieving geolocation data. So, let's look at how our code will need to change in order to detect problems:

<!DOCTYPE html>
<html>
  <head>

    <script src="js/jquery-1.4.2.min.js"></script>
    <script>
        jQuery(window).ready(function(){
            jQuery("#btnInit").click(initiate_geolocation);
        });

        function initiate_geolocation() {
            navigator.geolocation.getCurrentPosition(handle_geolocation_query,handle_errors);
        }

        function handle_errors(error)
        {
            switch(error.code)
            {
                case error.PERMISSION_DENIED: alert("user did not share geolocation data");
                break;

                case error.POSITION_UNAVAILABLE: alert("could not detect current position");
                break;

                case error.TIMEOUT: alert("retrieving position timed out");
                break;

                default: alert("unknown error");
                break;
            }
        }

        function handle_geolocation_query(position){
            alert('Lat: ' + position.coords.latitude +
                  ' Lon: ' + position.coords.longitude);
        }
    </script>
  </head>
  <body>
    <div>
      <button id="btnInit" >Find my location</button>
    </div>
  </body>
</html>

In the above code we have now introduced a new function, handle_errors that takes in an error argument. By inspecting the error argument, we can check for the kind of error that has occurred. Normally, the browser will identify one of these three errors as the cause:

  • User chose to not share location data
  • Device was unable to obtain position data
  • Device timedout while waiting to retrieve position data

Geolocation data

If we inspect what we received from the geolocation API, we'll see the coords and timestamp fields. The timestamp field simply denotes the time at which the instance of geolocation data was created. The coords attribute contains a set of geographic coordinates together with their associated accuracy, as well as a set of other optional attributes such as altitude and speed. The following is the description of them according to the W3C specs:

  • The latitude and longitude attributes are geographic coordinates specified in decimal degrees.
  • The accuracy attribute denotes the accuracy level of the latitude and longitude coordinates. It is specified in meters and must be supported by all implementations.
  • The altitude attribute denotes the height of the position, specified in meters above the WGS84 ellipsoid. If the implementation cannot provide altitude information, the value of this attribute must be null.
  • The altitudeAccuracy attribute is specified in meters. If the implementation cannot provide altitude information, the value of this attribute must be null.
  • The heading attribute denotes the direction of travel of the hosting device and is specified in degrees counting clockwise relative to the true north. If the implementation cannot provide heading information, the value of this attribute must be null.
  • The speed attribute denotes the current ground speed of the hosting device and is specified in meters per second. If the implementation cannot provide speed information, the value of this attribute must be null.

You may only access some of these fields if the target device actually supports them according to the above description, but it's still important to know they are there. The accuracy attribute in particular may be useful in your application to detect reliability.

IP address based Geolocation

So now the obvious question is what about those browsers that don't support geolocation at all? The answer is that we must use an external geolocation service. These services do their best to map the IP address of a device to geographic locations using large geolocation databases. Usually they do a good job, but at times they may suffer from the following issues:

  • IP addresses may be associated with the wrong location (e.g., the wrong postal code, city or suburb within a metropolitan area).
  • Addresses may be associated only with a very broad geographic area (e.g., a large city, or a state). Many addresses are associated only with a city, not with a street address or latitude/longitude location.
  • Some addresses will not appear in the database and therefore cannot be mapped (often true for IP numbers not commonly used on the Internet).

The important thing to remember is that when an external geolocation service is used, the accuracy is not as good as geolocation native to the device, and in some scenarios it may be completely off. Additionally, such services do not provide any information regarding the altitude, speed or heading of the device. Regardless, when GPS or triangulation are not available, they are a good fallback.

YQL Geo Library is a dedicated and lightweight library that lets you perform geolocation using good old Yahoo services. This library can do other things which I'll leave to you to discover, but for now let's look at how we should modify our code to successfully detect the location on both supporting and non-supporting browsers:

<!DOCTYPE html>
<html>

  <head>
  <script src="js/jquery-1.4.2.min.js"></script>
  <script src="js/yqlgeo.js"></script>
  <script>
    jQuery(window).ready(function(){
        jQuery("#btnInit").click(initiate_geolocation);
    })

    function initiate_geolocation() {
        if (navigator.geolocation)
        {
            navigator.geolocation.getCurrentPosition(handle_geolocation_query, handle_errors);
        }
        else
        {
            yqlgeo.get('visitor', normalize_yql_response);
        }
    }

    function handle_errors(error)
    {
        switch(error.code)
        {
            case error.PERMISSION_DENIED: alert("user did not share geolocation data");
            break;

            case error.POSITION_UNAVAILABLE: alert("could not detect current position");
            break;

            case error.TIMEOUT: alert("retrieving position timedout");
            break;

            default: alert("unknown error");
            break;
        }
    }

    function normalize_yql_response(response)
    {
        if (response.error)
        {
            var error = { code : 0 };
            handle_error(error);
            return;
        }

        var position = {
            coords :
            {
                latitude: response.place.centroid.latitude,
                longitude: response.place.centroid.longitude
            },
            address :
            {
                city: response.place.locality2.content,
                region: response.place.admin1.content,
                country: response.place.country.content
            }
        };

        handle_geolocation_query(position);
    }

    function handle_geolocation_query(position){
        alert('Lat: ' + position.coords.latitude + ' ' +
              'Lon: ' + position.coords.longitude);
        }
    </script>

  </head>
  <body>
    <div>
      <button id="btnInit" >Find my location</button>
    </div>

  </body>
</html>

So what's new in the above code? Three things:

  1. We are importing the YQL Geolocation Library into the page
  2. Inside the initiate_geolocation() function, the code checks to make sure geolocation is natively supported. If not it makes use of the YQL Geolocation Library.
  3. Finally we've defined the normalize_yql_response(response) function to turn the result of the YQL Geolocation Library into an output format similar to the W3C geolocation API specs.

Display a map using Geolocation

So far we have been able to retrieve the latitude and longitude coordinates of the device but when one is lost in the middle of a big city, latitude and longitude coordinates alone aren't going to be all that helpful. We must convert these coordinates into some form that relates to users more naturally, like a map. Let's display a map of the current location, using Google's Static Map API as shown in the following code section:

function handle_geolocation_query(position)
{
    var image_url = "http://maps.google.com/maps/api/staticmap?sensor=false&center=" + position.coords.latitude + "," +
                    position.coords.longitude + "&zoom=14&size=300x400&markers=color:blue|label:S|" +
                    position.coords.latitude + ',' + position.coords.longitude;

    jQuery("#map").remove();
    jQuery(document.body).append(
        jQuery(document.createElement("img")).attr("src", image_url).attr('id','map')
    );
}

With the help of jQuery, we modified the handle_geolocation_query(position) function to create an image element and add it to the page. The src attribute of the image element is set to a constructed URI which Google Static Map API will inspect and generate a map image for.

HTML5 Geolocation Maps API

watchPosition and clearWatch APIs

Now that we know how getCurrentPosition API works lets quickly look at the watchPosition API. When called, it must immediately return and then asynchronously start a watch process defined by the following set of steps:

  1. Acquire a new Position object. If successful, invoke the associated successCallback with a Position object as an argument. If the attempt fails, and the method was invoked with a non-null errorCallback argument, this method must invoke the errorCallback with a PositionError object as an argument.
  2. Invoke the appropriate callback with a new Position object every time the implementation determines that the position of the hosting device has changed.

This method returns an integer value that uniquely identifies the watch process. When the clearWatch() function is called with this identifier, the watch process must stop acquiring any new position fixes and must cease invoking any callbacks.

So in other words this API enables our code to repeatedly receive location information as the device location changes. Let's look at the scaffolding page we need to start with:

<!DOCTYPE html>
<html>
  <head>
    <script src="js/jquery-1.4.2.min.js"></script>
    <script>

        jQuery(window).ready(function(){
            jQuery("#btnInit").click(initiate_watchlocation);
            jQuery("#btnStop").click(stop_watchlocation);
        });

        var watchProcess = null;

        function initiate_watchlocation() {
        }

        function stop_watchlocation() {
        }

        function handle_errors(error)
        {
            switch(error.code)
            {
                case error.PERMISSION_DENIED: alert("user did not share geolocation data");
                break;

                case error.POSITION_UNAVAILABLE: alert("could not detect current position");
                break;

                case error.TIMEOUT: alert("retrieving position timedout");
                break;

                default: alert("unknown error");
                break;
            }
        }

        function handle_geolocation_query(position) {
        }
    </script>
  </head>
  <body>
    <div>
      <button id="btnInit" >Monitor my location</button>

      <button id="btnStop" >Stop monitoring</button>
    </div>
    <div id=”info”></div>
  </body>
</html>

As you can see the code is very much the same as the code we used in the "getCurrentPostion API" section earlier. There are a couple of additions however.

  1. First we have added a new button to allow us to stop the watch process with the stop_watchlocation() function to handle its click.
  2. We have a watchProcess variable to keep track of the watch process.

To start the process we need to add the code that calls the watchPosition() API like the code below:

function initiate_watchlocation() {
    if (watchProcess == null) {
        watchProcess = navigator.geolocation.watchPosition(handle_geolocation_query, handle_errors);
    }
}

To stop the process we need to add the code that calls the clearWatch() API like the code below:

function stop_watchlocation() {
    if (watchProcess != null)
    {
        navigator.geolocation.clearWatch(watchProcess);
        watchProcess = null;
    }
}

Finally, we handle displaying the map and additional data (e.g. position, accuracy) like this:

function handle_geolocation_query(position) {
    var text = "Latitude: "  + position.coords.latitude  + "<br/>" +
               "Longitude: " + position.coords.longitude + "<br/>" +
               "Accuracy: "  + position.coords.accuracy  + "m<br/>" +
               "Time: " + new Date(position.timestamp);
    jQuery("#info").html(text);

    var image_url = "http://maps.google.com/maps/api/staticmap?sensor=false&center=" + position.coords.latitude + ',' + position.coords.longitude +
                    "&zoom=14&size=300x400&markers=color:blue|label:S|" + position.coords.latitude + ',' + position.coords.longitude;

    jQuery("#map").remove();
    jQuery(document.body).append(
        jQuery(document.createElement("img")).attr("src", image_url).attr('id','map')
    );
}
HTML5 Geolocation no data

Conclusion

I hope that this tutorial has given you a head start into the world of geolocation APIs. Good luck and try to make your next project take advantage of geolocation benefits!

Advertisement