In this chapter, we will cover:
Getting your location
Handling cross-browser geolocation
Displaying a map based on your geolocation
Realtime positioning
DeviceOrientation
event
Using geolocation with foursquare
Among all the HTML5 classes, one that is most closely related to mobile development has to be Device Access.
Here is the official description of Device Access on the W3C HTML5 Movement site (http://www.w3.org/html/logo/):
Beginning with the Geolocation API, Web Applications can present rich, device-aware features, and experiences. Incredible device access innovations are being developed and implemented, from audio/video input access to microphones and cameras, to local data such as contacts and events, and even tilt orientation.
You can find the description and logo at: http://www.w3.org/html/logo/ #the-technology.
Location-based social networks like foursquare have had a profound impact on the way business works and how people mobilize. Groupon's new location-based offer, if it's released, may fundamentally change consumer behavior and the way retail businesses function. Google Maps uses realtime geolocation and GPRS to help people and vehicles navigate. There will be more and more exciting innovations built on top of this Device Access technology.
In this chapter, we will study geolocation API and DeviceOrientation API, address cross-browser issues, and see how we can use Device Access together with popular location-based services.
Target browsers: Android, iOS, webOS, Opera, Firefox
Using the geolocation API, we could return values like latitude, longitude, and accuracy of your current location:
Latitude and longitude: These attributes are geographic coordinates and are specified in decimal degrees
Accuracy: Denotes the accuracy level of the latitude and longitude coordinates and is specified in meters
Let's create an HTML document and get the latitude and longitude together with the accuracy. First, let's create a new HTML file, and name it ch05r01.html
.
Enter the following code into the HTML document:
<!doctype html> <html> <head> <title>Mobile Cookbook</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <div id="main"> <div id="someElm"> </div> </div> <script src="http://code.jquery.com/jquery-1.5.2.min.js"></script> <script> function getLocation() { navigator.geolocation.getCurrentPosition(showInfo); } function showInfo(position) { var latitude = position.coords.latitude; var longitude = position.coords.longitude; var accuracy = position.coords.accuracy; $('#someElm').html('latitude: '+latitude+'<br />longitude: '+longitude+'<br />accuracy: '+accuracy); } getLocation(); </script> </body> </html>
When you first render it, you will be prompted with a message as follows:
Geolocation support is opt-in. No browser will automatically send the physical location of your device to the server. Instead, it will ask for your permission before executing the program of sending the location of your device back and forth. The browser can remember your preference to prevent it from popping up again from the same site.
Now press the button that allows you to share the location. You will then get the location data displayed on the screen as follows:
navigator
is an object that is no stranger to JavaScript programmers. It's commonly used for user agent detection: navigator.userAgent
.
geolocation
is a new property on the navigator
object: navigator.geolocation
.
getCurrentPosition
is a method of navigator.geolocation
. In this example, we execute the function showInfo
as the first argument:
navigator.geolocation.getCurrentPosition(showInfo);
In the showInfo
function, we return three values from position
parameter, that is, latitude, longitude
, and accuracy:
var latitude = position.coords.latitude; var longitude = position.coords.longitude; var accuracy = position.coords.accuracy;
Target browsers: cross-browser
Geolocation doesn't work on all mobile browsers, and even for those browsers that do support it, they could have an API that's different from the standard. iOS and Android use the standard. Browsers that are known to have a different API include Blackberry, Nokia, and Palm. Luckily, we have a mobile-centric geolocation polyfill—geo-location-javascript. It has non-standard Blackberry and webOS tricks to help normalize different API behaviors.
Download the resources that come with this chapter and create a js
folder. Put geo.js
into the js
folder. Now create an HTML document named ch05r02.html
.
Enter the following code into the HTML document:
<!doctype html> <html> <head> <title>Mobile Cookbook</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="http://code.google.com/apis/gears/gears_init.js" type="text/javascript" charset="utf-8"></script> <script src="js/geo.js" type="text/javascript" charset="utf-8"></script> </head> <body> <div id="main"> <div id="someElm"> </div> </div> <script src="http://code.jquery.com/jquery-1.5.2.min.js"></script> <script> if(geo_position_js.init()){ geo_position_js.getCurrentPosition(success_callback,error_callback,{enableHighAccuracy:true,options:5000}); } else{ $('#someElm').html("Functionality not available"); } function success_callback(p) { $('#someElm').html('latitude: '+p.coords.latitude+'<br />longitude: '+p.coords.longitude+'<br />accuracy: '+p.coords.accuracy); } function error_callback(p) { $('#someElm').html('error='+p.message); } </script> </body> </html>
Test it in Opera and you should be able to see the result as follows:
At the top of the HTML document, we link to gears_init.js
. If the browser doesn't have a geolocation API supported by default, but has Gears installed, the Gears API may return the geolocation data. For browsers that have the geolocation API, but just in a different method, the second script geo.js
will be used to normalize the API.
If geo_position_js.init()
returns true, it means in one way or anther we are able to get the geolocation data. In this case we will proceed to the next step. Instead of using navigator.geolocation.getCurrentPosition
, we use geo_position_js.getCurrentPosition
as the method:
geo_position_js.getCurrentPosition(showInfo,error_callback,{enableHighAccuracy:true,options:5000});
Here is an additional resource that will help you to get geolocation info.
YQL Geo Library provides an alternative approach, an IP address-based geolocation. It is a lightweight library that is tied to Yahoo services. It can:
Get the geographical location from a text
Get the location information from lat/lon
Get all the geographical locations from a certain URL
Get the place from an IP number
Target browsers: cross-browser
The Google Maps API V3 has been designed to load fast and work well on mobile devices. In particular, we have focused on development for advanced mobile devices such as the iPhone and handsets running the Android operating system. Mobile devices have smaller screen sizes than typical browsers on the desktop. As well, they often have particular behavior specific to those devices, such as "pinch-to-zoom" on the iPhone.
Let's create a map that displays on your mobile device. First, let's create an HTML document named ch05r03.html
.
Enter the following code:
<!doctype html> <html> <head> <title>Mobile Cookbook</title> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script> <script src="http://code.google.com/apis/gears/gears_init.js"></script> <script src="js/geo.js"></script> <style> html { height: auto; } body { height: auto; margin: 0; padding: 0; } #map_canvas { height: auto; position: absolute; bottom:0; left:0; right:0; top:0; } </style> </head> <body> <div id="map_canvas"></div> <script src="http://code.jquery.com/jquery-1.5.2.min.js"></script> <script> var initialLocation; var siberia = new google.maps.LatLng(60, 105); var newyork = new google.maps.LatLng(40.69847032728747, -73.9514422416687); var browserSupportFlag = new Boolean(); var map; var infowindow = new google.maps.InfoWindow(); function initialize() { var myOptions = { zoom: 12, mapTypeId: google.maps.MapTypeId.ROADMAP }; map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); if(geo_position_js.init()){ browserSupportFlag = true; geo_position_js.getCurrentPosition(function(position) { initialLocation = new google.maps.LatLng(position.coords.latitude,position.coords.longitude); contentString = "you are here"; map.setCenter(initialLocation); infowindow.setContent(contentString); infowindow.setPosition(initialLocation); infowindow.open(map); }); } } function detectBrowser() { var useragent = navigator.userAgent; var mapdiv = document.getElementById("map_canvas"); if (useragent.indexOf('iPhone') != -1 || useragent.indexOf('Android') != -1) { mapdiv.style.width = '100%'; mapdiv.style.height = '100%'; } else { mapdiv.style.width = '600px'; mapdiv.style.height = '800px'; } } detectBrowser(); initialize(); </script> </body> </html>
Now let's break down the code and see what each section does:
The iPhone has the pinch-to-zoom feature and Google Maps API V3 has special handling for this event. So you can set the following metatag and this will make sure that the users cannot resize the iPhone. Android devices running software version 1.5 (Cupcake) also support these parameters:
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
Set the<div>
containing your map to have width and height attributes of 100 percent:
mapdiv.style.width = '100%'; mapdiv.style.height = '100%';
You can detect iPhone and Android devices by inspecting the navigator.userAgent
property within the DOM:
function detectBrowser() { var useragent = navigator.userAgent; var mapdiv = document.getElementById("map_canvas"); if (useragent.indexOf('iPhone') != -1 || useragent.indexOf('Android') != -1 ) { mapdiv.style.width = '100%'; mapdiv.style.height = '100%'; } else { mapdiv.style.width = '600px'; mapdiv.style.height = '800px'; } }
Specifying the Sensor Parameter, applications that determine the user's location using a sensor must pass sensor=true
when loading the Maps API JavaScript.
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
Use of the Google Maps API requires that you indicate whether your application is using a sensor (such as a GPS locator) to determine the user's location. This is especially important for mobile devices. Applications must pass a required sensor parameter to the <script>
tag when including the Maps API JavaScript code, indicating whether or not your application is using a sensor device.
We parse in the geolocation coordinates to the map API's LatLng
method:
initialLocation = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
You can learn more about Google Maps JavaScript API V3 at the official documentation page at:
http://code.google.com/apis/maps/documentation/javascript/
Mobile tuts has an excellent article about mobile geolocation called HTML5 Apps: Positioning with Geolocation. You can read it at:
HTML5 Apps: Positioning with Geolocation
http://mobile.tutsplus.com/tutorials/mobile-web-apps/html5-geolocation/
Target browsers: cross-browser
Apart from getCurrentPosition
, geolocation API has another method named watchPosition
. It performs two important actions when called:
It returns a value that identifies a watch operation.
It asynchronously starts the watch operation.
Enter the following code into the document:
<!doctype html> <html> <head> <title>Mobile Cookbook</title> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <style> html { height: auto; } body { height: auto; margin: 0; padding: 0; } #map_canvas { height: auto; position: absolute; bottom:0; left:0; right:0; top:0; } </style> </head> <body> <div id="map_canvas"></div> <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script> <script src="http://code.jquery.com/jquery-1.5.2.min.js"></script> <script> var watchProcess = null; var initialLocation; var map; var infowindow = new google.maps.InfoWindow(); var myOptions = { zoom: 12, mapTypeId: google.maps.MapTypeId.ROADMAP }; map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); navigator.geolocation.getCurrentPosition(function(position) { updatePos(position.coords.latitude,position.coords.longitude,position.coords.accuracy); }); initiate_watchlocation(); function initiate_watchlocation() { if (watchProcess == null) { watchProcess = navigator.geolocation.watchPosition(handle_geolocation_query, handle_errors); } } function stop_watchlocation() { if (watchProcess != null) { navigator.geolocation.clearWatch(watchProcess); watchProcess = null; } } locationdisplaying, in real timefunction handle_errors(error) { switch(error.code) { case error.PERMISSION_DENIED: alert("user did not share geolocation data"); break; case error.POSITION_UNAVAILABLE: alert("could not detect current position"); break; case error.TIMEOUT: alert("retrieving position timedout"); break; default: alert("unknown error"); break; } } function handle_geolocation_query(position) { updatePos(position.coords.latitude,position.coords.longitude,position.coords.accuracy); } function updatePos(lat,long,acc) { var text = "Latitude: " + lat + "<br/>" + "Longitude: " + long + "<br/>" + "Accuracy: " + acc + "m<br/>"; initialLocation = new google.maps.LatLng(lat,long); contentString = text; map.setCenter(initialLocation); infowindow.setContent(contentString); infowindow.setPosition(initialLocation); infowindow.open(map); } </script> </body> </html>
Here is how it will be rendered:
The following function will initiate the location watch:
function initiate_watchlocation() { if (watchProcess == null) { watchProcess = navigator.geolocation.watchPosition(handle_geolocation_query, handle_errors); } }
navigator.geolocation.watchPosition
will either return success or error upon execution. In the success function, you can parse the latitude and longitude:
navigator.geolocation.watchPosition(handle_geolocation_query, handle_errors);
When the position is being watched, the handle_geolocation_query
is used to get the current position and parse to the update position function:
function handle_geolocation_query(position) { updatePos(position.coords.latitude,position.coords.longitude,position.coords.accuracy); }
The DeviceOrientation
event is an important aspect of Device Access. It includes device motion events and device orientation events. Unfortunately, these events are currently supported in iOS only.
Enter the following code into the document:
<!doctype html> <html> <head> <title>Mobile Cookbook</title> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script> <style> #no { display: none; } #ball { width: 20px; height: 20px; border-radius: 10px; background-color: red; position:absolute; top: 0px; left: 0px; } </style> </head> <body> <div id="content"> <h1>Move the Ball</h1> <div id="yes"> <p>Move your device to move the ball.</p> <div id="ball"></div> </div> <div id="no"> Your browser does not support Device Orientation and Motion API. Try this sample with iPhone, iPod or iPad with iOS 4.2+.</div> </div> <script> // Position Variables var x = 0; var y = 0; // Speed - Velocity var vx = 0; var vy = 0; // Acceleration var ax = 0; var ay = 0; var delay = 10; var vMultiplier = 0.01; if (window.DeviceMotionEvent==undefined) { document.getElementById("no").style.display="block"; document.getElementById("yes").style.display="none"; } else { window.ondevicemotion = function(event) { ax = event.accelerationIncludingGravity.x; ay = event.accelerationIncludingGravity.y; } setInterval(function() { DeviceOrientation eventusingvy = vy + -(ay); vx = vx + ax; var ball = document.getElementById("ball"); y = parseInt(y + vy * vMultiplier); x = parseInt(x + vx * vMultiplier); if (x<0) { x = 0; vx = 0; } if (y<0) { y = 0; vy = 0; } if (x>document.documentElement.clientWidth-20) { x = document.documentElement.clientWidth-20; vx = 0; } if (x>document.documentElement.clientWidth-20) { x = document.documentElement.clientWidth-20; vx = 0; } if (y>document.documentElement.clientHeight-20) { y = document.documentElement.clientHeight-20; vy = 0; } ball.style.top = y + "px"; ball.style.left = x + "px"; }, delay); } </script> </body> </html>
This code was made by Maximiliano Firtman (http://www.mobilexweb.com/blog/safari-ios-accelerometer-websockets-html5). In the example, we used accelerationIncludingGravity
. It returns the value of the total acceleration of the device, which includes the user acceleration and the gravity.
The three values, x, y, z, represent the acceleration in m/s^2 for each axis:
window.ondevicemotion = function(event) { event.accelerationIncludingGravity.x event.accelerationIncludingGravity.y event.accelerationIncludingGravity.z }
Here is a table that shows the current support for DeviceOrientationEvent
and DeviceMotionEvent:
Properties |
Description |
Returned values |
Class |
Support |
---|---|---|---|---|
|
The acceleration that the user is giving to the device. |
x, y, z (in m/s^2) |
|
iPhone 4 / iPod Touch 4G |
|
The total acceleration of the device, which includes the user acceleration and the gravity. |
x, y, z (in m/s^2) |
|
iPhone3 / iPod Touch 3G |
|
The interval in milliseconds since the last device motion event. |
milliseconds |
|
iPhone3 / iPod Touch 3G |
|
The rotation rate of the device. |
alpha, beta, and gamma (values are between 0 and 360) |
|
iPhone 4 / iPod Touch 4G |
|
The degrees the device frame is rotated around its z-axis. |
Values are between 0 and 360. |
|
iPhone 4 / iPod Touch 4G |
|
The degrees the device frame is rotated around its x-axis. |
Values are between -180 and 180. |
|
iPhone 4 / iPod Touch 4G |
|
The degrees the device frame is rotated around its y-axis. |
Values are between -90 and 90. |
|
iPhone 4 / iPod Touch 4G |
Target browsers: cross-browser
In recent years, the location-based social networking website foursquare has become more and more popular. It affected the way many business work and consumers behave. Users "check-in" at places using a mobile website, mobile app, or SMS.
Third-party developers have released many libraries for accessing the foursquare API from various programming languages. One of those is Marelle. It's based on jQuery and written in coffeescript. Don't worry, that's just JavaScript.
Go to the GitHub page of Marelle (http://praized.github.com/marelle/) and download the latest version. There are two examples, one is login and another is check-in.
Here is what the login script looks like:
// Supply your foursquare client id var FSQUARE_CLIENT_ID = 'FOURSQUARE_CLIENT_ID'; // on DOM ready... $(function() { // setup with your key and a callback function which // receives the Marelle Object ( "M" in this example ) $.Marelle( FSQUARE_CLIENT_ID ).done( function( M ){ // grab an authentication promise var authpromise = M.authenticateVisitor(); // handle logged-in visitor var authsuccess = function(visitor){ M.signoutButton( document.body ); console.log(visitor) /* I think the single entry point is through the visitor */ venuepromise = visitor.getVenues() // venuepromise.then etc..etc... }; // handle non visitor var authfailure = function() { M.signinButton( document.body ); }; // wait for promise to resolve authpromise.then(authsuccess,authfailure) }).fail(function(){ consoloe.log('Marelle could not be loaded.') }); });
First we trigger Marelle initialization $.Marelle(clientID)
and it returns a promise:
$.Marelle( FSQUARE_CLIENT_ID )
Then we grab an authentication promise using $.Marelle.authenticateVisitor():
$.Marelle( FSQUARE_CLIENT_ID ).done( function( M ){ var authpromise = M.authenticateVisitor(); });
Depending on the result of the authentication, authpromise.then()
is used to either execute authsuccess
or authfailure:
authpromise.then(authsuccess,authfailure)
If the authentication is successful, it appends a "disconnect" button to the provided selector:
M.signoutButton( document.body );
One can return a list of recommended venues, add or search venues:
venuepromise = visitor.getVenues()
If the authentication is unsuccessful, it appends a "Connect" button to the provided selector:
M.signinButton( document.body );