Using the Geolocation API

by Rob Larsen

The following is probably the last long-form article you’ll see here for a while. I’m in full on book mode for the next couple of months, so I’m not expecting to be writing a ton here for the foreseeable future.

Anyway, the following is actually inspired by the kind of work we’re going to be doing for the book. I’m not actually doing any code-heavy writing for the book, so I wanted to get my hands dirty with this sort of content. It’s fun.

As an aside, I’ll have another (short) post about the book shortly. I’ve got a full allotment of co-authors and I’d like to give them, and the project, a little shine before I turn into a writing hermit.

And now… Geolocation


One of the most powerful aspects of mobile web app development is the ability to blur the line between the real world and the world on the screen. Allowing users to interact with physical places in novel ways is driving startups across the world and is infiltrating some of the most popular sites and applications on the web. Facebook, Foursquare, Twitter, Google+ and countless other services have built the idea of location into the core of their applications. You too can do the same in your mobile web app by taking advantage of the well-supported Geolocation API.

Whether it’s interaction with your own location based services or with a third party API, like the Google Maps API in use in this recipe, the journey begins with getting the user’s latitude and longitude.

In this article you’ll learn how to:

  • Use the W3C Geolocation API to get a user’s latitude and longitude
  • Smoothly handle devices without Geolocation support, providing a reasonable fallback for older devices
  • Use the Google Maps API to place a marker indicating the user’s location, labeled with a friendly place name

The Basics of the Geolocation API

Before we dive into the heart of the example let’s quickly look at the Geolocation API.

The elevator pitch is to the point- the W3C’s Geolocation API allows developers to retrieve the geographical location of a device. It became a Candidate Recommendation, the level at which the W3C deems features and functionality pretty much settled, in September of 2010 and already has support across a variety of devices and browsers. At the present time it’s supported all the major smartphone browsers and even on the desktop it’s supported by all major browsers except Internet Explorer 6, 7, 8 and Safari 3.2 and 4.0.

As a note, the Geolocation API was heavily influenced by the analogous functionality provided by the Google Gears plugin. This is why you often see the mothballed Google Gears plugin referenced as a fallback in many geolocation examples.

The API itself is straightforward. It provides a navigator.geolocation object which in turn provides two methods (watchPosition and getCurrentPosition) which allow the browser to query the device’s location through the use of location information servers. If you’re getting a location for use in a search or in a check-in, then getCurrentPosition is the method you want to use as it’s designed for a single location lookup. If you’re tracking a user’s location over time, then watchPosition is the way to go since it’s designed to be used over a longer period of time.

Location information is pulled from a variety of sources including IP address, device GPS, Wi-Fi and Bluetooth MAC address, RFID, or Wi-Fi connection location. The different level of precision inherent in these several methods is exposed by the API as an accuracy property.

Now that we’ve taken a look at the Geolocation API, let’s walk through our code in depth.

Getting Started with the Geolocation API

While the Geolocation API is straightforward, getting it up and running smoothly in the real world is a little bit tricky. Accounting for a successful result is one thing, making sure there’s a decent response for browsers without geolocation capability or in other instances where geolocation isn’t available is another.

Our example will touch on ways to minimize these issues and will illustrate the basics of a successful request.

Testing for the Geolocation object and Querying the User’s Location

The first thing you’ll need to do when working with Geolocation is to test whether or not it’s actually available in the browser. This is done by testing against the presence of the navigator.geolocation object. As you’ll see in the following code sample, this is a simple if…else block with a call to the navigator.getCurrentPosition() method when the object is present and a fallback when it’s not available.

The method getCurrentPosition() takes three arguments:

  1. the function to run on a successful location request
  2. the function to run as when the request fails
  3. a PositionOptions object containing other optional configuration objects.

In our case we’re passing in two named functions, success and failure, and an optional timeout of five seconds, which will keep things moving if something goes awry with the request.

Listing 1 Testing for the presence of the navigator.geolocation object

if (navigator.geolocation){  // does the geolocation object exist?             
  navigator.geolocation.getCurrentPosition( 
    success, 
    failure, 
    {timeout:5000} 
  );          
} else {
  failure();  
}      

In addition to the timeout seen in the previous example the PositionOptions object accepts two other options- enableHighAccuracy and maximumAge. enableHighAccuracy indicates that you would like receive the best possible results at the potential cost of speed or battery performance. maximumAge sets a limit, in milliseconds, for the age of a cached position object. The browser caches recent position location responses. Setting maximumAge to 0 will immediately force the browsers to try to obtain a new location.

Handling a successful geolocation request

The first function we’ll look at it is our success function. You can see it in Listin 1.2

In our example we’re using the Google Maps API to display a marker with the user’s current location.

The function accepts a single data argument. This argument is automatically passed into the function by the geolocation API. This is object is defined in the specification to contain two properties coords and timestamp. timestamp is, as expected, a timestamp indicating the age of the position information. For this example we’re most interested in the coords object which contains a latitude/longitude pair indicating the user’s position.

Moving on from the single argument, you’ll see several Google Maps specific variables.

The first, GM,represents a simple technique to speed up JavaScript. By creating a local representation of the google.maps object we save lookups to the global space. In general, local variables are faster. This is especially important with mobile which devices don’t have the fastest JavaScript engines.

Every little bit helps.

The most important piece, from a geolocation perspective, is the use of two properties, data.coords.latitude and data.coords.longitude, to build a new Google Maps LatLng object. The LatLng object is a core component of Google Maps. At its core it’s a latitude/longitude pair enhanced with methods and properties used throughout the API. To create one in our example you simply pass it the two properties of the data.coords object. We store that in our position variable.

We now have our user’s location, ready to place on the map.

The next section we’re using the Google Maps Geocoder to get a friendly label for the user’s location. Geocoding works in two ways. Normal geocoding means you pass the service an address string and it will return a series of geographical results. In our case we’re doing reverse geocoding, which means we pass the service a latitude/longitude pair and the service returns whatever it knows about the location.

Listing 2 Successfully handling a geolocation request

var success = function( data ){ //the data object, passed into success
  var GM = google.maps,
      mapOptions = {
        zoom: 12,
        center:  defaultPosition,
        mapTypeId:  GM.MapTypeId.ROADMAP
      },
      map = new GM.Map(  document.getElementById('map'), mapOptions),
      position = new GM.LatLng( 
        data.coords.latitude, //accessing the coords property
        data.coords.longitude 
      ),      
      niceAddress = "Your location",
      geocoder = new GM.Geocoder();
      geocoder.geocode( { 'latLng' : position  }, 
        function( results, status ) {
          if ( status == GM.GeocoderStatus.OK ) {
            if (results[0]) {
              niceAddress = results[0].formatted_address;
            }
          } 
          var infowindow = new GM.InfoWindow({
            map: map,
            position: position,
            content: niceAddress
          });
        });
      map.setCenter(position);
    }

Handling a Geolocation Failure

Our failure function handles two negative situations. If the user doesn’t have a geolocation enabled browser or if there’s an error in the geolocation lookup, this function is ready to step in and save the day. The failure function can be seen in Listing 3

You’ll see the setup is similar to the success function with a Google Maps object being created with some smart defaults.

The major difference is in the way we get the latitude and longitude for the map. Instead of getting the coordinates from a geolocation response we create a simple form to allow the user to enter their location. Inside the formResponse function we use then use the Google Maps Geocoding service to get a latitude and longitude pair corresponding to the location in the form submission.

Additionally we use the geolocation error response, if it exists, to build out a slightly more useful error message. If a browser supports geolocation and has some issue with the location request it should return an error response as the single argument to the provided callback function.

Listing 3 The failure Function

var failure = function( error ){ //The potential error response
  var  GM = google.maps,
       mapOptions = {
         zoom: 12,
         center:  defaultPosition,
         mapTypeId:  GM.MapTypeId.ROADMAP
       },
       map = new GM.Map(  document.getElementById('map'), mapOptions),
       formResponse = function(e){
         var geocoder = new GM.Geocoder(),
             position = defaultPosition,
             niceAddress =  "Sorry We Couldn't Find Your Location";
         geocoder.geocode(
           { 'address':  document.getElementById("location").value }, 
           function( results, status ) {
             if ( status ==  GM.GeocoderStatus.OK ) {
               if (results[0]) {
                 niceAddress =  results[0].formatted_address;
                 position = new GM.LatLng( 
                   results[0].geometry.location.lat(),
                   results[0].geometry.location.lng() 
                 )
               }
             } 
           var options = {
             map : map,
             position :  position,
             content :  niceAddress
            },
           infowindow = new  google.maps.InfoWindow(options);
           map.setCenter(options.position);
           document.getElementById("geocode").style.display="none";
         }
       )
     return false;
   }
   var  fallback = document.createElement("form");
   fallback.id="geocode";
   if ( error ) { 
     switch(error.code) {//Error Handling based on error.code   
      //HANDLE ERRORS//
   }      
 }  
 fallback.innerHTML  = "<label for='location'>Eneter Your Location" + 
  "<input  type='text' id='location' /></label><input type='submit'  />";
 fallback.onsubmit  = formResponse;
 document.getElementById("main").appendChild(  fallback );
};

The error response contains a code object indicating the type of error and a human readable message string that’s defined to be used in debugging or for error logs. There are four potential values for the error.code. These can be seen in Table 1:

Table 1 Possible error response codes

Code

Name

Definition

0

UNKNOWN_ERROR

The location lookup failed due to an undefined error

1

PERMISSION_DENIED

The location lookup failed because the application does not have permission to use the Geolocation API.

2

POSITION_UNAVAILABLE

The position of the device could not be determined.

3

TIMEOUT

The location lookup took longer than the length of time defined in

Putting it all together

Listing 4 shows the completed function. In it we’ve wrapped our two methods into a larger function called loadMap. Doing so allows us to streamline the code by creating a single set of defaults for the map and to encapsulate all of the functionality under a single function so as to keep the global namespace as neat as possible.

Listing 4 The Completed Function

var loadMap = function(){
  var GM = google.maps,
      defaultPosition = new GM.LatLng(42, -71),
      mapOptions = {
      zoom: 12,
      center: defaultPosition,
      mapTypeId: GM.MapTypeId.ROADMAP},
    map = new GM.Map(
      document.getElementById('map'),
      mapOptions
    ),
    success = function( data ){
      var position = new GM.LatLng(
        data.coords.latitude,
        data.coords.longitude
      ),
         niceAddress = 'Your location',
         geocoder = new GM.Geocoder();
      geocoder.geocode({
        'latLng': position },
         function( results, status ) {
           if ( status == GM.GeocoderStatus.OK ){
             if (results[0]) {
               niceAddress = results[0].formatted_address;
             }
           }
           var infowindow = new GM.InfoWindow({
             map: map,
             position: position,
             content: niceAddress
           });
         }
       );
      map.setCenter( position );
    },
    failure = function( error ){
      var formResponse = function(){
        var geocoder = new GM.Geocoder(),
            position = defaultPosition,
            niceAddress = 'Sorry We Couldn't Find Your Location';
        geocoder.geocode({
     'address':document.getElementById('location').value
    },
          function( results, status ) {
            if ( status == GM.GeocoderStatus.OK ){
              if (results[0]) {
                niceAddress = results[0].formatted_address;
                position = new GM.LatLng(
                  results[0].geometry.location.lat(),
                  results[0].geometry.location.lng()
                )
               }
             }
             var options = {
                map: map,
                position: position,
                content: niceAddress
              },
              infowindow = new google.maps.InfoWindow(options);
              map.setCenter(options.position);
              document.getElementById('geocode').style.display='none';
           }
         )
       return false;
     }
     var fallback = document.createElement('form');
     fallback.id='geocode';
      if ( error ) {
       switch(error.code) {
         case error.PERMISSION_DENIED:
     	    fallback.innerHTML += "<p>You chose not share geolocation data. Please, use the form below. </p>" ;  
         break;  
	 case error.POSITION_UNAVAILABLE:
            fallback.innerHTML += "<p>Sorry, we couldn't determine your location. Please, use the form below. </p>" ;
         break;  
         case error.TIMEOUT: 
            fallback.innerHTML += "<p>Sorry, the location request time out. Please, use the form below. </p>" ;
         break;  
         default: 
            fallback.innerHTML += "<p>Sorry, there was an error. Please use the form below. </p>" ;
         break;
       }  	
    }
    fallback.innerHTML += "<label for='location'>Eneter Your Location <input type='text' id='location' /></label><input type='submit' />";
    fallback.onsubmit = formResponse;
    document.getElementById("main").appendChild( fallback );
  };
  if (navigator.geolocation){
    navigator.geolocation.getCurrentPosition( success, failure, {timeout:5000} ) ;
  } else {
    failure();
  }
}

Summary

With that we’ve walked through the basics of the geolocation API. While our example utilizes the Google Maps API, any exploration of location based services can be based on the same pattern. Use the navigator.geolocation object where available, and then design in a simple fallback for non-supporting browsers and devices.

code used in the geolocation sample

In Cased You Missed It… Isobar Code Standards Updated and Posted to Github

by Rob Larsen

So, as you probably noticed I did a bunch of writing for IBM earlier this year. Because of that I’ve got about a million draft posts here that I’ve got sitting in various states of completion. I’m going to work my way through them over the next few weeks. You’ve been warned (in a good way, I hope.)

Anyway, this happened a couple of weeks ago, but it’s still interesting (and important to me personally) so I thought I’d point it out again, in case you missed it the first time.

Over at Isobar we recently relaunched our Front-end Code Standards & Best Practices document and posted it up on Github to solicit feedback and changes from the community.

This document has made a strong impression in the year plus since Paul announced it and because of that we take it pretty seriously. To that end we wanted to update the design a little bit (just because it needed it) and have revisited a lot of the content to make sure it remains fresh and still reflects the current trends of web development.

So, check it out and let us know if there’s anything we can improve.

My Latest IBM Article is Live: “An introduction to Ajax”

by Rob Larsen

An introduction to Ajax.

Get a technical introduction to Ajax programming, and discover the core JavaScript code and popular library implementations. This article presents a brief history of the technology, then outlines the technical basics of Ajax interactions using core JavaScript coding as well as three popular JavaScript libraries.

My New IBM Article is Live: “HTML5, CSS3, and related technologies”

by Rob Larsen

In it, I make a small argument for using HTML5 as a generic marketing term. I’ll be following up on that a little later, here on this blog.

Anyway, check it out. The resources section is worth it alone.

HTML5, CSS3, and related technologies.

Web standard development and marketing

It’s a great time to be a web developer. After a long period of hibernation, the standards bodies and browser vendors have been extremely busy over the past few years, generating a torrent of exciting technology. Developers are greedily seizing on this work, producing demos and full-blown applications at a steady pace. Fed by this activity and further boosted by the growth of their standards-capable mobile browsers, companies like Google and Apple are using these new standards to market their products and services. The wider press is also seizing on this wave and pushing standards hype well beyond the normal circle of web developers and browser vendors.

This volume of discussion has obvious benefits, of course. People getting excited about web standards is a positive development for everyone in the industry. From that perspective, the persistent use of blanket terms, especially HTML5, as a sort of brand shorthand for “emerging web technology” is a useful shortcut. It allows nontechnical people to grasp—in a generalized way—the exciting work being done in the standards space right now.

Interestingly, even the W3C has gotten into the act, using HTML5 and its associated logo (see Figure 1) to publicize the “web platform.”

Read the rest…

My Latest developerWorks Article is Live: ECMA-262, 5th Edition

by Rob Larsen

ECMA-262, 5th Edition.

The standard published under the dry title ECMA-262, 5th Edition hereafter referred to as ES5, is the latest version of the ECMAScript specification. ECMAScript is the standard upon which JavaScript—the single most important language on the web today—is built. Because the language also serves as the basis for Adobe ActionScript in addition to other flavors, it’s fair to say that the ECMAScript standard is central to the present and future of interactivity on the web.

Published in December 2009, ES5 represents an incremental improvement in the language, arrived at through a lengthy, and occasionally contentious, process. Still, whatever friction there might have been in the development of the specification, the end result contains some excellent enhancements to the language. This article outlines the long history of the specification’s development, then steps through many of its important new features and concepts.

How To Make a Web Site the Modern Way. Part 14: Formatting, Shorthand, Resets and Organization

by Rob Larsen

We continue with our examination of CSS with some real basics- formatting, writing rules, organization and the like. Nothing groundbreaking, but the basics are important in any endeavor, so here they are.

Formatting

During development I format my CSS with selectors on one line and then each property on its own line. The declarations are indented 4 spaces. I like this style because my interest is always in the properties, not the selectors. I can find any selector I need with CTRL+F and then I can easily scan down the list of properties to do my business.

It looks like this:
Read the rest of this entry »

HTML5: What You Should Be Using Right Now

by Rob Larsen

Continuing our examination of HTML5, this time we’ll take a slightly deeper look at the state of HTML5 support in browsers. We’ll step through several major features, examine the technical hurdles in place, identify the level of browser support and finally provide some recommendations on whether or not to pull the trigger with that shiny new feature.

New Semantic Elements

HTML5 has added many new semantic elements. These both codify existing, common patterns and offer new, meaningful ways mark up your content. So, for example, going forward, instead of using <div id="header"> we will now just use <header>. Some other examples (with definitions culled from the specification) include:

section
The section element represents a generic document or application section. A section, in this context, is a thematic grouping of content, typically with a heading.

Examples of sections would be chapters, the various tabbed pages in a tabbed dialog box, or the numbered sections of a thesis. A Web site’s home page could be split into sections for an introduction, news items, contact information.

footer
The footer element represents a footer for its nearest ancestor sectioning content or sectioning root element. A footer typically contains information about its section such as who wrote it, links to related documents, copyright data, and the like.
nav
The nav element represents a section of a page that links to other pages or to parts within the page: a section with navigation links.
article
The article element represents a component of a page that consists of a self-contained composition in a document, page, application, or site and that is intended to be independently distributable or reusable, e.g. in syndication. This could be a forum post, a magazine or newspaper article, a blog entry, a user-submitted comment, an interactive widget or gadget, or any other independent item of content.

That’s far from a complete list, but it should serve to give you the flavor of what’s available.

Support

This is a curious case. If, by “support” we mean “doesn’t break anything” then the support here is broad. Using the HTML5 Shiv code snippet or the full blown Modernizr library every browser that matters supports the HTML5 elements. If you’re expecting user agents to do something interesting with your new elements, then the support level is much lower.
Read the rest of this entry »

How To Make a Web Site the Modern Way. Part 13: The Cascade/CSS Specificity

by Rob Larsen

One of the most important things in CSS is understanding the way rules are inherited and applied in the browser. This is one of those things that many developers “get” intuitively but don’t necessarily understand at a granular level.

There’s actually an algorithm, so if you’re stumped, you can actually count it out. It works like this:

Read the rest of this entry »

How To Make a Web Site the Modern Way. Part 12: Cascading Style Sheets

by Rob Larsen

After a long break, I’m finally back with the long-awaited CSS portion of this little series.

In this article I’ll go over some core concepts. Next post I’ll outline one poorly understood, but vital part of CSS. More posts full (yes, full) of tips, tricks and best practices will follow.

Core concepts

CSS is a style sheet language used to determine the formatting of an HTML document (although it could be used to style other XML documents like SVG and XUL.) Before we had CSS (and before it was widely adopted) all of this presentation information was embedded in the document, either in the form of attributes like width or bgcolor or in the form of purely presentational tags like font. Combined with the abuse of the table tag to create complicated layouts, the landscape for layout and design on the web was an unmanageable mess.

CSS fixed all that. Using separate style sheets for an entire site and leveraging semantic markup, and identifiers like ids (for unique page elements) and classes (for multiple, like elements) a developer can apply styles to an entire site while updating a single, cacheable file.
Read the rest of this entry »

I’m Presenting on CSS in October

by Rob Larsen

I haven’t posted about this yet. I’m an incredible idiot. I’m presenting at the Boston PHP group in October. My presentation is a couple of weeks before Steve Krug. No pressure.

I hope to see you there :)

Learn CSS (with me)

Is CSS still a mystery to you? Do you find yourself editing your styles over and over justo get them to display correctly in IE and Firefox? Have you created a powerful application, and want it to look nice and clean? Do you want to take your knowledge of CSS and Design to the next level?

In this presentation Rob Larsen will step through the basics of Cascading Style Sheets (CSS,) the visual language of the Web. Starting with the most fundamental concepts and finishing with concrete examples illustrating common patterns, this presentation will serve as a launching point for those new to CSS and will strengthen the understanding of the core principles for developer or designer more experienced with CSS.

In this event you will learn:

  • Basics of CSS
  • Design & Layout
  • Web Standards
  • Rich Visual Behaviors
  • CSS1, CSS2, CSS3
  • Frameworks, Abstractions, etc.
  • Dealing with cross browser support
  • Separation of style, content and behavior
  • Testing
  • Tips & Tricks

If you still fumble with CSS and want to take your experience to the next level, then this event is for you.

About the presenter:
Rob Larsen has more than 11 years of experience building and designing web sites and web applications. Currently he’s a Consultant at Isobar, working for some of the world’s largest brands.

Prior to joining Isobar, Rob was the Principal Presentation Engineer at Cramer. At Cramer, Rob and his team produced standards-based, accessible and SEO-friendly sites and rich media applications. Before that, Rob worked for several years as a consultant for clients like Compete, Duracell, Gillette, Boston’s Museum of Science, PC Connection, RSA Security, State Street Corporation and Webex.