HTML5 Geolocation overview

Geolocation is the determination of where one is located in the world and (optionally) sharing that information with people that are trusted. There is more than one way to figure out where one is located — the IP address, the wireless network connection, which cell tower the phone (or PC ) is communicating with, or dedicated GPS hardware that calculates latitude and longitude from information sent by satellites in the sky.

IP Mapping was originally designed with the aim of displaying IP addresses upon Google Maps and experience has shown that although it works well it is very reliant upon the accuracy of the data held by the various database and communication suppliers. HTML5 geolocation offers an alternative mechanism. If enabled a check is first performed to determine whether a user's browser is capable of supporting HTML5 geolocation and if it is a request is then made to obtain the information. If successful this is then used to display the location upon a map. If unsuccessful, or if the browser does not support HTML5 geolocation, the fall back, existing IP mapping is used instead.

The HTML5 geolocation API enables the sharing of your location with trusted web sites. The latitude and longitude are available to JavaScript on the web page, which in turn can send it back to the remote web server and do fancy location-aware things such as finding local businesses or showing the location on a map.

Privacy is an obvious concern when one is involved with sharing your physical location with a remote web server. The geolocation API explicitly states: “User Agents must not send location information to Web sites without the express permission of the user.” In other words, sharing your location is always opt-in. If you don't want to, you don't have to.

The geolocation API lets one share your location with trusted web sites. The geolocation API is supported by most browsers on the desktop and mobile devices.

The geolocation API centres around a new property on the global navigator object: navigator.geolocation.

[Important]Important

The HTML5 geolocation is asynchronous which means that there may be a delay before an actual location is stored with in the database and the visitor displaying the page.

The simplest use of the geolocation API looks like this:

function get_location() {
  navigator.geolocation.getCurrentPosition(show_map);
}

That has no detection, no error handling, and no options. Your web application should probably include at least the first two of those.

What one does if geolocation support is not available is optional but usual results in a fall back mechanism. The following occurs when a call is made to getCurrentPosition(). As geolocation support is opt-in, it means that the browser will never force one to reveal the current physical location to a remote server. The user experience differs from browser to browser. In Mozilla Firefox, calling the getCurrentPosition() function of the geolocation API will cause the browser to pop up an “infobar” at the top of the browser window. The infobar looks like this:

Figure 8.2. Geolocation opt-in infobar

Geolocation opt-in infobar.

Selecting the menu item displays a few more options.

Figure 8.3. Firefox Geolocation opt-in infobar (2)

Firefox Geolocation opt-in infobar (2).

The Chrome geolocation request box is slightly different as shown below,and there is no specific 'Not Now' option. However with the UI design, the user is more likely to choose either Allow or Deny, which will then return an answer to the navigator.geolocation.getCurrentPosition function. There is the option however that the user can also click an "X" on the far right of the advisory notice. This is essentially the same as clicking "Not now" in Firefox. No result is returned to the geolocation function.

Figure 8.4. Chrome Geolocation opt-in infobar

Chrome Geolocation opt-in infobar.

The Opera opt in popup is displayed below.

Figure 8.5. Opera Geolocation opt-in infobar

Opera Geolocation opt-in infobar.

For completeness we show the Internet Explorer (IE 11) opt in bar as well below:

Figure 8.6. IE Geolocation opt-in infobar

IE Geolocation opt-in infobar.

Other browsers, such as Safari etc., will have different request boxes displayed.

Note that the file name or site address as shown in the above image will vary depending upon the site name. The inforbar informs the end user of the following information:

  1. that a website wants to know your location

  2. which website wants to know your location

  3. allows the user to click through to Mozilla’s “Location-Aware Browsing” help page which explains what the heck is going on (short story: Google provides the location and stores your data in accordance with its Location Service Privacy Policy)

  4. allows the user to choose to share their location

  5. allows the user to choose not to share their location

  6. permits the user to tell their browser to remember their choice (either way, share or don't share) so that they never see the infobar again on the specific website

Furthermore, this infobar is

  1. non-modal, so it won't prevent the user from switching to another browser window or tab

  2. tab-specific, so it will disappear if they switch to another browser window or tab and will reappear when they switch back to the original tab

  3. unconditional, so there is no way for a website to bypass it

  4. blocking, so there is no chance that the website can determine the location while its waiting for the answer

The JavaScript code that causes the infobar to appear is a single function call which takes a callback function. The call to getCurrentPosition() will (should?) return immediately, but that doesn’t mean that one has access to the users location. The first time you are guaranteed to have location information is in the callback function. A typical callback function might look like this:

function show_map(position) {
  var latitude = position.coords.latitude;
  var longitude = position.coords.longitude;
  // let's show a map or do something interesting!
}

The callback function will be called with a single parameter, an object with two properties: coords and timestamp. The timestamp is just that, the date and time when the location was calculated. (Since this is all happening asynchronously, one cannot really know when that will happen in advance. It might take some time for the user to read the infobar and agree to share their location. Devices with dedicated GPS hardware may take some more time to connect to a GPS satellite. And so on.) The coords object has properties like latitude and longitude which are what one would expect: the user’s physical location in the world.

Table 8.7. Position Object

Property

Type

Notes

coords.latitude

double

decimal degrees

coords.longitude

double

decimal degrees

coords.altitude

double or null

meters above the reference ellipsoid

coords.accuracy

double

meters

coords.altitudeAccuracy

double or null

meters

coords.heading

double or null

degrees clockwise from true north

coords.speed

double or null

meters/second

timestamp

DOMTimeStamp

like a Date() object


Only three of the properties are guaranteed to be present (coords.latitude, coords.longitude, and coords.accuracy). The rest might come back null, depending on the capabilities of the device and the backend positioning server that it communicates with. The heading and speed properties are calculated based on the user’s previous position, if possible.

Handling Errors

Geolocation can be complicated. Things can go wrong. We have mentioned the “user consent” angle already. If your web application wants the users location but the user doesn't want to give it to you, you cannot use it. The user preference is king. But what does that look like in code? It looks like the second argument to the getCurrentPosition() function: an error handling callback function.

navigator.geolocation.getCurrentPosition(
  show_map, handle_error)

If anything goes wrong, the error callback function will be called with a PositionError object. (See specific situation where the error callback is not called. i.e Firefox 'Not Now' selected or popup window closed.)

Table 8.8. PositionError Object

Property

Type

Notes

code

short

an enumerated value

message

DOMString

not intended for end users


The code property will be one of

  1. PERMISSION_DENIED - if the user clicks that “Don’t Share” button or otherwise denies you access to their location.

  2. POSITION_UNAVAILABLE - if the network is down or the positioning satellites can’t be contacted.

  3. TIMEOUT - if the network is up but it takes too long to calculate the user’s position. How long is “too long”?

Be gracious in defeat.

function handle_error(err) {
  if (err.code == 1) {
    // user said no!
  }
}

The getCurrentPosition() function has an optional third argument, a PositionOptions object. There are three properties you can set in a PositionOptions object. All the properties are optional. You can set any or all or none of them.

Table 8.9. PositionOptions Object

Property

Type

Default

Notes

enableHighAccuracy

Boolean

false

true might be slower

timeout

long

(no default)

in milliseconds

maximumAge

long

0

in milliseconds


The enableHighAccuracy property is exactly what it sounds like. If true, and the device can support it, and the user consents to sharing their exact location, then the device will try to provide it. Both iPhones and Android phones have separate permissions for low- and high-accuracy positioning, so it is possible that calling getCurrentPosition() with enableHighAccuracy:true will fail, but calling with enableHighAccuracy:false would succeed. IP Mapping automatically sets this property to false.

The timeout property is the number of milliseconds your web application is willing to wait for a position. This timer doesn't start counting down until after the user gives permission to even try to calculate their position. One is not timing the user; one is timing the network. IP Mapping sets this parameter to 5000 milliseconds

The maximumAge property allows the device to answer immediately with a cached position. For example, let’s say you call getCurrentPosition() for the first time, the user consents, and your success callback function is called with a position that was calculated at exactly 10:00 AM. Exactly one minute later, at 10:01 AM, you call getCurrentPosition() again with a maximumAge property of 75000.

navigator.geolocation.getCurrentPosition(
  success_callback, error_callback, {maximumAge: 75000});

Using these options one is effectively stating that on doesn't necessarily need the user’s exact current location. One would be satisfied with knowing where they were 75 seconds ago (75000 milliseconds). The device knows where the user was 60 seconds ago (60000 milliseconds), because it calculated their location after the first time you called getCurrentPosition(). So the device doesn't bother to recalculate the user’s current location. It just returns exactly the same information it returned the first time: same latitude and longitude, same accuracy, and same timestamp.

For IP Mapping out intend is not necessarily to be able to pinpoint exactly where they are located since we are really only interested in the general area, probably only the city itself. For this reason a large value is preferable and can be set in the module parameters.

Handling Firefox 'Not Now' option

Since geolocation executes asynchronously, the navigator.geolocation.getCurrentPosition() function implements success/failure handlers and a timeout parameter so it can let you know if it was able to determine a location in a reasonable amount of time. We saw above, that it is constructed as follows:

navigator.geolocation.getCurrentPosition(success_handler,
        error_handler, {timeout:ms, maximumAge:ms,
        enableHighAccuracy:boolean});

This function looks straightforward but there are several caveats. First, not all browsers respect the timeout parameter consistently (specifically Firefox). Second, setting a maximum age in hopes of getting a recently cached value doesn't work consistently and may cause no return. Finally, because of the varied implementations for user privacy preferences and dialogs, this function cannot be relied on to always return. In some cases where a visitor ignores or dismisses the location sharing prompt, your application could be left waiting indefinitely for a result.

In particular in Firefox it appears that if the user hits Not Now, then one never gets a response back from the handler. This is an implementation decision by Mozilla (Firefox developers). Therefore, we have to set up a timeout which can check a flag that would be set by one of the handlers. If this flag is not set (meaning the handlers didn't fire in the allotted time), we have two options:

  1. Assume that the user denied the request (even though the denial was temporary)

  2. You can ask the user for permission again (via the same call) and the user will be presented with the dialog again.

This second option is not considered reasonable and would be very annoying, so we assume they denied temporarily and ask them again (politely!) the next time they visit the site (or the page).

[Note]Note

We also have a decision to make as to whether we record the visit. If is sensible to assume that we should, using the IP location detection, but what if the user then decides to accept the sharing of their location. i.e After the timeout has fired. If we also record this then we would have two locations stored in our system. One with the IP determined address and the other with the Geolocation determined address. So we have to add an additional check into the code to ensure that the specific IP address has not recorded the location in the time period since the timeout default fired.

To protect against these conditions, we write our own timeout like this:

// Check for HTML5 geolocation support.
  if(navigator.geolocation) {

    // Start a timer to ensure we get some kind of response.
    // Make sure to clear this timer in our success and error handlers
    location_timeout = setTimeout(function(){
      _error_handler({'TIMEOUT':'1'})
    }, 8000);

    // Call the HTML5 geolocation feature with our handlers for success/error and an 8-second timeout.
    navigator.geolocation.getCurrentPosition(success_handler, error_handler, {timeout:8000});
  }

  // If navigator.geolocation is not available (no HTML5 geolocation support).
  // Fall back to IP-based geolocation.
  else {
    IpFallback_Function();
  }
}
[Note]Note

We also have to ensure that the timeout variable is visible to all of the functions, so define it outside of the setting after the geolocation navigator call illustrated above.

The navigator.geolocation.getCurrentPosition() function returns either a Position object or a PositionError object. The PositionError object should contain a constant with a numeric error code and an error message string we can use to determine what went wrong. The following is a sample error handler using a switch case statement:

function error_handler(error) {

  // Respond to the possible error states.
  // In our live implementation we do not really care about these so do not display them.
  // But they are handy for testing and development.
  switch(error.code){
    case error.PERMISSION_DENIED:
      console.log("The user prevented this page from retrieving a location.");
      break;
    case error.POSITION_UNAVAILABLE:
      console.log("The browser was unable to determine its location: " + error.message);
      break;
    case error.TIMEOUT:
      console.log("The browser timed out before retrieving its location.");
      break;
    default:
      console.log("There was an unspecified or novel error. Nuts.");
  }

  // Clear the previously set timeout so we don't execute the error_handler twice.
  clearTimeout(location_timeout);

  // Call our IP-based geolocation function as a fallback.
  IpFallback_Function();
}

Our success handler is also slightly modified to handle the timeout.

function success_handler(position) {

  // Clear the timeout since the success_handler has executed...
  clearTimeout(location_timeout);

  // Get the coordinates from the HTML5 geolocation API.
  var latitude = position.coords.latitude;
  var longitude = position.coords.longitude;

  // If HTML5 geolocation reports success but fails to provide coordinates...
  if (!latitude || !longitude) {
    console.log("navigator.geolocation.getCurrentPosition returned bad data.");

    // Call our IP-based geolocation function as a fallback.
    IpFallback_Function();
  }
  else {
    // HTML5 geolocation success!
    // Call our database save function to store the result.
  }
}

So our code has been made slightly more complicated but is more robust because of the change. The above code is more representative than an illustration of the actual code implemented. Some timeout values may need modifying depending upon the 'responsiveness' of the web sites involved and their efficiency in saving values to the database.

Go To Top

Joomla! Debug Console

Session

Profile Information

Memory Usage

Database Queries