The Geolocation API allows an application to leverage the GPS capabilities of a mobile device and determine the device’s location on the surface of the earth. Using this API, either an application can manually check the device’s current position or it can create a location watch that will cause the application to be periodically notified of the device’s physical location.
To use this API, the device running the application must provide geolocation capabilities (by utilizing either a GPS radio and associated software or some alternate mechanism for determining the device’s location). While geolocation capabilities in smartphones are regularly enhanced in newer models, an application cannot guarantee that even though the device has geolocation capabilities, it will be able to determine its location. There are many geographical (such as being in a canyon) or mechanical issues (such as being inside a building) that can affect the device’s ability to report its location to a PhoneGap application.
Since most HTML 5–compatible mobile browsers provide support for the W3C Geolocation API Specification already (www.w3.org/TR/geolocation-API), PhoneGap applications running on a compatible device can use this API directly today (this is the default behavior for PhoneGap applications). For devices that don’t provide a Geolocation API such as BlackBerry devices running Device Software 5 or Android 1.x devices, the PhoneGap development team has included a compatible Geolocation API in the standard PhoneGap JavaScript library. For these devices, to enable a PhoneGap application to use PhoneGap’s implementation of the Geolocation API, simply invoke Geolocation.usePhoneGap
() once the PhoneGap deviceready
event has fired.
The implementation of this API is structured very similarly to how the Accelerometer (Chapter 10) and Compass (Chapter 13) APIs work. You’ll find that the examples provided in this chapter are very similar to what has already been shown in those chapters.
To manually determine the location of a smartphone, an application should execute the following code:
navigator.geolocation.getCurrentPosition(onGeolocationSuccess,
ongeolocationError);
The call to getCurrentPosition
includes the callback functions onLocationSuccess
and onLocationError
. The onLocationSuccess
is executed when the device’s location has been successfully measured, and the onLocationError
function is executed if there is an error measuring the device’s location.
An application can also configure options that control the way in which the API measures location, as shown in the following example:
var geolocationOptions = {
timeout : 3000,
enableHighAccuracy : true
};
navigator.geolocation.getCurrentPosition(onGeolocationSuccess,
ongeolocationError, geolocationOptions);
In this example, a geolocationOptions
object is passed to the call to getCurrentPosition
. The available properties for geolocationOptions
are as follows:
• enableHighAccuracy
: Boolean value indicating whether the application would like to measure the device’s location with a higher degree of accuracy. When enabling this option, the application may obtain more accurate results, but at the same time, it will place a higher load on the device, which may decrease performance and battery life while the application runs.
• frequency
: Defines in milliseconds how often the location is measured. This option applies only when implementing a location watch (described later in this chapter). This is a PhoneGap-specific setting, and as PhoneGap more closely implements the W3C specification, this property will be removed. Developers should use the maximumAge
property instead.
• maximumAge
: Defines the maximum age (in milliseconds) of a cached location value that will be accepted by the application. A smaller value for this property should force the API to only deliver more recent location updates.
• timeout
: The maximum amount of time (in milliseconds) that is allowed to pass between the call to either geolocation.getCurrentPosition
or geolocation.watchPosition
until the onGeolocationSuccess
callback is executed.
After the API measures the device’s location, it executes the onGeolocationSuccess
callback function. Passed to the function is a location object that exposes coords
properties an application can use to understand the device’s location. The following function illustrates the properties that are exposed through the location object:
function onGeolocationSuccess(loc) {
//We received something from the API, so first get the
// timestamp in a date object so we can work with it
var d = new Date(loc.timestamp);
//Then replace the page's content with the current
// location retrieved from the API
lc.innerHTML = '<b>Current Location</b><hr />' +
'<b>Latitude</b>: ' + loc.coords.latitude +
'<br /><b>Longitude</b>: ' + loc.coords.longitude +
'<br /><b>Altitude</b>: ' + loc.coords.altitude +
'<br /><b>Accuracy</b>: ' + loc.coords.accuracy +
'<br /><b>Altitude Accuracy</b>: ' +
loc.coords.altitudeAccuracy +
'<br /><b>Heading</b>: ' + loc.coords.heading +
'<br /><b>Speed</b>: ' + loc.coords.speed +
'<br /><b>Timestamp</b>: ' + d.toLocaleString();
}
When an error occurs while measuring the device’s location, the onGeolocation Error
callback function is executed. Passed to the function is an error object that exposes the code
and message
properties, as shown in the following example:
function onGeolocationError(e) {
var msgText = "Geolocation error: #" + e.code + "
" +
e.message;
console.log(msgText);
alert(msgText);
}
Example 19-1 illustrates how to use the getCurrentPosition
method in an application. The application exposes a single button that, when clicked, calls getCurrentPosition
and updates the screen with the device’s current location.
<!DOCTYPE html>
<html>
<head>
<title>Example 19-1</title>
<meta name="viewport" content="width=device-width,
height=device-height initial-scale=1.0,
maximum-scale=1.0, user-scalable=no;" />
<meta http-equiv="Content-type" content="text/html;
charset=utf-8">
<script type="text/javascript" charset="utf-8"
src="phonegap-1.2.0.js"></script>
<script type="text/javascript">
//Location content
var lc;
//PhoneGap Ready variable
var pgr = false;
function onBodyLoad() {
//During testing, Let me know we got this far
alert("onBodyLoad");
//Add the PhoneGap deviceready event listener
document.addEventListener("deviceready", onDeviceReady,
false);
}
function onDeviceReady() {
//During testing, Let me know PhoneGap actually
// initialized
alert("onDeviceReady");
//Get a handle we'll use to adjust the accelerometer
//content
lc = document.getElementById("locationInfo");
//Set the variable that lets other parts of the program
//know that PhoneGap is initialized
pgr = true;
}
function getLocation() {
alert("getLocation");
if(pgr == true) {
var locOptions = {
timeout : 5000,
enableHighAccuracy : true
};
//get the current location
navigator.geolocation.getCurrentPosition(
onGeolocationSuccess, onGeolocationError,
locOptions);
//Clear the current location while we wait for a
//reading
lc.innerHTML = "Reading location...";
} else {
alert("Please wait,
PhoneGap is not ready.");
}
}
function onGeolocationSuccess(loc) {
alert("onLocationSuccess");
//We received something from the API, so first get the
// timestamp in a date object so we can work with it
var d = new Date(loc.timestamp);
//Then replace the page's content with the current
// location retrieved from the API
lc.innerHTML = '<b>Current Location</b><hr />' +
'<b>Latitude</b>: ' + loc.coords.latitude +
'<br /><b>Longitude</b>: ' + loc.coords.longitude +
'<br /><b>Altitude</b>: ' + loc.coords.altitude +
'<br /><b>Accuracy</b>: ' + loc.coords.accuracy +
'<br /><b>Altitude Accuracy</b>: ' +
loc.coords.altitudeAccuracy +
'<br /><b>Heading</b>: ' + loc.coords.heading +
'<br /><b>Speed</b>: ' + loc.coords.speed +
'<br /><b>Timestamp</b>: ' + d.toLocaleString();
}
function onGeolocationError(e) {
alert("Geolocation error: #" + e.code + "
" +
e.message);
}
</script>
</head>
<body onload="onBodyLoad()">
<h1>Geolocation API Demo #1</h1>
<p>
Click the button to determine the current location.
</p>
<input type="button" value="Refresh Location"
onclick="getLocation();">
<hr />
<p id="locationInfo"></p>
</body>
</html>
Figure 19-1 shows Example 19-1 running on a BlackBerry Torch 9800 simulator.
Instead of checking the device’s location manually, an application can define a geolocation watch that causes the device’s location to be passed to the program periodically.
To create a location watch, an application should execute the following code:
watchID = navigator.geolocation.watchPosition(
onGeolocationSuccess, onGeolocationError);
In this example, a watchID
variable is assigned to the result of the operation; an application can use the watchID
at a later time to cancel the watch. As with other PhoneGap APIs, the method is passed the names of two functions, the onGeolocationSuccess
and onGeolocationError
functions, which are executed after the device’s location has been successfully measured and when there is an error measuring the device’s location.
As with the call to getCurrentLocation
, the watchPosition
method can accept a geolocationOptions
object to control configuration parameters for the watch. In the following example, the geolocationOptions
object is configured to ignore cached values older than 10 seconds (10,000 milliseconds), to time out if a response isn’t received within 5 seconds (5,000 milliseconds), and to try to measure with higher accuracy when determining the device’s location.
var geolocationOptions = {
maximumAge : 10000,
timeout : 5000,
enableHighAccuracy : true
};
//get the current location
watchID = navigator.geolocation.watchPosition(
onGeolocationSuccess, onGeolocationError, geolocationOptions);
The onGeolocationSuccess
function is passed a geolocation object, loc
in this example, which exposes the same geolocation properties described in the previous section.
function onGeolocationSuccess(loc) {
//We have a new location, so get the timestamp in a date
// object so we can work with it
var d = new Date(loc.timestamp);
//Replace the page's content with the current
//location retrieved from the API
$('#locationInfo').html('<b>Latitude</b>: ' +
loc.coords.latitude + '<br /><b>Longitude</b>: ' +
loc.coords.longitude + '<br /><b>Altitude</b>: ' +
loc.coords.altitude);
$('#timestampInfo').prepend(d.toLocaleString() +
'<br />'),
}
In this example, a subset of the location information is built into some HTML markup for cleaner rendering on the application screen and then added to the locationInfo
division of the page using the $()
function from jQuery (www.jquery.com). The application also maintains a reverse chronological (newest at the top) history of timestamp information in the timestampInfo
division of the page (you can see the HTML markup for the page and the complete application code in the listing for Exercise 19-2 later in the chapter). When the application runs, it will display a screen similar to the one shown in Figure 19-2.
As you can see from the figure, the onGeolocationSuccess
function is being executed every two seconds, even if the device hasn’t moved. When I first started working with this API, I expected that some sort of trigger could be defined that could be used to minimize the number of times the onGeolocationSuccess
function would be fired, but that turned out to not be the case. No matter what values I entered for maximumAge
and timeout
, the watch returns geolocation information every two seconds.
If you think about it, querying for geolocation every two seconds will likely put a big load on the device and likely affect both application and overall device performance plus reduce battery life. To help reduce the impact on performance, I rewrote the function so it captures the previous longitude and latitude values and updates the location information on the screen only when there’s been a change, as shown in the following example:
function onGeolocationSuccess(loc)
//We have a new location, so get the timestamp in a date
// object so we can work with it
var d = new Date(loc.timestamp);
//Has anything changed since the last time?
if(lastLat != loc.coords.latitude ||
lastLong != loc.coords.longitude) {
//Then replace the page's content with the current
// location retrieved from the API
$('#locationInfo').html('<b>Latitude</b>: ' +
loc.coords.latitude + '<br /><b>Longitude</b>: ' +
loc.coords.longitude + '<br /><b>Altitude</b>: ' +
loc.coords.altitude);
$('#timestampInfo').prepend(d.toLocaleString() +
'<br />'),
lastLat = loc.coords.latitude;
lastLong = loc.coords.longitude;
} else {
$('#timestampInfo').prepend('Skipping: ' +
d.toLocaleTimeString() + '<br />'),
}
}
Depending on your application, you could easily modify this approach so it takes into consideration how much the location has changed before updating the screen or doing something time-consuming or processor-consuming within the application. Figure 19-3 shows the same application with that fix implemented; in other words, it shows the geolocation information being updated only when the values actually change.
The onGeolocationError
function is the same as the one used in the previous section:
function onGeolocationError(e) {
var msgText = "Geolocation error: #" + e.code + "
" +
e.message;
console.log(msgText);
alert(msgText);
}
Once a watch has been created, it can be canceled using the saved watchID
variable and the following code:
navigator.geolocation.clearWatch(watchID);
There’s no callback function required, but you could do a little extra work to help out your user, as shown in the following function example:
function cancelWatch() {
//Clear the watch
navigator.geolocation.clearWatch(watchID);
//Clear the watch ID (just because)
watchID = null;
//Let the user know we cleared the watch
alert("Location Watch Cancelled");
}
In the example, I clear the watch and then null out the watchID
variable just to make sure it’s not available in case the program accidently tries to clear the same watch later. We don’t have an onGeolocationError
function to execute if this fails, so it might even be better if you wrap the call into a try/catch
block and deal with any errors that might occur directly.
Example 19-2 lists the complete HTML markup and application code for the application illustrated in Figure 19-3.
<!DOCTYPE html>
<html>
<head>
<title>Example 19-1</title>
<meta name="viewport" content="width=device-width,
height=device-height initial-scale=1.0,
maximum-scale=1.0, user-scalable=no;" />
<meta http-equiv="Content-type" content="text/html;
charset=utf-8">
<script type="text/javascript" charset="utf-8"
src="jquery-1.7.1.js"></script>
<script type="text/javascript" charset="utf-8"
src="phonegap.js"></script>
<script type="text/javascript">
var watchID, lastLong, lastLat;
function onBodyLoad() {
//Add the PhoneGap deviceready event listener
document.addEventListener("deviceready", onDeviceReady,
false);
}
function onDeviceReady() {
//Create the watch
startWatch();
}
function startWatch() {
//Clear out the previous content on the page
$('#locationInfo').empty();
$('#timestampInfo').empty();
//Show and hide the appropriate buttons
$('#btnStart').hide();
$('#btnCancel').show();
//Geolocation Options
var locOptions = {
maximumAge : 10000,
timeout : 5000,
enableHighAccuracy : true
};
//get the current location
watchID = navigator.geolocation.watchPosition(
onLocationSuccess, onLocationError, locOptions);
}
function onLocationSuccess(loc) {
//We have a new location, so get the timestamp in a date
// object so we can work with it
var d = new Date(loc.timestamp);
//Has anything changed since the last time?
if(lastLat != loc.coords.latitude ||
lastLong != loc.coords.longitude) {
//Then replace the page's content with the current
// location retrieved from the API
$('#locationInfo').html('<b>Latitude</b>: ' +
loc.coords.latitude + '<br /><b>Longitude</b>: ' +
loc.coords.longitude + '<br /><b>Altitude</b>: ' +
loc.coords.altitude);
$('#timestampInfo').prepend(d.toLocaleString() +
'<br />'),
lastLat = loc.coords.latitude;
lastLong = loc.coords.longitude;
} else {
$('#timestampInfo').prepend('Skipping: ' +
d.toLocaleTimeString() + '<br />'),
}
}
function onLocationError(e) {
alert("Geolocation error: #" + e.code + "
" +
e.message);
}
function cancelWatch() {
//Clear the watch
navigator.geolocation.clearWatch(watchID);
//Clear the watch ID (just because)
watchID = null;
//Hide the cancel button so they can't cancel it again.
$('#btnCancel').hide();
$('#btnStart').show();
//Let the user know we cleared the watch
alert("Watch Cancelled");
}
</script>
</head>
<body onload="onBodyLoad()">
<h1>Geolocation API Demo #2</h1>
<input type="button" value="Cancel"
onclick="cancelWatch();" id="btnCancel">
<input type="button" value="Start"
onclick="startWatch();" id="btnStart">
<br />
<b>Location</b>
<hr />
<div id="locationInfo"></div>
<br />
<b>Timestamp</b>
<hr />
<div id="timestampInfo"></div>
</body>
</html>