{"id":8148,"date":"2012-01-02T14:03:05","date_gmt":"2012-01-02T19:03:05","guid":{"rendered":"https:\/\/htmlcssjs.wpengine.com\/?p=8148"},"modified":"2014-10-08T16:36:07","modified_gmt":"2014-10-08T20:36:07","slug":"using-the-geolocation-api","status":"publish","type":"post","link":"https:\/\/htmlcssjavascript.com\/javascript\/using-the-geolocation-api\/","title":{"rendered":"Using the Geolocation API"},"content":{"rendered":"<p>The following is probably the last long-form article you&#8217;ll see here for a while. I&#8217;m in full on <a href=\"https:\/\/htmlcssjs.wpengine.com\/web\/im-writing-a-book\/\">book mode<\/a> for the next couple of months, so I&#8217;m not expecting to be writing a ton here for the foreseeable future.<\/p>\n<p>Anyway, the following is actually inspired by the kind of work we&#8217;re going to be doing for the book. I&#8217;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&#8217;s fun. <\/p>\n<p>As an aside, I&#8217;ll have another (short) post about the book shortly. I&#8217;ve got a full allotment of co-authors and I&#8217;d like to give them, and the project, a little shine before I turn into a writing hermit.<\/p>\n<p>And now&#8230; Geolocation<\/p>\n<hr \/>\n<p>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 <a href=\"http:\/\/dev.w3.org\/geo\/api\/spec-source.html\">Geolocation API<\/a>. <\/p>\n<p>Whether it\u2019s 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\u2019s latitude and longitude. <\/p>\n<p>In this article  you\u2019ll learn how to:<\/p>\n<ul>\n<li>Use the W3C Geolocation API to get a user\u2019s  latitude and longitude<\/li>\n<li>Smoothly handle devices without Geolocation  support, providing a reasonable fallback for older devices<\/li>\n<li>Use the Google Maps API to place a marker  indicating the user\u2019s location, labeled with a friendly place name<\/li>\n<\/ul>\n<h2>The Basics of the Geolocation API<\/h2>\n<p>Before we dive into the heart of the example let\u2019s quickly look  at the Geolocation API.<\/p>\n<p>The elevator pitch is to the point- the W3C\u2019s 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\u2019s supported  all the major smartphone browsers and even on the desktop it\u2019s supported by all  major browsers except Internet Explorer 6, 7, 8 and Safari 3.2 and 4.0. <\/p>\n<p>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. <\/p>\n<p>The API itself is straightforward.  It provides a <code>navigator.geolocation<\/code>  object which in turn provides two methods (<code>watchPosition<\/code>  and <code>getCurrentPosition<\/code>) which allow the browser to query the  device\u2019s location through the use of location information servers.  If you\u2019re getting a location for use in a  search or in a check-in, then <code>getCurrentPosition<\/code>  is the method you want to use as it\u2019s designed for a single  location lookup. If you\u2019re tracking a user\u2019s location over time, then <code>watchPosition<\/code> is the way  to go since it\u2019s designed to be used over a longer period of time. <\/p>\n<p>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 <code>accuracy<\/code>  property. <\/p>\n<p>Now that we\u2019ve taken a look at the Geolocation API, let\u2019s walk through  our code in depth.<\/p>\n<h2>Getting Started with the Geolocation API<\/h2>\n<p> 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\u2019s a decent response for  browsers without geolocation capability or in other instances where geolocation  isn\u2019t available is another. <\/p>\n<p>Our example will touch on ways to minimize these issues and will  illustrate the basics of a successful request. <\/p>\n<h3>Testing for the Geolocation object and Querying  the User\u2019s Location<\/h3>\n<p>The first thing you\u2019ll need to do when working with Geolocation  is to test whether or not it\u2019s actually available in the browser. This is done  by testing against the presence of the <code>navigator.geolocation<\/code> object. As you\u2019ll  see in the following code sample, this is a simple <code>if\u2026else<\/code>  block with a call to the <code>navigator.getCurrentPosition()<\/code>  method when the object is present and a fallback when it\u2019s not available.<\/p>\n<p>The method <code>getCurrentPosition()<\/code>  takes three arguments:<\/p>\n<ol>\n<li>the function to run on a successful location  request<\/li>\n<li>the function to run as when the request fails<\/li>\n<li>a <code>PositionOptions<\/code> object containing other  optional configuration objects. <\/li>\n<\/ol>\n<p>In our case we\u2019re passing in two named functions, <code>success<\/code> and <code>failure<\/code>, and an optional  timeout of five seconds, which will keep things moving if something goes awry  with the request. <\/p>\n<p><strong>Listing 1 Testing for  the presence of the navigator.geolocation object<\/strong><\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nif (navigator.geolocation){  \/\/ does the geolocation object exist?             \r\n  navigator.geolocation.getCurrentPosition( \r\n    success, \r\n    failure, \r\n    {timeout:5000} \r\n  );          \r\n} else {\r\n  failure();  \r\n}      \r\n<\/pre>\n<p>  In addition to the timeout  seen in the previous example the <code>PositionOptions<\/code>  object accepts two other options- <code>enableHighAccuracy<\/code>  and <code>maximumAge<\/code>. <code>enableHighAccuracy<\/code>  indicates that you would like receive the best possible results at the  potential cost of speed or battery performance. <code>maximumAge<\/code> sets a limit, in  milliseconds, for the age of a cached position object. The browser caches  recent position location responses. Setting <code>maximumAge<\/code> to 0 will immediately force  the browsers to try to obtain a new location. <\/p>\n<h3>Handling a successful geolocation request<\/h3>\n<p>The first function we\u2019ll look at it is our success function. You can  see it in Listin 1.2<\/p>\n<p>In our example we\u2019re using the Google Maps API to display a  marker with the user\u2019s current location. <\/p>\n<p> The function accepts a single <code>data<\/code> 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 <code>coords<\/code> and <code>timestamp<\/code>.  <code>timestamp<\/code> is, as expected, a timestamp indicating the age of the position  information. For this example we\u2019re most interested in the <code>coords<\/code> object which  contains a latitude\/longitude pair indicating the user\u2019s position. <\/p>\n<p> Moving on from the single  argument, you\u2019ll see several Google Maps specific variables. <\/p>\n<p>The first, <code>GM<\/code>,represents  a simple technique to speed up JavaScript. By creating a local representation  of the <code>google.maps<\/code> object we save lookups to the global space. In general,  local variables are faster. This is especially important with mobile which devices don\u2019t have the fastest JavaScript engines. <\/p>\n<p ><em>Every little bit helps.<\/em> <\/p>\n<p>The most important piece, from a geolocation perspective, is the  use of two properties, <code>data.coords.latitude<\/code> and <code>data.coords.longitude<\/code>, to build  a new Google Maps LatLng object. The LatLng object is a core component of  Google Maps. At its core it\u2019s 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. <\/p>\n<p> We now have our user\u2019s location, ready to place on the map. <\/p>\n<p>  The next section we\u2019re using the Google Maps Geocoder to get a  friendly label for the user\u2019s 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\u2019re doing reverse geocoding,  which means we pass the service a latitude\/longitude pair and the service  returns whatever it knows about the location. <\/p>\n<p><strong>Listing 2 Successfully handling a geolocation  request<\/strong><\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nvar success = function( data ){ \/\/the data object, passed into success\r\n  var GM = google.maps,\r\n      mapOptions = {\r\n        zoom: 12,\r\n        center:  defaultPosition,\r\n        mapTypeId:  GM.MapTypeId.ROADMAP\r\n      },\r\n      map = new GM.Map(  document.getElementById('map'), mapOptions),\r\n      position = new GM.LatLng( \r\n        data.coords.latitude, \/\/accessing the coords property\r\n        data.coords.longitude \r\n      ),      \r\n      niceAddress = &amp;amp;quot;Your location&amp;amp;quot;,\r\n      geocoder = new GM.Geocoder();\r\n      geocoder.geocode( { 'latLng' : position  }, \r\n        function( results, status ) {\r\n          if ( status == GM.GeocoderStatus.OK ) {\r\n            if (results&#x5B;0]) {\r\n              niceAddress = results&#x5B;0].formatted_address;\r\n            }\r\n          } \r\n          var infowindow = new GM.InfoWindow({\r\n            map: map,\r\n            position: position,\r\n            content: niceAddress\r\n          });\r\n        });\r\n      map.setCenter(position);\r\n    }\r\n  \r\n  <\/pre>\n<h3>Handling a Geolocation Failure<\/h3>\n<p>  Our <code>failure<\/code>  function handles two negative situations. If the user doesn\u2019t have a  geolocation enabled browser or if there\u2019s an error in the geolocation lookup,  this function is ready to step in and save the day. The <code>failure<\/code>  function can be seen in Listing 3<\/p>\n<p>You\u2019ll see the setup is similar to the success  function with a Google Maps object being created with some smart defaults. <\/p>\n<p>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 <code>formResponse<\/code>  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. <\/p>\n<p> 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. <\/p>\n<p><strong>Listing 3 The failure Function<\/strong><\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nvar failure = function( error ){ \/\/The potential error response\r\n  var  GM = google.maps,\r\n       mapOptions = {\r\n         zoom: 12,\r\n         center:  defaultPosition,\r\n         mapTypeId:  GM.MapTypeId.ROADMAP\r\n       },\r\n       map = new GM.Map(  document.getElementById('map'), mapOptions),\r\n       formResponse = function(e){\r\n         var geocoder = new GM.Geocoder(),\r\n             position = defaultPosition,\r\n             niceAddress =  &amp;amp;quot;Sorry We Couldn't Find Your Location&amp;amp;quot;;\r\n         geocoder.geocode(\r\n           { 'address':  document.getElementById(&amp;amp;quot;location&amp;amp;quot;).value }, \r\n           function( results, status ) {\r\n             if ( status ==  GM.GeocoderStatus.OK ) {\r\n               if (results&#x5B;0]) {\r\n                 niceAddress =  results&#x5B;0].formatted_address;\r\n                 position = new GM.LatLng( \r\n                   results&#x5B;0].geometry.location.lat(),\r\n                   results&#x5B;0].geometry.location.lng() \r\n                 )\r\n               }\r\n             } \r\n           var options = {\r\n             map : map,\r\n             position :  position,\r\n             content :  niceAddress\r\n            },\r\n           infowindow = new  google.maps.InfoWindow(options);\r\n           map.setCenter(options.position);\r\n           document.getElementById(&amp;amp;quot;geocode&amp;amp;quot;).style.display=&amp;amp;quot;none&amp;amp;quot;;\r\n         }\r\n       )\r\n     return false;\r\n   }\r\n   var  fallback = document.createElement(&amp;amp;quot;form&amp;amp;quot;);\r\n   fallback.id=&amp;amp;quot;geocode&amp;amp;quot;;\r\n   if ( error ) { \r\n     switch(error.code) {\/\/Error Handling based on error.code   \r\n      \/\/HANDLE ERRORS\/\/\r\n   }      \r\n }  \r\n fallback.innerHTML  = &amp;amp;quot;&amp;amp;lt;label for='location'&amp;amp;gt;Eneter Your Location&amp;amp;quot; + \r\n  &amp;amp;quot;&amp;amp;lt;input  type='text' id='location' \/&amp;amp;gt;&amp;amp;lt;\/label&amp;amp;gt;&amp;amp;lt;input type='submit'  \/&amp;amp;gt;&amp;amp;quot;;\r\n fallback.onsubmit  = formResponse;\r\n document.getElementById(&amp;amp;quot;main&amp;amp;quot;).appendChild(  fallback );\r\n};\r\n<\/pre>\n<p>The error response contains a code  object indicating the type of error and a human readable message string that\u2019s  defined to be used in debugging or for error logs. There are four potential  values for the <code>error.code<\/code>. These can be seen in Table 1:<\/p>\n<p><strong>Table 1 Possible error response  codes<\/strong><\/p>\n<table class=\"dataTable\">\n<tr>\n<td width=\"62\" valign=\"top\">\n<div>\n      Code <\/div>\n<\/td>\n<td width=\"231\" valign=\"top\">\n<div>\n<p>Name <\/p>\n<\/p><\/div>\n<\/td>\n<td width=\"230\" valign=\"top\">\n<div>\n<p>Definition<\/p>\n<\/p><\/div>\n<\/td>\n<\/tr>\n<tr>\n<td width=\"62\" valign=\"top\">\n<p>0<\/p>\n<\/td>\n<td width=\"231\" valign=\"top\">\n<p>UNKNOWN_ERROR <\/p>\n<\/td>\n<td width=\"230\" valign=\"top\">\n<p>The location lookup failed due to an undefined error<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td width=\"62\" valign=\"top\">\n<p>1<\/p>\n<\/td>\n<td width=\"231\" valign=\"top\">\n<p>PERMISSION_DENIED <\/p>\n<\/td>\n<td width=\"230\" valign=\"top\">\n<p>The location lookup failed because the application does    not have permission to use the Geolocation API.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td width=\"62\" valign=\"top\">\n<p>2<\/p>\n<\/td>\n<td width=\"231\" valign=\"top\">\n<p>POSITION_UNAVAILABLE <\/p>\n<\/td>\n<td width=\"230\" valign=\"top\">\n<p>The position of the device could not be determined. <\/p>\n<\/td>\n<\/tr>\n<tr>\n<td width=\"62\" valign=\"top\">\n<p>3<\/p>\n<\/td>\n<td width=\"231\" valign=\"top\">\n<p>TIMEOUT<\/p>\n<\/td>\n<td width=\"230\" valign=\"top\">\n<p>The location lookup took longer than the length of time    defined in <\/p>\n<\/td>\n<\/tr>\n<\/table>\n<h3>Putting it all together<\/h3>\n<p> Listing 4 shows the completed function. In it we\u2019ve wrapped  our two methods into a larger function called <code>loadMap<\/code>.  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. <\/p>\n<p><strong>Listing 4 The Completed Function<\/strong><\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nvar loadMap = function(){\r\n  var GM = google.maps, \r\n      defaultPosition = new GM.LatLng(42, -71),\r\n      mapOptions = {\r\n      zoom: 12, \r\n      center: defaultPosition, \r\n      mapTypeId: GM.MapTypeId.ROADMAP},\r\n    map = new GM.Map( \r\n      document.getElementById('map'), \r\n      mapOptions\r\n    ), \r\n    success = function( data ){\r\n      var position = new GM.LatLng( \r\n        data.coords.latitude, \r\n        data.coords.longitude \r\n      ), \r\n         niceAddress = 'Your location',\r\n         geocoder = new GM.Geocoder();\r\n      geocoder.geocode({ \r\n        'latLng': position },\r\n         function( results, status ) {\r\n           if ( status == GM.GeocoderStatus.OK ){\r\n             if (results&#x5B;0]) {\r\n               niceAddress = results&#x5B;0].formatted_address; \r\n             }\r\n           }\r\n           var infowindow = new GM.InfoWindow({\r\n             map: map, \r\n             position: position, \r\n             content: niceAddress\r\n           });\r\n         }\r\n       );\r\n      map.setCenter( position );\r\n    }, \r\n    failure = function( error ){ \r\n      var formResponse = function(){ \r\n        var geocoder = new GM.Geocoder(),\r\n            position = defaultPosition, \r\n            niceAddress = 'Sorry We Couldn't Find Your Location';\r\n        geocoder.geocode({\r\n     'address':document.getElementById('location').value \r\n    }, \r\n          function( results, status ) {\r\n            if ( status == GM.GeocoderStatus.OK ){ \r\n              if (results&#x5B;0]) {\r\n                niceAddress = results&#x5B;0].formatted_address; \r\n                position = new GM.LatLng(\r\n                  results&#x5B;0].geometry.location.lat(),\r\n                  results&#x5B;0].geometry.location.lng() \r\n                )\r\n               } \r\n             }\r\n             var options = {\r\n                map: map, \r\n                position: position, \r\n                content: niceAddress\r\n              },\r\n              infowindow = new google.maps.InfoWindow(options);\r\n              map.setCenter(options.position);\r\n              document.getElementById('geocode').style.display='none';\r\n           }\r\n         )\r\n       return false; \r\n     } \r\n     var fallback = document.createElement('form');\r\n     fallback.id='geocode';\r\n      if ( error ) {\r\n       switch(error.code) {\r\n         case error.PERMISSION_DENIED:\r\n     \t    fallback.innerHTML += &amp;amp;quot;&amp;amp;lt;p&amp;amp;gt;You chose not share geolocation data. Please, use the form below. &amp;amp;lt;\/p&amp;amp;gt;&amp;amp;quot; ;  \r\n         break;  \r\n\t case error.POSITION_UNAVAILABLE:\r\n            fallback.innerHTML += &amp;amp;quot;&amp;amp;lt;p&amp;amp;gt;Sorry, we couldn't determine your location. Please, use the form below. &amp;amp;lt;\/p&amp;amp;gt;&amp;amp;quot; ;\r\n         break;  \r\n         case error.TIMEOUT: \r\n            fallback.innerHTML += &amp;amp;quot;&amp;amp;lt;p&amp;amp;gt;Sorry, the location request time out. Please, use the form below. &amp;amp;lt;\/p&amp;amp;gt;&amp;amp;quot; ;\r\n         break;  \r\n         default: \r\n            fallback.innerHTML += &amp;amp;quot;&amp;amp;lt;p&amp;amp;gt;Sorry, there was an error. Please use the form below. &amp;amp;lt;\/p&amp;amp;gt;&amp;amp;quot; ;\r\n         break;\r\n       }  \t\r\n    }\r\n    fallback.innerHTML += &amp;amp;quot;&amp;amp;lt;label for='location'&amp;amp;gt;Eneter Your Location &amp;amp;lt;input type='text' id='location' \/&amp;amp;gt;&amp;amp;lt;\/label&amp;amp;gt;&amp;amp;lt;input type='submit' \/&amp;amp;gt;&amp;amp;quot;;\r\n    fallback.onsubmit = formResponse;\r\n    document.getElementById(&amp;amp;quot;main&amp;amp;quot;).appendChild( fallback );\r\n  };\r\n  if (navigator.geolocation){\r\n    navigator.geolocation.getCurrentPosition( success, failure, {timeout:5000} ) ;\t\t\t\r\n  } else {\r\n    failure();\t\r\n  } \t\r\n}\r\n<\/pre>\n<h3>Summary<\/h3>\n<p>With that we\u2019ve 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 <code>navigator.geolocation<\/code>  object where available, and then design in a simple fallback for non-supporting  browsers and devices.  <\/p>\n<p><a href='https:\/\/htmlcssjs.wpengine.com\/wp-content\/uploads\/2011\/12\/geolocation.zip'>code used in the geolocation sample<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The following is probably the last long-form article you&#8217;ll see here for a while. I&#8217;m in full on book mode for the next couple of months, so I&#8217;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&#8217;re going to be [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[5],"tags":[8,10,22],"class_list":["post-8148","post","type-post","status-publish","format-standard","hentry","category-javascript","tag-google","tag-html5","tag-standards"],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p3uLY6-27q","jetpack_sharing_enabled":true,"jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/htmlcssjavascript.com\/wp-json\/wp\/v2\/posts\/8148","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/htmlcssjavascript.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/htmlcssjavascript.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/htmlcssjavascript.com\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/htmlcssjavascript.com\/wp-json\/wp\/v2\/comments?post=8148"}],"version-history":[{"count":0,"href":"https:\/\/htmlcssjavascript.com\/wp-json\/wp\/v2\/posts\/8148\/revisions"}],"wp:attachment":[{"href":"https:\/\/htmlcssjavascript.com\/wp-json\/wp\/v2\/media?parent=8148"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/htmlcssjavascript.com\/wp-json\/wp\/v2\/categories?post=8148"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/htmlcssjavascript.com\/wp-json\/wp\/v2\/tags?post=8148"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}