HTML5 Apps: Positioning with Geolocation

HTML5 Apps: Positioning with Geolocation

Tutorial Details
  • Technology: HTML5
  • Difficulty: Beginner
  • Estimated completion time: 45 - 60 minutes

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!

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://sticksallison.com Christian Nally

    Thanks for the great tutorial.

    I did run into one issue where I was only getting the locations of cell towers. ( Android Gecko )

    Then I spotted something about enableHighAccuracy , and read through here:
    http://dev.w3.org/geo/api/spec-source.html#high-accuracy

    The comments there suggests to me that for some platforms, users may find that the results returned from getCurrentPosition might not be as accurate as you’d like without the enableHighAccuracy parameter.

    Here was my final relevant line:

    navigator.geolocation.getCurrentPosition(showPosition,errorPosition,{ enableHighAccuracy:true });

    YMMV

    I’m not sure if you’re updating this post, but this might be helpful for some.

  • Prahlad

    Thanks …………

  • rafik

    your tutorial just made my day!!

    i also wanna work on how to search for a place..and get the distance between my current location and this place..any ideas ?

    • Nathan

      For distance I’ve used basic Cartesian distance calcs. I use a SQL function and can call it from a Proc. You can easily convert this to whatever language.

      Call the Function:

      SELECT x.SomeData, dbo.DistanceBetween(x.Latitude, x.Longitude, Latitude1, Longitude1) AS Distance

      //DistanceBetween SQL Function:

      CREATE FUNCTION [dbo].[DistanceBetween] (@Lat1 as real,
      @Long1 as real, @Lat2 as real, @Long2 as real)
      RETURNS real
      AS
      BEGIN

      DECLARE @dLat1InRad as float(53);
      SET @dLat1InRad = @Lat1 * (PI()/180.0);
      DECLARE @dLong1InRad as float(53);
      SET @dLong1InRad = @Long1 * (PI()/180.0);
      DECLARE @dLat2InRad as float(53);
      SET @dLat2InRad = @Lat2 * (PI()/180.0);
      DECLARE @dLong2InRad as float(53);
      SET @dLong2InRad = @Long2 * (PI()/180.0);

      DECLARE @dLongitude as float(53);
      SET @dLongitude = @dLong2InRad – @dLong1InRad;
      DECLARE @dLatitude as float(53);
      SET @dLatitude = @dLat2InRad – @dLat1InRad;
      /* Intermediate result a. */
      DECLARE @a as float(53);
      SET @a = SQUARE (SIN (@dLatitude / 2.0)) + COS (@dLat1InRad)
      * COS (@dLat2InRad)
      * SQUARE(SIN (@dLongitude / 2.0));
      /* Intermediate result c (great circle distance in Radians). */
      DECLARE @c as real;
      SET @c = 2.0 * ATN2 (SQRT (@a), SQRT (1.0 – @a));
      DECLARE @kEarthRadius as real;
      /* SET kEarthRadius = 3956.0 miles */
      SET @kEarthRadius = 6376.5; /* kms */

      DECLARE @dDistance as real;
      SET @dDistance = @kEarthRadius * @c;
      return (@dDistance);
      END

  • Bob

    Hi,

    I was just wondering if anyone has this working with the default browser on Android 2.x. I’ve tried it on my Motorola and 2.2.2 and had a friend with an HTC Thunderbolt running 2.3.4 try it too. If you add the {enableHighAccuracy:true} setting, it turns on the gps but never returns and never gets a location. It works just fine in Opera on the same phones, so it has to be something in the browser. I have javascript turned on and have enabled sharing the location.

    Any ideas?

    Thanks!

  • http://welessthanthree.com Andy Gillette

    If we include the YQL library is there any reason to try HTML5 first? It looks like yql get ‘visitor’ already attempts HTML5 and then uses IP as fallback … Am I wrong?

  • Aaron

    Just fyi the first code snippet:

    function handle_geolocation_query(position){
    alert(‘Lat: ‘ + position.coords.latitude + ‘ ‘ +
    ‘Lon: ‘ + position.coords.latitude);
    }

    should be:

    function handle_geolocation_query(position){
    alert(‘Lat: ‘ + position.coords.latitude + ‘ ‘ +
    ‘Lon: ‘ + position.coords.longitude);
    }

  • http://andygup.net Andy

    Nice, complete beginner article. In regards to accuracy, here’s an blog post that talks about what works and what doesn’t: http://www.andygup.net/?p=600 YMMV across browsers, phones, cell providers.

  • Phil

    Is there a simple way to convert the position object’s longitude and latitude into a street/city/state?

  • http://JohnCraigFreeman.net John Craig Freeman

    I am trying to capture users current location and pass the longitude and latitude values off to a PHP document that will generated an augmented reality object at the user’s current location. I have the getCurrentPosition API working and my PHP document is successfully generating new instances of the virtual object. How would I pass the longitude and latitude values to the PHP?

    • MD

      I’m looking for some solution similar to this.

    • Rory

      You could use a jQuery AJAX method to send Javascript variables to your PHP script and read them from the POST variables.

      // in your javascript
      function handle_geolocation_query(position) {
      $.post(“myAR.php”, {
      latitude: position.coords.latitude,
      longitude: position.coords.longitude
      });
      }

      // myAR.php

    • Rory

      You could use a jQuery AJAX method to send Javascript variables to your PHP script and read them from the POST variables.

      // in your javascript
      function handle_geolocation_query(position) {
      $.post(“myAR.php”, {
      latitude: position.coords.latitude,
      longitude: position.coords.longitude
      });
      }

      // myAR.php
      $latitude = $_POST['latitude'];
      $longitude = $_POST['longitude'];

      // do AR stuff…

  • http://prosolutionpillsreviews.beep.com/ Neville Duran

    Thanks for the snippet code !

  • John

    Thanks for this. Very helpful.
    I put the code inside a form because I am populating an input field with the returned Lat/Lon. However, after clicking on the button, it does get the lat/lon but the form gets submitted right after. How do I prevent/stop the form from being submitted?

  • John

    John here… i found answer to previous question. Just need to make button type=button.

  • David

    Hi.

    Is there a way to show a map without using geolocation. In my case I have the coordinates for my maps (kml) and I need to not fail when the mobile has not gps supported.

    Any idea?

    Thanks

  • Joe

    Great tutorial!!

    Is there anyway you could have a dynamic map displayed rather than a static image? Is it harder to implement?

  • Pablo Farías Navarro

    Thanks for sharing this tutorial!

    For the ones who want to get into HTML5 app dev, I’ve created an online course on making HTML5 apps for iOS, Android and the web: http://www.udemy.com/iphone-and-android-html5-apps/

  • Olly

    Hey, is there anyway to display the city using the W3C Api ?

  • Blasto

    Uff…!! It’s a very good tutorial! Thanks!

    But… I have a problem. An understable problem…

    In this pages http://demos.denonstudio.net/geo/watch.html and …/index.html, both are exact with my position. The accuracy is arround 20-30 meteres on my pc and my smartphone.

    But, when I do this on my html, the accuracy is over 120.000 meters when I see on my pc with wifi connection, and when I look at my smartphone using hsdpa, the accuracy is over 1000 meters.

    I have the yqlgeo.js at /js, and the html at /. Now, the version that jQuery uses is 1.9.0. I’ve changed the url to this, but nothing.

    Why It’ could be?

    Thanks, and good job!

  • http://www.facebook.com/sandy2302 Sandesh Damkondwar

    Helped too much, Thanks.

  • http://twitter.com/boxingdavidh Currante…..

    Hi.

    Making some test from scratch with your code, if I do this:

    function initiate_geolocation() {

    alert(“initiate_geolocation()”);

    if (navigator.geolocation)

    {

    navigator.geolocation.getCurrentPosition(handle_geolocation_query, handle_errors);

    }

    else

    {

    alert(“!navigator.geolocation”);

    // yqlgeo.get(‘visitor’, normalize_yql_response);

    }

    }

    in safari (desktop version) 5.1.7 (7534.57.2) I get “could not detect current position” but what we trying to do is go to yqlgeo to get position, but never enters…

    Any idea?.

    Thanks