Using Geolocation in the Browser and with Hosted Services

Christian Heilman | December 15, 2010

 

If there is one thing that changed immensely in the last few years then it is that our users are not sitting in their own house in front of a stationary box any longer but are more and more using their systems on the go.

Smartphones, laptops, notebooks and sub-notebooks make it easy for us to be mobile and still do our jobs and 3G sticks and tethering over mobiles allow us to be online almost everywhere. Therefore it makes sense to give users information about where they are and what is around them. Match that with a sense of gaming and you have a success like Foursquare without much technical effort.

Ways to determine the geographical location of a user

There are a few ways to find out where a user is - a tried and true approach that yields OK results, a newer way supported in HTML5 capable browsers and checking the location of the user in social networks and location broker systems. In this article we will only cover the first two ways. The reason is that there are lots and lots of services to use - Twitter, Facebook, Google Places, Foursquare, Gowalla - and all of them have their own ways of giving you access.

Explaining all of them would take too long.

Going oldschool - reading the user's IP number

The oldest way to determine a user's geographical location is to check the IP number and use a service to turn the IP into a location. These services like MaxMind, IP info DB and GeoIP and bundled with Maps APIs are used in a lot of ways - the most common ones being fraud protection and localized ad display. If you find that ads on a web site talk about the location that you are currently in, that is what was used. Google also switches the interface automatically to the area that you are in right now which can be amazingly annoying. For example I've just been to Jordan and had a right-to-left Google to deal with.

Determining the location of an IP is dead easy - MaxMind offers a service and a prepackaged JavaScript to use.

<div id="loc"></div>
<script src="http://j.maxmind.com/app/geoip.js"></script>
<script>
var geodata = '<ul>' +
  '<li>Country Code: ' + geoip_country_code() + '</li>' +
  '<li>Country Name: ' + geoip_country_name() + '</li>' +
  '<li>City: ' + geoip_city() + '</li>' +
  '<li>Region: ' + geoip_region() + '</li>' +
  '<li>Region Name: ' + geoip_region_name() + '</li>' +
  '<li>Latitude: ' + geoip_latitude() + '</li>' +
  '<li>Longitude: ' + geoip_longitude() + '</li>' +
  '<li>Post code: ' + geoip_postal_code() + '</li>' +
  '</ul>';
document.getElementById('loc').innerHTML = geodata;
</script>

In my case, the result is the following:

  • Country Code: GB
  • Country Name: United Kingdom
  • City: London
  • Region: H9
  • Region Name: London, City of
  • Latitude: 51.5002
  • Longitude: -0.1262
  • Post code:

The issue with IP lookup is accuracy. Depending on the connection your IP could be miles away from you in the data center that connects you to the web. Users with access to a world-wide VPN can also easily spoof the IP by going through a server on the network (this allows you to watch BBC whilst you are in the US and Hulu whilst being in the UK, too, so geographical blocking of services is pretty much pointless). You can check the difference between your location and the location of your IP in Mozilla, Opera, Chrome and Safari by going to this site:

In my case the difference is 5 miles. Not too bad but if you really want to place me then this is not quite the ticket. It is also very dependent on the quality of the database you use - some IPs don't result in any location.

The bigger concern about IP lookup is that it is creepy - I did not give my consent to you placing me physically on the planet and if I were a non-technical user of the web I could easily be spooked out by you telling me where I am located. Therefore it is nicer to ask the user for permission beforehand.

Enter the W3C geolocation API

If you want higher accuracy and the ability to give the end user the choice of telling you their location, you can use the W3C geolocation API. This one retrieves the current location from a few datasets, depending on what is available: The GPS location, the nearest mobile mast or the location of the wireless network you are connected to.

It is supported by Opera, Safari, Safari Webkit, Firefox and other, new and mobile browsers.

It is an incredibly easy API to use. All you need to do is check the getCurrentPosition method of the navigator.geolocation object and you get back the latitude and longitude:

navigator.geolocation.getCurrentPosition(function(position){
    var lat = position.coords.latitude;
    var lon = position.coords.longitude;
    alert('peekaboo I see you at' + lat + ',' + lon);
});

Security is built in as browsers ask for permission of the user to get their location. In Firefox this looks like this:

The returned object coords has a few properties you can use:

latitude: The latitude of the location

longitude: The longitude of the location

altitude: The altitude in meters

accuracy: An indicator how accurate the location is in meters

altitudeAccuracy: An indicator how accurate the altitude is in meters

heading: The heading of the user calculated by the difference of the last locations in degrees (0 to 360)

speed: The speed of the user calculated by the difference of the last locations in meters per second

Not all of these are available to you on a computer as they are intended for mobile devices.

The earlier code example is dangerous as it assumes the browser has geolocation. Therefore it is safer to check for the existence before accessing the object. Furthermore, it is important to think about the error case. Here is the way to use geolocation in a safe manner:

if(navigator.geolocation){
  var container = document.getElementById('geoinfo');
  navigator.geolocation.getCurrentPosition(
    function(position){
      var out = '<ul>'+
        '<li><span>Latitude:</span>' + 
             position.coords.latitude + '</li>' +
        '<li><span>Longitude:</span>' + 
             position.coords.longitude + '</li>' +
        '<li><span>Altitude:</span>' + 
             position.coords.altitude + '</li>' +
        '<li><span>Accuracy:</span>' + 
             position.coords.accuracy + '</li>' +
        '<li><span>Altitude Accuracy:</span>' +
             position.coords.altitudeAccuracy + '</li>' +
        '<li><span>Heading:</span>' + 
             position.coords.heading + '</li>' +
        '<li><span>Speed:</span>' + 
             position.coords.speed + '</li>' +
      '</ul>';
      container.innerHTML = out;
    
    },
    function(error){
      var errormessages = [
        'Permission denied',
        'Position not available',
        'Time out'
      ];
      container.innerHTML = '<li>Error: ' + 
                            errormessages[error.code-1] + 
                            '</li>';
    },
    {
      enableHighAccuracy: true,
      maximumAge: 10000,
      timeout:10000
    }
  );
}

The getCurrentPosition() method takes three parameters: a success case function that retrieves the position object, an error case function that retrieves the error object and a JSON object that allows you to set the accuracy, the maximum age of the location and a timeout.

As lookups can take some time, it makes sense to define a period of time after which the browser should give up.

As getting the location over GPS is heavy on the battery a lot of devices, you should cache the data for some time, which is why we need the maximumAge property. You can set maximumAge to 'Infinity' and a timeout of 0 to force the browser to tell you a cached location to fall back to. That can be handy when there is no GPS available.

The error object has a few properties you can play with - the error.code which is 1 for permission denied (as in the user said no - they don't want to share their location), 2 when the position could not be determined and 3 when there was a timeout. There is also a predefined error.message and the constants error.TIMEOUT, error.POSITION_UNAVAILABLE and error.PERMISSION_DENIED. Using an array of messages as shown above allows you to add your own flavour.

Where getCurrentPosition() is a one-off lookup, you can also have the browser pull constantly for updates of the location using the watchPosition() method. This one is similar to getCurrentPosition() and works much like setTimeOut in a browser. You give your watch an ID and clear it when you want to stop monitoring:

// making the browser watch the position constantly...
var breitling = navigator.geolocation.watchPosition(
  function(position){...},
  function(error){...},
  {
    enableHighAccuracy:true,
    maximumAge:10000 
  }
);
// stopping the monitoring
navigator.geolocation.clearWatch(breitling);

Be aware that a constantly running watchPosition() can very quickly drain the battery of the mobile phone!

Enhancing the geographical data

Having a user's location as latitude and longitude and some more information is nice, but it doesn't mean much for mere mortals who are not versed in the way of geo technologies.

The normal way to make it obvious what you are talking about when it comes to geography is to show the location on a map but what if that is not an option? In order to turn a certain location into something comprehensible you'd need human language, like the names of cities, neighborhoods, countries and states. It gets even more useful when you have the hierarchy to go with it.

This is where geographical databases come in. Geonames is probably the biggest and most open one out there - as it is a Wiki. This also means a few issues, namely false positives and wrong data (something that is part of the nature of a Wiki, really). It also is more focused on US locations than the world.

Another very interesting and free resource is Yahoo's GeoPlanet which is even downloadable as a dataset. It has locations in various languages, world-wide coverage and even does disambiguation for you. Disambiguation is necessary as you don't want to have "Jack London" or "Paris Hilton" be recognized as a location on the planet.

If you want to have a look at what GeoPlanet has to offer, check out the interface I built on top of it which allows you to find yourself and the locations around you. Check the parents, siblings and children of the area you are in and you will find that GeoPlanet does not only contain political and legal boundaries and names but also colloquial terms like "the west end" for the main tourist area of London. You can try out the GeoPlanet Explorer and you can also install it for yourself by getting the source from GitHub:

Turning a latitude and longitude into a place is done by a process called reverse geocoding. The main issue with this process is not the technology, but the copyright. Most of this information is owned by people who make maps and technology companies pay for that information. Map providers also offer that service when you show the result on their maps. Another free service that allows you to do that is Yahoo's PlaceFinder. Again, to show you the depth of the data inside it I built a PlaceFinder interfacefor it and the source is available on GitHub:

There is an immense amount of geographical datasets out in the world. The problem is that all of them are defined differently. There is Yahoo's WOEID, Geonames' IDs, ISO codes, IATA flight codes and many more. To help you build solutions that use all or any of them there is the Yahoo concordance API which translates between the different formats. To see concordance in action you can check the GeoSetta demo:

You can use this for example to convert the user's location to a Where On Earth ID to get the hierarchy of the area from GeoPlanet and convert the ID to a Geonames ID to use their Wikipedia lookup. The around you demo shows how this is done. The source is available on GitHub:

YQLGeo - Geolocation in the browser made easy

To make it easier for myself and also the world, I put together a small JavaScript library that does all the things we talked about in this article. It is called YQLGeo and is available as open source on GitHub and you can try it out here.

Using YQLGeo is dead easy. You have one method:

yqlgeo.get(what,callback)

what is the thing you want to analyze - this could be the URL to a web document, a text, an IP, a pair of latitude and longitude information or "visitor" to detect the geographic location of the current visitor.

callback is the callback method that will be called when there was a successful retrieval of information. The data comes back as an object - if there was an error retrieving the information the data will be wrapped in an error property.

Say for example you want to know where Paris, France is all you need to do is:

yqlgeo.get('paris,fr',function(o){
  alert(o.place.name+' ('+
    o.place.centroid.latitude+','+
    o.place.centroid.longitude+
  ')');
})

If you want to get the current location of the visitor, you send the string 'visitor' as the first parameter:

yqlgeo.get('visitor',function(o){
  if(o.error){
    alert('No location found for user :('); // some IPs are not in the DB :(
  } else {
    alert(o.place.name + ',' + o.place.country.content +  
          ' (' + o.place.centroid.latitude + ',' +
                 o.place.centroid.longitude + ')'
          );
  }
});

The script then asks the user for their location and uses the W3C geolocation API in geo-enabled browsers and falls back to looking up the IP when they say no or older browsers are used.

Summary

That's all you need to know to get you on the way to building location aware applications helping your users to find out about things around them and how to find and use their location.

We've talked about finding your user by IP and its shortcomings (IP might be a few miles off the real location, users can fake IPs with VPNs and it is just creepy not to ask the user for permission to locate them) and the W3C way of finding the user (which is less creepy as it asks for permission and much more accurate).

We also talked about systems that allow you to turn a latitude and longitude into a human understandable format and find the hierarchy it is part of (which city belongs to which country, continent and so on) to enhance the experience of your end users.

Now it is time for you to bring this knowledge to good use, so go and play!

For example instead of asking them to enter all their address data in a form why not just ask them for their location and pre-fill things like city and country for them? Using YQL Geo this should be very easy to achieve - go for it.

 

About the Author

Christian Heilmann grew up in Germany and, after a year working for the red cross, spent a year as a radio producer. From 1997 onwards he worked for several agencies in Munich as a web developer. In 2000 he moved to the States to work for Etoys and, after the .com crash, he moved to the UK where he lead the web development department at Agilisys. In April 2006 he joined Yahoo! UK as a web developer and moved on to be the Lead Developer Evangelist for the Yahoo Developer Network. In December 2010 he moved on to Mozilla as Principal Developer Evangelist for HTML5 and the Open Web. He publishes an almost daily blog at http://wait-till-i.com and runs an article repository at http://icant.co.uk. He also authored Beginning JavaScript with DOM Scripting and Ajax: From Novice to Professional.

Find Christian on: