The Geolocation API provides scripted access to geographical location information associated with the hosting device. This gives your applications the ability to locate users and track their latitude and longitude as they move about. This functionality could be used for many interesting use cases such as:
Give your app the ability to schedule a task to alert users the moment they enter or leave a location. You could also target ads for users within a certain city or state.
Combine your app with a service like the Google Maps API (Figure 7-1), and you can translate latitude and longitude coordinates into actual postal addresses.
Track distances driven, walked, or ran.
The API itself is device agnostic; it doesn’t care how the browser determines location. The underlying mechanism to obtain the user’s actual location may be through WiFi, GPS, or by the user actually entering a zip code into the device. The API is designed to gather both “one-shot” position requests and repeated position updates. Of course, Geolocation is no different than any of the other HTML5e APIs in regard to bugs, workarounds, and differences in implementations across browsers. After a review of the basics, we’ll dive into the cross-browser nuances.
To access a user’s location, run the following JavaScript:
navigator
.
geolocation
.
getCurrentPosition
(
function
(){
//show a map});
The web browser asks the user for permission to reveal his location, as shown in Figure 7-2.
After receiving permission, the browser returns a position
object with a coords
attribute. This allows you to call the following properties, from which you can learn the user’s latitude, longitude, and many other data points:
position
.
coords
.
latitude
//geographic coordinate in decimal degrees
position
.
coords
.
longitude
//geographic coordinate in decimal degrees
position
.
coords
.
altitude
//the height of the position (meters above the ellipsoid)
position
.
coords
.
accuracy
//accuracy level of latitude and longitude coordinates
position
.
coords
.
altitudeAccuracy
//specified in meters
position
.
coords
.
heading
// direction of travel of the hosting device in degrees
position
.
coords
.
speed
//the device's current velocity (meters per second)
Only the latitude
, longitude
, and accuracy
properties are guaranteed to be available. The rest might come back null, depending on the capabilities of the user’s device and the backend positioning server that it talks to.
The getCurrentPosition()
function has an optional third argument, a PositionOptions
object:
navigator
.
geolocation
.
getCurrentPosition
(
successCallback
,
errorCallback
,
{
enableHighAccuracy
:
true
,
timeout
:
10000
,
maximumAge
:
6000
});
The enableHighAccuracy
attribute provides a hint that
the application would like to receive the best possible results. If this attribute is true, the
device can support it, and the user consents, then the device will try to provide an exact
location. Using this attribute may result in slower response times or increased power
consumption. The user might also deny this capability, or the device might have more accurate
results to provide. The intended purpose of this attribute is to allow applications to inform
the implementation that they do not require high accuracy Geolocation fixes and, therefore, the
implementation can avoid using Geolocation providers that consume a significant amount of power
(think GPS).
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 share position data. You’re not timing the user; you’re timing the network.
The maximumAge
attribute indicates that the
application is willing to accept a cached position whose age is no greater than the specified
time in milliseconds. This gives you a window of time to pull a cached location from the user
device. By defining this attribute, you are saying that you’re fine with where this device was
located at x milliseconds in the past.
Your web app will dictate exactly the specificity of your Geolocation needs. So keep in mind battery life and latencies on the user device when you use the above properties. If you need to track the location of the user continuously, the spec defines the watchPosition()
function. It has the same structure as getCurrentPosition()
and will call the successCallback
whenever the device position changes:
navigator
.
geolocation
.
watchPosition
(
successCallback
,
errorCallback
,
{
enableHighAccuracy
:
true
,
timeout
:
10000
,
maximumAge
:
6000
});
Use watchPosition()
with care in Mobile Safari running on iOS5. As of this writing, there is a known issue: the page will run for roughly four minutes, after which the user will receive a “JavaScript execution exceeded timeout” error. To work around the watchPosition()
issue on iOS5, you can implement the following code using getCurrentPosition()
with setInterval()
:
var
geolocationID
;
(
function
getLocation
()
{
var
count
=
0
;
geolocationID
=
window
.
setInterval
(
function
()
{
count
++
;
if
(
count
>
3
)
{
//when count reaches a number, reset interval
window
.
clearInterval
(
geolocationID
);
getLocation
();
}
else
{
navigator
.
geolocation
.
getCurrentPosition
(
successCallback
,
errorCallback
,
{
enableHighAccuracy
:
true
,
timeout
:
10000
});
}
},
600000
);
//end setInterval;
})();
Another issue with the specific WebKit Geolocation implementation, is that accessing geolocation activates the Geolocation service, which currently blocks page caching (https://bugs.webkit.org/show_bug.cgi?id=43956). If you simply check the geolocation
property, you can avoid this issue:
function
supports_geolocation
()
{
try
{
return
'geolocation'
in
navigator
&&
navigator
[
'geolocation'
]
!==
null
;
}
catch
(
e
)
{
return
false
;
}
}
You can view a live demo with all implemented workarounds at http://html5e.org/example/geo.
To track a user over a set of latitude and longitude coordinates, you can use the Haversine formula. With it, your application can calculate the shortest distance over the Earth’s surface and provide an as-the-crow-flies distance between the points. The code you need is:
function
calculateDistance
(
lat1
,
lon1
,
lat2
,
lon2
)
{
var
R
=
6371
;
// km
var
dLat
=
(
lat2
-
lat1
).
toRad
();
var
dLon
=
(
lon2
-
lon1
).
toRad
();
var
a
=
Math
.
sin
(
dLat
/
2
)
*
Math
.
sin
(
dLat
/
2
)
+
Math
.
cos
(
lat1
.
toRad
())
*
Math
.
cos
(
lat2
.
toRad
())
*
Math
.
sin
(
dLon
/
2
)
*
Math
.
sin
(
dLon
/
2
);
var
c
=
2
*
Math
.
atan2
(
Math
.
sqrt
(
a
),
Math
.
sqrt
(
1
-
a
));
var
d
=
R
*
c
;
return
d
;
}
Number
.
prototype
.
toRad
=
function
()
{
return
this
*
Math
.
PI
/
180
;
}
This distance calculation, along with many others, is available at http://www.movable-type.co.uk/scripts/latlong.html under the Creative Commons Attribution 3.0 License.
The term “geocoding” generally refers to translating a human-readable address into a location on a map. The process of doing the converse, translating a location on the map into a human-readable address, is known as reverse geocoding. The following code is a simple example of how to reverse geocode coordinates returned from the Geolocation API with the Google Maps API:
<!DOCTYPE html>
<html>
<head>
<meta
name=
"viewport"
content=
"initial-scale=1.0, user-scalable=no"
>
<meta
charset=
"utf-8"
>
<title>
Google Maps JavaScript API v3 Example: Reverse Geocoding</title>
<link
href=
"https://google-developers.appspot.com/maps/documentation/
javascript/examples/default.css"
rel=
"stylesheet"
>
<script
src=
"https://maps.googleapis.com/maps/api/js?sensor=false"
>
</script>
<script>
var
geocoder
;
var
map
;
var
infowindow
=
new
.
maps
.
InfoWindow
();
var
marker
;
function
initialize
()
{
geocoder
=
new
.
maps
.
Geocoder
();
var
latlng
=
new
.
maps
.
LatLng
(
40.730885
,
-
73.997383
);
var
mapOptions
=
{
zoom
:
8
,
center
:
latlng
,
mapTypeId
:
'roadmap'
}
map
=
new
.
maps
.
Map
(
document
.
getElementById
(
'map_canvas'
),
mapOptions
);
}
function
codeLatLng
()
{
var
input
=
document
.
getElementById
(
'latlng'
).
value
;
var
latlngStr
=
input
.
split
(
','
,
2
);
var
lat
=
parseFloat
(
latlngStr
[
0
]);
var
lng
=
parseFloat
(
latlngStr
[
1
]);
var
latlng
=
new
.
maps
.
LatLng
(
lat
,
lng
);
geocoder
.
geocode
({
'latLng'
:
latlng
},
function
(
results
,
status
)
{
if
(
status
==
.
maps
.
GeocoderStatus
.
OK
)
{
if
(
results
[
1
])
{
map
.
setZoom
(
11
);
marker
=
new
.
maps
.
Marker
({
position
:
latlng
,
map
:
map
});
infowindow
.
setContent
(
results
[
1
].
formatted_address
);
infowindow
.
open
(
map
,
marker
);
}
else
{
alert
(
'No results found'
);
}
}
else
{
alert
(
'Geocoder failed due to: '
+
status
);
}
});
}
</script>
</head>
<body
onload=
"initialize()"
>
<div>
<input
id=
"latlng"
type=
"textbox"
value=
"40.714224,-73.961452"
>
</div>
<div>
<input
type=
"button"
value=
"Reverse Geocode"
onclick=
"codeLatLng()"
>
</div>
<div
id=
"map_canvas"
style=
"height: 90%; top:60px; border: 1px solid black;"
></div>
</body>
</html>
When working with the Geolocation API, you should detect and wrap available Geolocation mechanisms that are available across different mobile devices. For example, you could detect Google Gears, BlackBerry, and the default Geolocation API within one JavaScript init()
method. But why try to code all this yourself, when you could just use a framework? The Geolocation JavaScript frameworks are relatively small in both size and selection.
A mobile centric framework using nonstandard BlackBerry and WebOD tricks, geo-location-javascript (http://code.google.com/p/geo-location-javascript) wraps the underlying platform-specific implementation through a simple JavaScript API that is aligned to the W3C Geolocation API specification. Under an MIT license, geo-location-javascript supports a range of platforms, including iOS, Android, BlackBerry OS, browsers with Google Gears support (Android, Windows Mobile), Nokia Web Run-Time (Nokia N97), webOS Application Platform (Palm Pre), Torch Mobile Iris Browser, and Mozilla Geode.
To setup and use the API, the code you need is:
<html>
<head>
<title>
Javascript geo sample</title>
<script
src=
"http://code.google.com/apis/gears/gears_init.js"
type=
"text/javascript"
charset=
"utf-8"
></script>
<script
src=
"js/geo.js"
type=
"text/javascript"
charset=
"utf-8"
></script>
</head>
<body>
<b>
Javascript geo sample</b>
<script>
if
(
geo_position_js
.
init
()){
geo_position_js
.
getCurrentPosition
(
success_callback
,
error_callback
,
{
enableHighAccuracy
:
true
});
}
else
{
alert
(
"Functionality not available"
);
}
function
success_callback
(
p
)
{
alert
(
'lat='
+
p
.
coords
.
latitude
.
toFixed
(
2
)
+
';
lon='
+
p
.
coords
.
longitude
.
toFixed
(
2
));
}
function
error_callback
(
p
)
{
alert
(
'error='
+
p
.
message
);
}
</script>
</body>
</html>
Supporting all jQuery’s A-graded browsers and the latest Opera, the Webshims (http://afarkas.github.com/webshim/demos) framework is based on jQuery and Modernizr and falls under an MIT license. It tries to handle many different polyfills and shims, including Gelocation.
The set-up and usage code looks like this:
<!DOCTYPE html>
<html
lang=
"en"
>
<head>
<script
src=
"http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"
>
</script>
<script
src=
"../js-webshim/minified/extras/modernizr-custom.js"
></script>
<script
src=
"../js-webshim/minified/polyfiller.js"
></script>
<script>
$
.
webshims
.
setOptions
(
'geolocation'
,
{
confirmText
:
'{location} wants to know your position. It is Ok.'
});
//load all polyfill features
//or load only a specific feature with $.webshims.polyfill('feature-name');
$
.
webshims
.
polyfill
();
</script>
A few other scripts try to handle this polyfill, including:
Geolocation API crossbrowser support (http://bit.ly/Geolocation-API-Polyfill)
HTML5 Geolocation with fallback (http://gist.github.com/366184)
Currently, one of the greatest drawbacks to using the Geolocation API within a mobile web browser is not having the ability to run in the background after the browser has closed. For example, it gets extremely difficult to track the user in the background and allow the person to switch to another app outside of the mobile browser. At this point, your browser must remain open as a background process for your Geolocation-based app to work properly.