Recently, I had the opportunity to work on a local business mapping application that required a visitor's location in order to display a list of local businesses. The project was expected to support a wide range of desktop and mobile browsers and while I was anxious to try out the HTML5 Geolocation API, I knew I'd need to fall back to IP-based geolocation. Combining both techniques turned out to be a bit tricky so I thought I'd share my approach and some code.
Before implementing HTML5 Geolocation, be aware that it doesn't happen silently. The W3C's specification includes very specific language aimed at protecting visitor privacy:
User agents must not send location information to Web sites without the express permission of the user. User agents must acquire permission through a user interface, unless they have prearranged trust relationships with users...
As you would expect, browsers implement the interface for obtaining permission differently and they offer different sets of location disclosure preferences. Some examples:
Unfortunately, browsers handle the visitor response to location sharing prompts (or the lack thereof) differently as well. I found it was very important to account for these differences and thoroughly test use cases for each browser.
Geolocation Support, Timeouts, and Fallback
The basic strategy for obtaining a visitor's location is simple:
- Check for Geolocation Support. None? Fallback.
- Read the Visitor's Location. Failed? Timed Out? Fallback.
- Store the Location for Future Use.
You can check for HTML5 geolocation support in the browser like this:
Now for the tricky part. 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. It's constructed like this:
This function looks straightforward but there are several caveats. First, not all browsers respect the timeout parameter consistently (looking at you, 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. To protect against these conditions, you can write your own timeout like this:
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 you can use to determine what went wrong. Here's an example of an error handler using a switch case statement:
The Position object contains a timestamp and a Coordinates object that provides a minimal set of location attributes:
|heading||double||null||Degrees (0° = North)|
|speed||double||null||Meters per Second|
Again, browser implementations vary. If you're stationary, heading and speed may be null, NaN, 0 or some combination depending on browser support. If your application just needed a raw lat/lng then you can set up your success handler like this and call it a day:
Wait, What About an Address?
So latitude and longitude are great for adding a marker to a Google map or calling in an air strike but what if you were hoping to get something more personal, like an address? Well, the HTML5 Geolocation Specification doesn't include addresses (yet) but you can easily get one through a reverse geocoding service. Google's reverse geocoding web service is an obvious choice; just watch out for the usage limits and terms of service.