At this point, I am sure there are a lot of you reading this that are thinking to yourself, “Thank you, Mr. Author, for all of that lovely background information on what geolocation is, but can we see how to do this in code, already?” If that is you, then you are in luck, because this chapter is all about coding with the W3C Geolocation API.
The background information in the previous chapters is definitely relevant to our discussion on the Geolocation API itself. Understanding, for example, that the latitude and longitude that we retrieve from the user’s browser is in the WGS 84 datum will come in handy. If you have no idea what I am talking about, go back and read (or re-read) Chapter 2 so that you have a good grasp on the information we are going to be working with.
The W3C Geolocation API is a specification that provides scripted access to geographical location information associated with the hosting device.[8] It is meant to be a “high-level interface” so that the developer using it does not need to worry about details such as how the location information is being gathered. It does not matter whether the device is using GPS, IP Address, or Cell ID; only the geolocation information itself is important. The one caveat that the specification makes, however, is that there is no guarantee that the location returned from the API is the actual location of the device. This should come as no surprise, given that GPS may not have enough visible satellites to determine an accurate position, there may not be enough cell towers to get a proper triangulation from a Cell ID, or an IP Address could be spoofed to give a completely false location. Because of these, and other possible reasons, the developer may feel reasonably confident in the results returned by the API, but should never rely on any information blindly.
The latest version of the W3C Geolocation API Specification is W3C Candidate Recommendation 07 September 2010.
Currently, the W3C Geolocation API is supported by most modern browsers on both the desktop and on mobile phones. Table 3-1 shows the current browser support for the API. The biggest issue developers will face is that older browsers have obviously not adopted this technology since it was created after those browsers were released. This is particularly difficult for developers because of the widespread use of Internet Explorer 8 (and perhaps even Internet Explorer 7), and also for all users of mobile phones who have not upgraded to current versions of software or hardware. The good news is that the API should be available in all future browser and phone releases.
As I noted, not all browsers support the W3C Geolocation API, and these legacy browsers never will natively. Fortunately, other programmers have taken it upon themselves to do something about it, and wrote wrapper libraries that give these browsers most of the functionality found in the Geolocation API. However, there are differences between these libraries and the W3C Geolocation API which make it a bit more challenging for the developer to write code that will work in all browsers. First, let us take a look at some of these other APIs, and then we will see how we can resolve our differences.
Gears, formally known as Google Gears, is (as the name obviously suggests) a code library written by the good folks over at Google, Inc. Gears is designed to be an open source project that enables more powerful web applications by adding new features to the web browser. The component of Gears that most interests us is the Geolocation module, which acts very much like the W3C Geolocation API. In fact, parts of the W3C’s Geolocation API were probably modeled on Gears.
In the Gears API Blog on March 11, 2011, Google announced that no further new releases to Gears would be coming, nor would Gears support newer browsers such as Firefox 4 and Internet Explorer 9, and it will be removed from Chrome 12. Older browsers, however, can continue to use Gears to gain the functionality they lack.
Gears can be added to a web page using the following line of code:
<script type="text/javascript"
src="http://code.google.com/apis/gears/gears_init.js"></script>We do not need to worry about the specifics of using Gears in our code because, as you will see in geo-location-javascript we will let another library take care of reconciling differences between Gears, other APIs, and the W3C Geolocation API. Additional examples can be found in Chapter 4, but for now, just remember that this library is out there and available.
Of course, hardware vendors have their own specifications and APIs for using geolocation on their devices, especially for older mobile device platforms before geolocation became mainstream. The following lists some of the other Geolocation APIs that may be encountered or required depending on the devices connecting to your site:
iOS
BlackBerry
Nokia
webOS (Palm)
All iPhones with OS less than 3.0 relied on Apple’s own Geolocation API for any location-based application development. The same holds true for BlackBerry developers—BlackBerry devices with OS less than 6 relied on BlackBerry’s own implementation.
Nokia has its own version of a Geolocation API that works in the browser shipped with its phones, as does Palm with its webOS 2.1 SDK’s HTML5 Enhancements. It is hoped that further development by these vendors will release fully W3C Geolocation API compliant browsers or support, which would eliminate the mess that developers currently face.
Which brings us to...
geo-location-javascript is an attempt to build a JavaScript framework that will wrap all of the underlying platform implementations into a single API that works similarly to the W3C Geolocation API Specification. The following platforms are currently supported by the API:
iOS
Android
BlackBerry OS
Gears
Nokia Web Run-Time (WRT)
webOS Application Platform (Palm)
Torch Mobile Iris Browser
Mozilla Geode
The API has two key functions: checking to see if the connecting device has geolocation capabilities and getting the location of the device. As you will see in The W3C Geolocation API Does More, this is just a small subset of functionality that the full W3C Geolocation API has, and there is additional coding work needed if you want that additional functionality.
Consider the following code snippet:
<script type="text/javascript"
src="http://code.google.com/apis/gears/gears_init.js"></script>
<script type="text/javascript" src="geo.js"></script>
<script type="text/javascript">
function initialize() {
if (geo_position_js.init())
geo_position_js.getCurrentPosition(show_position, error_handler,
{ enableHighAccuracy: true }
);
else
alert('Geolocation functionality is not available.');
</script>The first line of code loads Gears into the application, and the
following line loads the geo-location-javascript API. In a real
application scenario, the initialize() function would be called on an
onload event fired by the
document.
The initialize() function
itself is straightforward. It first checks to see if the device
supports geolocation, and if it does it acquires the current position
of the device. Should the device not support geolocation, then a
message to that effect will be displayed to the user. The
geo-location-javascript API, like all other Geolocation APIs,
including the W3C Geolocation API, requires the user to opt in and
allow the location to be collected before proceeding. Learn more about
that in Privacy.
There are two callback functions in the call to geo_position_js.getCurrentPosition():
show_position and error_handler. If anything were to go wrong
with the attempt to locate the device, or the user opted out of the
location search, the error_handler
function would be called. Otherwise, the show_position function would be called, and
the latitude and longitude
of the device would be available to the application.
As previously mentioned, the geo-location-javascript API does
not have support for a position polling method like the W3C
Geolocation API does (discussed further in the next section). You
will need to use a JavaScript setInterval() and poll the getCurrentPosition() yourself.
In this chapter, and the book in general, the focus is on a JavaScript implementation of the W3C Geolocation API to work within HTML5 applications. The interface’s implementation is being included in all modern browsers for both desktop and mobile devices. Because it is a newer API, however, there has been a need for other APIs to step in and create the cross-browser implementations needed to include older devices and browsers. The geo-location-javascript API, described in geo-location-javascript, is one such API.
The downside to using these APIs is they do not share all the functionality that the W3C Geolocation API has, leaving gaps in what can be developed. As you will see in the rest of this chapter, the W3C Geolocation API is a robust and thorough interface that allows for the development of web applications that rival applications created natively on devices.
The W3C Geolocation API specifies a general implementation for the
objects, properties, and methods associated with the
Geolocation interface. One object holds the whole
implementation of the W3C Geolocation API—the
Geolocation object. This object can be used within
JavaScript to gather geolocation information about the device on which the
browser resides. The Geolocation object is a new
property of the global window.navigator
Browser Object, and can be accessed from the window.navigator.geolocation
instantiation.
As with all JavaScript objects, it is a best practice to first test for the existence of an object’s implementation within a browser before using it, as the following code illustrates:
if (window.navigator.geolocation) {
// do some geolocation stuff
} else {
// the browser does not natively support geolocation
}In the preceding code, the existence of an implementation of the
geolocation property is tested, and if
it exists, the code will do geolocation processing, otherwise something
else will need to be tried.
The Geolocation object contains three public methods, as described in Table 3-2. All geolocation functionality stems from these methods and the callback functions they take as parameters.
| Method | Description |
clearWatch(watchId) | Stops the watch process associated with the passed watchId. |
getCurrentPosition(successCallback,
[errorCallback, [options]]) | Attempt to gather geolocation information, calling successCallback when it succeeds or the optional errorCallback when it fails. |
watchPosition(successCallback, [errorCallback,
[options]]) | Attempts to gather geolocation information at regular intervals, calling successCallback when it succeeds or the optional errorCallback when it fails. |
Once it has been verified that the browser supports the W3C
Geolocation API, requests can be made to get the current position of the
device in question. This is done using the getCurrentPosition() method. The getCurrentPosition() method requires at least
one parameter, a position callback function. Optionally, it can also take
an error callback function, and an options parameter. It is called like
this:
navigator.geolocation.getCurrentPosition(successCallback, errorCallback,
options);The first parameter, successCallback, will be called when a
successful position has been discovered by the inner workings of the API.
The second parameter, errorCallback, is
optional and will be called when there is an error in gathering a
position. The final parameter, options,
is a PositionOptions object that is also
optional.
The successCallback parameter
for the getCurrentPosition() callback
is required—if it is omitted, the getCurrentPosition() call automatically fails
and aborts any location fetching.
The following code snippet shows the getCurrentPosition() more fully:
if (window.navigator.geolocation) {
navigator.geolocation.getCurrentPosition(successCallback, errorCallback,
options);
} else {
alert('Your browser does not natively support geolocation.');
}
function successCallback(position) {
// Do something with a location here
}
function errorCallback(error) {
// There was a problem getting the location
}The PositionOptions object is an optional
parameter that can be passed to the getCurrentPosition() method, and as you
will see in Update the User’s Position, is also an optional
parameter to the watchPosition()
method. All of the properties available in the
PositionOptions object, shown in Table 3-3, are optional as well.
| Property | JavaScript type | Description |
enableHighAccurary | Boolean | Flags the API to attempt to get as close to the exact
location of the device as possible. The default value is
false. Slower response times
and/or increased power consumption may result from setting this
property to true. |
maximumAge | Integer | Signals to the application that it will accept a cached
position with an age no greater than the time specified in
milliseconds. The default value is 0. |
timeout | Integer | Indicates the maximum length of time, in
milliseconds, that the application will
wait from the beginning of a call to the evocation of the
successCallback function. The default value
is 0. |
The following is an example of calling getCurrentPosition() with the
PositionOptions object set:
var options = {
enableHighAccuracy: true,
maximumAge: 60000,
timeout: 45000
};
navigator.geolocation.getCurrentPosition(successCallback, errorCallback,
options);This code calls getCurrentPosition(), requesting high
accuracy, and a cached position no older than 60 seconds, with a timeout
period of 45 seconds before returning an error.
A cached position is a position that was
gathered by the application sometime in the past that may be used again
instead of requiring a new position to be fetched. When it is not
necessary for the application to display changes in a position very
frequently, a cached position is a good alternative. This will save
processing overhead since a new call to the API will not be needed in
order to get a cached position. To specify the acceptable age of a
position, the PositionOptions object is passed to
the getCurrentPosition() or watchPosition() method with the optional
maximumAge property set to the
desired time in milliseconds.
For example:
var options = {
maximumAge: 600000
};
navigator.geolocation.getCurrentPosition(successCallback, errorCallback,
options);The preceding code would accept a cached position that no older
than 60 minutes. If you wish to get a fresh position each time, do not
pass a maximumAge property (it
defaults to 0), or pass the value
0 yourself. Should you wish to
always get a cached position, regardless of its age, pass the value
Infinity to the calling method, like
this:
var options = {
maximumAge: Infinity
};
navigator.geolocation.getCurrentPosition(successCallback, errorCallback,
options);There are times when an application requires an updated position
every time the device changes location. In these cases, a call to the
watchPosition() method is warranted in
place of calling the getCurrentPosition() method. The watchPosition() method has the same basic
structure as the getCurrentPosition()
method. It also takes a successCallback
parameter that is required, as well as two optional parameters: errorCallback and options.
The major difference between the two methods is that the watchPosition() method will return a value
immediately upon being called which uniquely identifies that
watch operation. The watch operation itself is an
asynchronous operation. It is called like
this:
var watcher = navigator.geolocation.watchPosition(successCallback,
errorCallback, options);The first parameter, successCallback, will be called when a
successful position has been gathered by the API. The second parameter,
errorCallback, is optional and will be
called when there is an error in gathering a position. The final
parameter, options, is a
PositionOptions object that is also optional. The
variable, watcher, is the unique
identifier for this particular watch operation.
The following code snippet shows how to use watchPosition():
var watcher = null;
var options = {
enableHighAccuracy: true,
timeout: 45000
};
if (window.navigator.geolocation) {
watcher = navigator.geolocation.watchPosition(successCallback,
errorCallback, options);
} else {
alert('Your browser does not natively support geolocation.');
}
function successCallback(position) {
// Do something with a location here
}
function errorCallback(error) {
// There was a problem getting the location
}The watchPosition() method has
built-in functionality to automatically poll the device for a change in
position and will call the successCallback function every time there is a
new position for the device. This eliminates the need for the developer
to roll her own code to poll the device every x
number of seconds. Because the watchPosition() method has automatic polling,
this also provides for true real-time geolocation applications. Creating
custom polling functionality will give you pseudo-real-time position
updates at best, and will cause additional processing overhead that will
slow the application down in the long run.
Whenever possible, the watchPosition() method’s polling capabilities
should be used for position updating. Do not create custom position
polling functionality unless your application has a unique need for
it.
Like the JavaScript clearTimeout() and clearInterval() methods, the W3C Geolocation
API provides a method for clearing a watch operation by passing the
desired watchId to the clearWatch() method. The syntax is:
navigator.geolocation.clearWatch(watcher);
The following code demonstrates creating a new watch operation, and then removing that watch upon the successful fetching of a position:
var watcher = null;
var options = {
enableHighAccuracy: true,
timeout: 45000
};
if (window.navigator.geolocation) {
watcher = navigator.geolocation.watchPosition(successCallback,
errorCallback, options);
} else {
alert('Your browser does not natively support geolocation.');
}
function successCallback(position) {
navigator.geolocation.clearWatch(watcher);
// Do something with a location here
}Once a location request has been gathered by the API, the successCallback function is called. This works
the same using either the getCurrentPosition() or updatePosition() methods. The successCallback function is passed one parameter
from the API, a Position object.
The Position object holds all of the
geolocation information that is returned from the W3C Geolocation API
call and is passed to a successCallback function. Table 3-4 contains a list of the current
properties this object has. There is room in this object for additional
information, and, in particular, geocoding information in possible
future versions of the API.
The main geographic information gathered by the API is held in a Coordinates object which is a property of the Position object (see Position Object). This information is in the World Geodetic System reference system WGS 84. More information on this reference system can be found in WGS 84. Currently, no other reference system is supported by the W3C Geolocation API. A list of the properties found in the Coordinates object are in Table 3-5.
| Property | Description |
latitude | The geographic coordinate of latitude for the device, measured in decimal degrees. |
longitude | The geographic coordinate of longitude for the device, measured in decimal degrees. |
altitude | The geographic height of the device, measured in meters above the WGS 84 ellipsoid. |
accuracy | The accuracy of the latitude and longitude, specified in meters. |
altitudeAccuracy | The accruacy of the height, specified in meters. When not
supported, this value is null. |
heading | The direction of travel of the device, measured in
degrees from 0° clockwise to 360°. This value will be NaN when the device is not moving.
When not supported, this value is null. |
speed | The current ground speed of the device, measured in
meters per second. When not supported, this value is null. |
In Example 3-1, all of the components of the Geolocation object covered so far are shown in use.
<!DOCTYPE html>
<html lang="en">
<head>
<title>A First Geolocation Example</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
<meta charset="utf-8"/>'
<script type="text/javascript">
var options = {
enableHighAccuracy: true,
maximumAge: 1000,
timeout: 45000
};
if (window.navigator.geolocation) {
navigator.geolocation.getCurrentPosition(successCallback,
errorCallback, options);
} else {
alert('Your browser does not natively support geolocation.');
}
function successCallback(position) {
var output = '';
output += "Your position has been located.\n\n";
output += 'Latitude: ' + position.coords.latitude + "°\n";
output += 'Longitude: ' + position.coords.longitude + "°\n";
output += 'Accuracy: ' + position.coords.accuracy + " meters\n";
if (position.coords.altitude)
output += 'Altitude: ' + position.coords.altitude + " meters\n";
if (position.coords.altitudeAccuracy)
output += 'Altitude Accuracy: ' + position.coords.altitudeAccuracy +
" meters\n";
if (position.coords.heading)
output += 'Heading: ' + position.coords.Heading + "°\n";
if (position.coords.speed)
output += 'Speed: ' + position.coords.Speed + " m/s\n";
output += 'Time of Position: ' + position.timestamp;
alert(output);'
}
function errorCallback(error) {
// There was a problem getting the location
}
</script>
</head>
<body>
<div>A First Geolocation Example</div>
</body>
</html>This example shows how to get all of the geographic information
from the Position object, though it does not do
anything more than alert the results
to the user. In a real application, the coordinates would be used to
plot a point on a map, or they could be saved to a database.
There are several reasons why a location request could fail within
the API, and with any of these the errorCallback function is called when it is
provided with either the getCurrentPosition() or updatePosition() methods. When provided, the
errorCallback function is passed one
parameter from the API, a PositionError
object.
The PositionError object holds all of the
error information returned from the W3C Geolocation API call and is
passed to an errorCallback function.
Table 3-6 contains a list of this
object’s current properties.
| Property | Description |
code | The code is a numeric value alerting
the developer to what the error is. The code will be one of the
following values:
|
message | A detailed message containing the error that was encountered for the purposes of developer debugging. This message is not meant to be displayed to the end-user of the application. |
In Example 3-3, a final version of the Geolocation object using all of the API is shown.
<!DOCTYPE html>
<html lang="en">
<head>
<title>A First Geolocation Example</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
<meta charset="utf-8"/>
<script type="text/javascript">
var options = {
enableHighAccuracy: true,
maximumAge: 1000,
timeout: 45000
};
if (window.navigator.geolocation) {
navigator.geolocation.getCurrentPosition(successCallback,
errorCallback, options);
} else {
alert('Your browser does not natively support geolocation.');
}
function successCallback(position) {
var output = '';
output += "Your position has been located.\n\n";
output += 'Latitude: ' + position.coords.latitude + "°\n";
output += 'Longitude: ' + position.coords.longitude + "°\n";
output += 'Accuracy: ' + position.coords.accuracy + " meters\n";
if (position.coords.altitude)
output += 'Altitude: ' + position.coords.altitude + " meters\n";
if (position.coords.altitudeAccuracy)
output += 'Altitude Accuracy: ' + position.coords.altitudeAccuracy +
" meters\n";
if (position.coords.heading)
output += 'Heading: ' + position.coords.Heading + "°\n";
if (position.coords.speed)
output += 'Speed: ' + position.coords.Speed + " m/s\n";
output += 'Time of Position: ' + position.timestamp;
alert(output);
}
function errorCallback(error) {
switch (error.code) {
case error.PERMISSION_DENIED:
alert('You have denied access to your position.');
break;
case error.POSITION_UNAVAILABLE:
alert('There was a problem getting your position.');
break;
case error.TIMEOUT:
alert('The application has timed out attempting to get your ' +
location.');
break;
}
}
</script>
</head>
<body>
<div>A First Geolocation Example</div>
</body>
</html>In this example, the errors are captured, and a more user-friendly
alert is sent to the user. In a real
application, it would be good to log the error, and possibly do more
with the message for the user, but this should give you some idea of
what to do with the error code being passed with the
PositionError object.
Privacy is an important matter, not only with the W3C Geolocation API, but with all geolocation applications available today. Companies know how dearly individuals hold their privacy, and take many precautions to safeguard any data collected from the user. If a user is ever in any doubt as to a company’s policies and procedures, he should check with the company’s Privacy Policy regarding user data.
To address security and privacy considerations with the W3C Geolocation API, the specification outlines that no locations may be sent to web applications without the express permission of the user. The standard way this is being implemented across browsers is to provide an information bar that, as per the W3C specification, lists the URI of the document requesting a location (see Figure 3-1). The user can opt to always allow access for the site with a checkbox indicating this site-level access, which can be undone at any time within the settings of the browser itself.
From a development standpoint, should the user decide not to grant your application access to his location information, you should make sure that your application can handle this gracefully.
Unless the user has granted the application a site-level access retained within the specific browser, the permissions given by the user will expire at the end of the current browsing session. With such opt-in policies, any implementation of the W3C Geolocation API should achieve adequate privacy functionality and alleviate a user’s anxiety towards their location data. The best a developer can do is guarantee proper handling of a user’s data, while users would be wise to follow a policy of only granting access to their personal data to trusted individuals and sites.
[8] Geolocation API Specification: W3C Candidate Recommendation 07 September 2010. Editor, Andrei Popescu, Google, Inc. http://www.w3.org/TR/geolocation-API/.