Geocoding A User's Location Using Javascript's GeoLocation API

A couple of days ago, I demonstrated how to geocode a user's IP address for free using ColdFusion and IPInfoDB. This provided us with a server-side approach to obtaining location information for a incoming web request. As Christian Ready demonstrated at CFUNITED 2010, however, many of the modern browsers now provide us with a Javascript geolocation API that can be used to determine a user's location right from within the web page. Now that I've looked at the server-side approach, I thought it would be fun to explore this client-side approach.

In browsers that support geocoding, the geolocation API is exposed in Javascript as a member of the navigator object:

navigator.geolocation

Therefore, an easy way to check for geolocation support is simply to check for the existence of this navigator property:

if (navigator.geolocation){ ... geolocation support ... }

Once geolocation support is determined to exist, the API exposes three simple methods:

getCurrentPosition ( success [, error, options ] )

( success [, error, options ] ) watchPosition ( success [, error, options ] )

( success [, error, options ] ) clearWatch( watchTimerID )

The getCurrentPosition() method launches an asynchronous request that tries to find the location (latitude and longitude) of the current browser. This initial position uses efficient lookups like IP routing and may not be the most accurate. On browsers that support GPS (or other forms of triangulation), a secondary more accurate positions may be found using the watchPosition() method.

The watchPosition() method acts very much like Javascript's core setInterval() method. When you call the watchPosition() method, it creates a timer that periodically gathers the user's current position. And, just like setInterval(), the watchPosition() method returns a timer ID that can be used in conjunction with the clearWatch() method to stop this timer (think clearInterval()). Not only can the watchPosition() method be used to find secondary, more accurate location information, it can also be used to track a user's location as they move about from one position to another (primarily for those using mobile devices).

While you could just as easily set up your own timer to periodically call the getCurrentPosition() method, Mozilla recommends the watchPosition() approach as it is more energy efficient:

Note that you can also watch for changes in position by calling getCurrentPosition on a regular basis. But for power savings and performance reasons we suggest that you use watchPosition when you can. Callback APIs generally save power and are only called when required. This will make the browser more responsive, especially on mobile devices.

When the success callback is invoked from within either the getCurrentPosition() method or the watchPosition() method, a Position object is passed in as the only invocation argument. This Position object contains a coords object which, in turn, contains our latitude and longitude values. These latitudinal and longitudinal values can then be used to interact with location-based services like Google Maps.

Now that we have a general idea of how this ties together, let's take a look at some code to see the API in action. In the following demo, I am going to be using the navigator.geolocation API in conjunction with the Google Maps API to plot the location of the current user.

<!DOCTYPE html> <html> <head> <title>Geocoding Browser Position Using Javascript's Geolocation API</title> <style type="text/css"> html, body { height: 100% ; margin: 0px 0px 0px 0px ; overflow: hidden ; padding: 0px 0px 0px 0px ; width: 100% ; } #mapContainer { height: 100% ; width: 100% ; } </style> <!--- Include jQuery and Google Map scripts. ---> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script> </head> <body> <div id="mapContainer"> <!-- This is where Google map will go. ---> </div> <!--- Now that we have defined our map container, we should be able to immediately load our Google Map. ---> <script type="text/javascript"> // Get the map container node. var mapContainer = $( "#mapContainer" ); // Create the new Goole map controller using our // map (pass in the actual DOM object). Center it // above the first Geolocated IP address. map = new google.maps.Map( mapContainer[ 0 ], { zoom: 11, center: new google.maps.LatLng( 40.700683, -73.925972 ), mapTypeId: google.maps.MapTypeId.ROADMAP } ); // I add a marker to the map using the given latitude // and longitude location. function addMarker( latitude, longitude, label ){ // Create the marker - this will automatically place it // on the existing Google map (that we pass-in). var marker = new google.maps.Marker({ map: map, position: new google.maps.LatLng( latitude, longitude ), title: (label || "") }); // Return the new marker reference. return( marker ); } // I update the marker's position and label. function updateMarker( marker, latitude, longitude, label ){ // Update the position. marker.setPosition( new google.maps.LatLng( latitude, longitude ) ); // Update the title if it was provided. if (label){ marker.setTitle( label ); } } // -------------------------------------------------- // // -------------------------------------------------- // // -------------------------------------------------- // // -------------------------------------------------- // // Check to see if this browser supports geolocation. if (navigator.geolocation) { // This is the location marker that we will be using // on the map. Let's store a reference to it here so // that it can be updated in several places. var locationMarker = null; // Get the location of the user's browser using the // native geolocation service. When we invoke this method // only the first callback is requied. The second // callback - the error handler - and the third // argument - our configuration options - are optional. navigator.geolocation.getCurrentPosition( function( position ){ // Check to see if there is already a location. // There is a bug in FireFox where this gets // invoked more than once with a cahced result. if (locationMarker){ return; } // Log that this is the initial position. console.log( "Initial Position Found" ); // Add a marker to the map using the position. locationMarker = addMarker( position.coords.latitude, position.coords.longitude, "Initial Position" ); }, function( error ){ console.log( "Something went wrong: ", error ); }, { timeout: (5 * 1000), maximumAge: (1000 * 60 * 15), enableHighAccuracy: true } ); // Now tha twe have asked for the position of the user, // let's watch the position to see if it updates. This // can happen if the user physically moves, of if more // accurate location information has been found (ex. // GPS vs. IP address). // // NOTE: This acts much like the native setInterval(), // invoking the given callback a number of times to // monitor the position. As such, it returns a "timer ID" // that can be used to later stop the monitoring. var positionTimer = navigator.geolocation.watchPosition( function( position ){ // Log that a newer, perhaps more accurate // position has been found. console.log( "Newer Position Found" ); // Set the new position of the existing marker. updateMarker( locationMarker, position.coords.latitude, position.coords.longitude, "Updated / Accurate Position" ); } ); // If the position hasn't updated within 5 minutes, stop // monitoring the position for changes. setTimeout( function(){ // Clear the position watcher. navigator.geolocation.clearWatch( positionTimer ); }, (1000 * 60 * 5) ); } </script> </body> </html>

As you can see, this code makes use of both the getCurrentPosition() and the watchPosition() methods. In a desktop browser, it is likely that the getCurrentPosition() method is the only one that will hold any value; however, on a mobile device like the iPhone, the watchPosition() method will allow us to tap into the GPS-based location information.

When the browser goes to find the user's location, it cannot do so without the user's expressed consent. As such, when the browser first tries to invoke the navigator.geolocation API, the user is presented with a confirmation form:

Once the user confirms that it is OK for the browser to use the current location, the getCurrentPosition() callback can be invoked:

As you can see here, Firefox has obtained my location - with about a mile or two accuracy - and has plotted the point on the Google map. Only the getCurrentPosition() callback gets invoked because the position of my browser will not change (or become more accurate over time).

While I had originally thought that only Firefox supported this geolocation API, some quick testing confirmed that this, in fact, also worked on Google Chrome, Opera, and Safari. However, while Safari supports the API, it failed to find my location:

Mobile Safari, on the other hand, worked perfectly. And, not only did it get the initial position using getCurrentPosition(), it was the only browser I tested that could find a further, more accurate position through GPS and the watchPosition() method:

If you look at the mobile console, you'll see that after the initial position was determined, the GPS functionality continued to invoke our watchPosition() callback periodically, updating my location marker as the position of my phone changed:

This is some pretty cool stuff. At first, I was somewhat turned off by the fact that the user needed to approve the browser's access to the location information; however, with the prevalence of mobile apps that now do this, I don't think that this will be any cause for concern.

Tweet This Great article by @BenNadel - Geocoding A User's Location Using Javascript's GeoLocation API Woot woot — you rock the party that rocks the body!







