10. Location Awareness with the Geolocation API

For years, websites have used location information to provide enhanced user experiences, such as where the closest store is or events in your area. The location data has been gathered by using a browser’s IP address and matching it in a database or just asking the user for their location. With smartphones and built-in GPS, there is a significant increase in apps that are location-aware. With HTML5 and the Geolocation API, there is an easy and fairly reliable method by which websites and web applications can access a browser’s location. In this chapter, you will learn about the Geolocation API objects and methods in a series of recipes to retrieve the browser device’s location information for use in your application.

Geolocation Overview

The ability to identify the location information of a browser, whether laptop- or mobile-based, provides key information that can be used for a variety of functionality, including the following:

• Displaying the browser’s position on a map

• Displaying location-specific information or points of interest

• Adding location data to user contributions such as place reviews or photographs

Accessing a user’s location by correlating the IP address of the browser can be problematic because the database of IP addresses and locations must be extensive and well-maintained. The location information can also be vague, providing detail only down to a general area. It is not uncommon for sites that leverage location data to ask a user for their ZIP or postal code or full address to overcome the IP address location challenges. However, this information is tied to where the user may be at the time rather than where they may be in the future. The HTML5 Geolocation API provides built-in methods that can provide quite granular information.

With the Geolocation API, the browser is now able to tell you its location in the world via latitude and longitude values with a measure of accuracy. The degree of accuracy is based on several factors, and developers can influence the degree of accuracy. Now you may be wondering, what good is latitude and longitude if you do not know the latitude and longitude coordinates of the coffeehouse around the corner? For geolocation to be beneficial, the universal means of conveying a location must be at the “lowest common denominator,” and the coordinate system of latitude and longitude provides this. As you will see later in this chapter, there are several services from various providers that can consume latitude and longitude coordinates and provide additional information. And to reverse geocode (see the following sidebar), a set of coordinates is quite easy given the large geographic databases of providers such as Google.


Reverse geocoding, the opposite of geocoding that converts an address into a set of latitude and longitude coordinates, is the practice of converting a set of latitude and longitude coordinates into a physical address. Various services can provide this information. One of the most commonly used is the Google Maps JavaScript API V3 Services (http://code.google.com/apis/maps/documentation/javascript/services.html#ReverseGeocoding).


Browser Compatibility

The Geolocation API is still young, but given the value it holds, the API definition is being adopted rapidly by the various browsers. Table 10.1 lists the current browser support for the Geolocation API.

Table 10.1 Geolocation API Browser Availability

image

Where in the World: getCurrentPosition

The basic function of the Geolocation API is to find the current location of the browser in the world. The getCurrentPosition method provides this information to you in a JavaScript asynchronous call. It is important to note that the calls that determine location in JavaScript are asynchronous in nature. Most JavaScript is performed synchronously or in the main program flow. With asynchronous method calls, JavaScript performs the call in the background and then returns the results to a function when the process is complete. By having the API call as an asynchronous call, the query can be displayed to the user without blocking the processing of the rest of the page.

The getCurrentPosition method retrieves the current position for the browser and takes one required parameter (a success callback function name) and two optional parameters (an error callback function and a position options object):

getCurrentPosition (successCallback [, errorCallback] [, positionOptions])

The parameters of the getCurrentPosition include the following:

successCallback: The function to execute and pass the coordinates to

errorCallback: (Optional) The function to handle any errors that occurred

options: (Optional) An options object to handle how the position is retrieved

Since the call to getCurrentPosition is asynchronous, the method needs to be told which functions for success and potential failure will be executed when the method has completed. Let’s jump in and find your location now with a recipe.

Beginner Recipe: Determining Your Location with a Simple getCurrentPosition

In this recipe, the page will use the getCurrentPosition method with a success callback function to determine your current location and display the properties of the position object returned. Use these steps and the code in Listing 10.1 to create this recipe:

1. Create a blank HTML page with a div (called btnFindMe) and the Find Me button, which will call the findMe function when clicked.

2. Add the findMe function in a set of script tags with the following code to check for the Geolocation API, and then call the getCurrentPosition method:

if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(geoSuccess);
} else {
    document.getElementById('myLocation').innerHTML =
        "Geolocation API Not Supported";
}

3. Add the geoSuccess function that will handle the successful callback from the getCurrentPosition request.

4. Add a second div (called myLocation) to the HTML in which you will display the returned position information from the getCurrentPosition.

Listing 10.1 getCurrentPosition to Find Browser Location


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>10.1 Find Me</title>
<script>

// Initialize the page with other event listeners
function init() {
  var btnFindMe = document.getElementById('findMe'),
  btnFindMe.addEventListener('click',findMe,false);
}

// success callback function for getCurrentPosition
function geoSuccess(position) {

  // grab the position DOMTimeStamp for display
  var dateDisplay = new Date(position.timestamp);

  // get reference to result div
  var myLocationDiv = document.getElementById('myLocation'),

  // display the coords and timestamp object fields
  myLocationDiv.innerHTML = 'Lat: ' + position.coords.latitude + '<br>' +
    'Lng: ' + position.coords.longitude + '<br>' +
    'Accuracy: ' + position.coords.accuracy + '<br>' +
    'Altitude (opt): ' + position.coords.altitude + '<br>' +
    'Alt. Accuracy (opt): ' + position.coords.altitudeAccuracy + '<br>' +
    'Heading (opt): ' + position.coords.heading + '<br>' +
    'Speed (opt): ' + position.coords.speed + '<br>' +
    'Position DOMTimeStamp: ' + position.timestamp + '<br>' +
    'Time Date Stamp: ' + dateDisplay.toLocaleString();
}

// function called from button click to find position
function findMe() {
  var myLocationDiv = document.getElementById('myLocation'),
  // check for geolocation support
  if (navigator.geolocation) {
    // make asynchronous getCurrentPosition call
    navigator.geolocation.getCurrentPosition(geoSuccess);
    myLocationDiv.innerHTML = 'Retrieving your location.';
  } else {
    // geolocation not supported
    myLocationDiv.innerHTML = 'Geolocation API Not Supported';
  }
}

// Initialize the page on load
window.addEventListener('load',init,false);

</script>
</head>
<body>
<div id='btnFindMe'>
  <button id="findMe">Find Me</button>
</div>
<div id="myLocation"></div>
</body>
</html>


When you click the Find Me button, the findMe() function will be called. The first step in this function is to check whether the browser you are using supports the Geolocation API. This check is done by using the following code:

if (navigator.geolocation) {

If the navigator geolocation object is available, then you can perform the getCurrentPosition; otherwise, you can handle the lack of support of the API by displaying an appropriate message that the API is not supported. Upon successfully verifying the availability of the API, the getCurrentPosition method is called with the callback function name of geoSuccess. This will be the function performed when the getCurrentPosition completes.

When the getCurrentPosition method is called, the very first action that the browser will perform is to verify that the user has authorized the browser to provide this information to the page or prompt the user to do so. Depending on the browser being used, the message and options may be slightly different. For example, in Firefox, an authorization panel drops down from the top of the browser, allowing the user to share their location or not share and remember the selection for the site. In Safari, a dialog will appear that will confirm with the user to allow, disallow, and allow for the next 24 hours.

When the browser is authorized to retrieve your location, the browser will leverage WiFi and cellular network information if available to determine the location. This information will be passed to the success callback function as a position object. The position object holds properties of the location including the latitude and longitude (see Table 10.2).

Table 10.2 The Position Return Object

image

The position object data is divided into the coords object and a timestamp, in DOMTimeStamp format. Inside the geoSuccess function you can now display the various properties of the coords object, referencing each through the position root object. For example, to retrieve the latitude of the position, you use position.coords.latitude. Each browser will handle the optional fields differently; Figure 10.1 shows the output from Chrome 14.

Figure 10.1 Your location revealed in Chrome 14.

image

Location Privacy

Knowing the location of a browser, and thus the location of the person viewing the browser, can be considered to be private information. The method for allowing users to share their private location information is through an authorization action, as you saw in this first recipe. Until the user either allows or denies access to the location, the getCurrentPosition API call will be on hold. This is a key reason that this call is performed via an asynchronous method so that the rest of the page does not “block” waiting for the user authorization or reply.

You may be wondering at this point what happens if the user does not provide authorization for the information or the location information times out. This is where the error handler parameter of the getCurrentPosition comes into play and what you will look at in the next recipe.


Tip

As you use the Geolocation API, you may find that in some instances your page produces no result or returns a timeout error (as you will see in the next recipe). This will typically result from an error being present in your code in one of your callback functions. Since the position methods are asynchronous, these errors may or may not bubble up to your browser window based on the browser you are viewing. It can be helpful to use console.log() debugging in your callback functions to identify the issue.


Intermediate Recipe: Mapping a Location with getCurrentPosition

In this recipe, you will use the getCurrentPosition method to retrieve the location of the browser and map it on a Google map on the page. You will include in the recipe an error handler in case an error is returned from the getCurrentPosition method (which you will cause to happen as soon as you have the page working correctly).

Similar to the prior recipe, when the page loads, the user can click the Map Me button that will trigger the getCurrentPosition method. Once you receive the call, you will then use the latitude and longitude coordinates to create an instance of a Google map with a marker and an info window for the coordinates and city and state. The city and state comes from the Mozilla address object and will not be available in other browsers. If you want to show the corresponding physical address in other browsers, then you will need to use reverse geocoding, which you will see in another recipe in this chapter.

1. Leverage the previous recipe, and change the button to Map Me and the function called to mapMe.

2. Include the Google Maps JavaScript API V3 Overlay script tag (note that with V3 of the Google Maps kit, you no longer need a developer key):

<script src="http://maps.google.com/maps/api/js?sensor=false">

3. Modify the getCurrentPosition request to add the error handler function:

navigator.geolocation.getCurrentPosition(geoSuccess, geoErrorHandler);

4. Add the geoErrorHandler function geoErrorHandler(error), which will handle any errors that are returned by the getCurrentPosition request.

5. Update the HTML body div sections to mirror those in Listing 10.2 to have a container including a mapCanvas, the mapMe button, and myLocation.

Listing 10.2 Using getCurrentPosition to Map a Location


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>10.2 Map Me With Error Handling</title>
<style>
  #container {
    width:500px;
  }

  #mapCanvas {
    width:500px;
    height:300px;
    border-style:solid;
    border-width:2px;
    margin: 22px 0;
  }

  #btnMapMe {
    float:left;
  }

  #myLocation {
    float:right;
  }
</style>
<script src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script>

// Initialize the page
function init() {
  // Add the button click listener
  var btnMapMe = document.getElementById('mapMe'),
  btnMapMe.addEventListener('click',mapMe,false);
}
// success callback function for getCurrentPosition
function geoSuccess(position) {

  // get reference to result div
  var myLocationDiv = document.getElementById('myLocation'),

  // retrieve our lat and long coordinates
  var posLat = position.coords.latitude;
  var posLng = position.coords.longitude;
  var posAccuracy = position.coords.accuracy;

  // display the coords and timestamp object fields
  myLocationDiv.innerHTML = 'Lat: ' + posLat + ', Lng: ' + posLng + '<br>Accuracy: ' + posAccuracy;

  // create a google map latlng out of our coordinates
  var myLatlng = new google.maps.LatLng(posLat, posLng);

  // set our options for our map using our latlng as the center
  var myOptions = {
    zoom: 14,
    center: myLatlng,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  }

  // create our google map instance
  var map = new google.maps.Map(document.getElementById('mapCanvas'), myOptions);

  // add our marker for our location
  var marker = new google.maps.Marker({
    position: myLatlng,
    map: map
  });

  // create our info window text
  var infoText = '';
  infoText = posLat + ', ' + posLng + '<br>Accuracy: ' + posAccuracy;
  if (position.address) {
    infoText += '<br>' + position.address.city + ', ' + position.address.region;
  }

  // create the info window and set the text
  var infowindow = new google.maps.InfoWindow();
  infowindow.setContent(infoText);
  infowindow.open(map, marker);
}
// error handler for getCurrentPosition
function geoErrorHandler(error) {

  // initialize our error message
  var errMessage = 'ERROR: ';

  // based on the error code parameter set the message
  switch(error.code)
{
    case error.PERMISSION_DENIED:
      errMessage += 'User did not share geolocation data.';
      break;
    case error.POSITION_UNAVAILABLE:
      errMessage += 'Could not detect current position.';
      break;
    case error.TIMEOUT:
      errMessage += 'Retrieving position timed out.';
      break;
    default:
      errMessage += 'Unknown error.';
      break;
  }

  // display the error to the user
  document.getElementById('myLocation').innerHTML = errMessage;
}

// function called from button click to find position
function mapMe() {
  var myLocationDiv = document.getElementById('myLocation'),

  // check for geolocation support
  if (navigator.geolocation) {
    // make asynchronous getCurrentPosition call
    navigator.geolocation.getCurrentPosition(geoSuccess, geoErrorHandler);
    myLocationDiv.innerHTML = 'Retrieving your location...';
  } else {
    // geolocation not supported
    myLocationDiv.innerHTML = 'Geolocation API Not Supported';
  }
}

// Initialize the page
window.addEventListener('load',init,false);

</script>
</head>
<body>
<div id="container">
  <div id="mapCanvas"></div>
  <div id="btnMapMe">
    <button id="mapMe">Map Me</button>
  </div>
  <div id="myLocation"></div>
</div>
</body>
</html>


When you click the Map Me button, the mapMe function will be called. As before, you check whether the Geolocation API is available and, if so, perform the getCurrentPosition method. If the getCurrentPosition succeeds, then the geoSuccess function is called, and the coordinates are retrieved, displayed in the myLocation div, and then used to create a Google map instance with the marker and info window. The result will look similar to Figure 10.2 but with your location.

Figure 10.2 Your location mapped in a Google map

image

In the getCurrentPosition method call, you add the second parameter, which is the error handler, named geoErrorHandler. The following are the errors that can be returned by the position methods:

PERMISSION_DENIED (1): The request failed because the user did not authorize use of the location information.

POSITION_UNAVAILABLE (2): The position of the device could not be determined by the browser.

TIMEOUT (3): This is returned if a timeout property has been supplied and the timeout length has passed.

The error handler allows you to catch an error returned and take the appropriate action. A common error, PERMISSION_DENIED, results from the user not granting access to the information required by the Geolocation API call on the page. To view this error, reload your page, and when the browser asks for you to allow access to the location information, choose to not share or disallow access to your location. The error handler geoErrorHandler will be called with a position error object passed to it. The position error object, titled error in our code, will include two attributes: code and message. The code shown previously is a numerical constant that defines the type of error, while the message may contain an optional string message for you as the developer to gain more understanding as to why the error occurred. In this case, since the user has denied access to the location information, the PERMISSION_DENIED error code will be provided, and you can display your own message.


Note

Mozilla adds a fourth value for possible error codes: UNKNOWN_ERROR (0). This error is provided when the location retrieval fails for an unknown reason and is one that is not covered by the other errors. In Listing 10.2, the switch default case will catch any unknown error, including the UNKNOWN_ERROR provided by Mozilla. In this case, the message attribute of the error object can be more beneficial for determining the reason for the error.


In the recipe, you leverage the Google Maps JavaScript API V3 Overlay to display to the user their location with a marker. Tied to this marker, you also open an info window with their latitude and longitude coordinates. Mozilla provides an additional position attribute titled address, which is not in the W3C specification. It provides the physical location of the coordinates. This physical address location may of course not be exact since the accuracy of the position may be too large, but when available, it saves having to use another service to reverse geocode the coordinates. In this recipe, if the address object is available, you can pull the city and region attributes from it and append the values to the info window. The following are the address object attributes that are available when the object is provided:

city: DOMString with the city

country: DOMString with the country

countryCode: DOMString with the country code

county: DOMString with the county

postalCode: DOMString with the postal or ZIP code

premises: DOMString with the premises

region: DOMString with the region

street: DOMString with the street name

streetNumber: DOMString with the street number

If the address object is not available, then you can use a reverse geocoding service provided by Google, as you will see in the next recipe. In the next recipe, you will look at the three options provided with the getCurrentPosition interface and how they can be beneficial depending on your specific needs.

Intermediate Recipe: Determining Distance with PositionOptions

This recipe will use getCurrentPosition to first locate your browser’s location and then calculate the distance to a set of points, reverse geocode your position, and display this information to the viewer. To better control the location information provided, you will use the third parameter of the getCurrentPosition method, PositionOptions.

PositionOptions is an object passed to the getCurrentPosition method as a parameter and allows you to have some control over the behavior of the method. This can be beneficial given the type of application you are working with. As an example, if you are working on a location-based restaurant application for the mobile space, then the normal accuracy of the returned location may be too broad for your needs. You can set three options in the PositionOptions of getCurrentPosition, as shown in Table 10.3.

Table 10.3 PositionOptions Parameters

image

Note

For the timeout option of the PositionOptions parameter, the time that the user takes to authorize the access to the location information while the request panel or dialog is up is not calculated in this amount. The timeout milliseconds is calculated only for the time that the actual call is being performed.


When the page loads, the setLocation function will be called, which will trigger the getCurrentPosition method using a set of options. Once you receive the call, you will then use the latitude and longitude coordinates to create an instance of a Google map, reverse geocode the coordinates, and calculate the distance to various cities.

1. Add the setLocation method call to the body onload attribute, and add the setLocation function, making sure to include the position options object.

2. Update the Google Maps JavaScript API V3 Overlay script tag to load the geometry library that will be used for the distance calculation:

<script src="http://maps.google.com/maps/api/js?sensor=false&libraries=geometry">

3. Add the reverseGeoCode function that takes your latitude and longitude point and retrieves the address information from a Google geocoder.

4. Add the calculateDistance function that uses computeDistanceBetween to calculate the distance to London, New York, and San Francisco.

5. Update the HTML body div sections to mirror those in Listing 10.3 to have a container, including a mapCanvas, location information, and city distance divs.

Listing 10.3 getCurrentPosition with Position Options


<!DOCTYPE html>
<html>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>10.3 Points To</title>
<style>
  #container {
    width:500px;
  }

  #mapCanvas {
    width:500px;
    height:300px;
    border-style:solid;
    border-width:2px;
    margin: 22px 0;
  }

  #location {
    float:right;
    text-align:right;
  }
  #cityDistance tr:nth-child(odd) { background-color:#eee; }
  #cityDistance tr:nth-child(even) { background-color:#fff; }

  .numDistance {
    text-align:right;
  }
</style>
<script src="http://maps.google.com/maps/api/js?sensor=false&libraries=geometry"></script>
<script>
// global reference variable
var map;

// success callback function for getCurrentPosition
function geoSuccess(position) {

  // get our lat and lng coordinates
  var myPosLat = position.coords.latitude;
  var myPosLng = position.coords.longitude;

  // display the coords and timestamp object fields
  document.getElementById('myPosLat').innerHTML = myPosLat;
  document.getElementById('myPosLng').innerHTML = myPosLng;

  // create our latlng object
  var myLatLng = new google.maps.LatLng(myPosLat, myPosLng);

  // set our options for the map and create the map
  var myOptions = {
    zoom: 14,
    center: myLatLng,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  }
  map = new google.maps.Map(document.getElementById('mapCanvas'), myOptions);

  // reverse geocode the lat and lng
  reverseGeoCode(myLatLng);

  // calculate the distance to points of interest
  calculateDistance(myLatLng);

  // update our status
  document.getElementById('geoStatus').innerHTML = 'Location Retrieved';
}

// function to reverse geocode given a lat / lng
function reverseGeoCode(geoLatLng) {
  // create our object instances
  var geocoder           = new google.maps.Geocoder();
  var infowindow         = new google.maps.InfoWindow();

  // perform our geocoding
  geocoder.geocode({'latLng': geoLatLng}, function(results, status) {
    if (status == google.maps.GeocoderStatus.OK) {
      // check if we received an address
      if (results[0]) {
        // create marker on map
        var marker = new google.maps.Marker({
          position: geoLatLng,
            map: map
        });
        // set the content to the address and open the window
        infowindow.setContent(results[0].formatted_address);
        infowindow.open(map, marker);
      }
    } else {
      alert('Geocoder failed due to: ' + status);
    }
  });
}

// calculate distance function
function calculateDistance(disLatLng) {

  // set up variables and objects for distance
  var conEarth = 3963.19;  // ave. miles circumference
  var gmapsSpherLib = google.maps.geometry.spherical;

  // points of interest
  var NYCLatLng = new google.maps.LatLng(40.7141667,-74.0063889);
  var LDNLatLng = new google.maps.LatLng(51.5001524,-0.1262362);
  var SFOLatLng = new google.maps.LatLng(37.615223,-122.389979);

  // distance calculations
  var distFromLDN = gmapsSpherLib.computeDistanceBetween(disLatLng,LDNLatLng,conEarth).toFixed(2);
  var distFromNYC = gmapsSpherLib.computeDistanceBetween(disLatLng,NYCLatLng,conEarth).toFixed(2);
  var distFromSFO = gmapsSpherLib.computeDistanceBetween(disLatLng,SFOLatLng,conEarth).toFixed(2);
  // set display with values
  document.getElementById('divDistFromLDN').innerHTML = distFromLDN + ' mi.';
  document.getElementById('divDistFromNYC').innerHTML = distFromNYC + ' mi.';
  document.getElementById('divDistFromSFO').innerHTML = distFromSFO + ' mi.';
}

// error handler for getCurrentPosition
function geoErrorHandler(error) {

  // initialize our error message
  var errMessage = 'ERROR: ';

  // based on the error code parameter set the message
  switch(error.code)
  {
    case error.PERMISSION_DENIED:
      errMessage += 'User did not share geolocation data.';
      break;
    case error.POSITION_UNAVAILABLE:
      errMessage += 'Could not detect current position.';
      break;
    case error.TIMEOUT:
      errMessage += 'Retrieving position timed out.';
      break;
    default:
      errMessage += 'Unknown error.';
      break;
  }

  // display the error to the user
  document.getElementById('geoStatus').innerHTML = errMessage;
}

// function to initialize call for position
function setLocation() {

  var divStatus = document.getElementById('geoStatus'),

  // check for geolocation support
  if (navigator.geolocation) {

    // oldest allowed is 1 minute and timeout as 30 sec.
    var posOptions = {maximumAge:60000,
      timeout:30000};

    // make asynchronous getCurrentPosition call
    navigator.geolocation.getCurrentPosition(geoSuccess, geoErrorHandler, posOptions);
    divStatus.innerHTML = 'Retrieving your location.';

  } else {
    // geolocation not supported
    divStatus.innerHTML = 'Geolocation API Not Supported';
  }
}

// Launch the location retrieval
window.addEventListener('load',setLocation,false);

</script>
</head>
<body>
<div id="container">
  <div id="mapCanvas"></div>
  <div id="location">
    <table id="status">
      <tr>
        <td colspan="2"><div id="geoStatus"></div></td>
      </tr>
      <tr>
        <td>Latitude:</td>
        <td class="numDistance"><div id="myPosLat"></div></td>
      </tr>
        <tr>
        <td>Longitude:</td>
        <td class="numDistance"><div id="myPosLng"></div></td>
      </tr>
    </table>
  </div>
  <div id="distance">
    <table id="cityDistance">
      <tr>
        <td>London:</td>
        <td class="numDistance"><div id="divDistFromLDN"></div></td>
      </tr>
        <tr>
        <td>New York:</td>
        <td class="numDistance"><div id="divDistFromNYC"></div></td>
      </tr>
      <tr>
        <td>San Francisco:</td>
        <td class="numDistance"><div id="divDistFromSFO"></div></td>
      </tr>
    </table>
  </div>
</div>
</body>
</html>


When the page loads, you use the getCurrentPosition method to retrieve the latitude and longitude coordinates but with some key options passed. An object titled posOptions is created and then passed to the getCurrentPosition. In the posOptions, you set the maximum age option to 60000, equal to one minute, and the timeout to 30 seconds (30000). This tells the getCurrentPosition to pull only from a previously cached location if the age of the location information is less than one minute old. The timeout limits the length of time allowed for the getCurrentPosition to retrieve the position:

var posOptions = {maximumAge:60000, timeout:30000};

Once you have the position information, you then reverse geocode to get the full address using a handy geocoder object from the Google script and then calculate the distance to three cities. The result will look similar to Figure 10.3.

Figure 10.3 Calculated distance from three cities

image

As you have seen in this recipe, you can control the behavior of the location position acquisition by setting the PositionOptions object in the getCurrentPosition call. The options allow you to change accuracy and performance, tuning your application to the experience that is needed by the user. The recipes to this point have included maps, reverse geocoding, and even distance calculations. The thought has probably crossed your mind about the mobile space and how to change the information presented to the user as their location changes. Well, the Geolocation API has just the thing, as you will see in the next recipe.

Advanced Recipe: Following a Moving Location with watchPosition

The browser that your visitor is using in many cases will be mobile-based. It is not uncommon to see people walking down the street, riding the subway, or otherwise moving about while getting information about their surroundings or their locations. The getCurrentPosition method provides a position object once when called. However, as a person moves around, it would be nice to “follow” the location. This is where two new methods, watchPosition and clearWatch, of the GeoLocation API are useful.

The watchPosition method is very similar to the getCurrentPosition and takes the same parameters. When the watchPosition method is called, the browser will create a background task and provide a reference ID to a watch process as a return. The background task will retrieve the current position, send the location to the success callback, and then set a timer to watch the position. Each time the timer is triggered and a new location is retrieved, the location is then compared to see whether it is “significantly” different. If the new location is significantly different from the last, then the success callback function is called with the new location information. The process will continue to run until the clearWatch method is called with the watch ID as a parameter or the browser tab or window is closed (in mobile platforms such as the iPhone, this could be when the browser is also sent to the background). The following are the interfaces of the watchPosition and clearWatch methods, respectively:

long watchPosition (successCallback [, errorCallback] [, positionOptions])

The parameters of the watchPosition method are as follows:

successCallback: The function to execute and pass the location object to when a new location is identified by the browser

errorCallback: (Optional) The function to handle any errors that occurred

options: (Optional) An options object to handle how the position is retrieved

clearWatch (watchId)

The parameter of the clearWatch method is as follows:

watchId: The long ID reference to the watch process to end

In this recipe, you will use the watchPosition method to retrieve the location of the browser and map a new marker on a Google map whenever the location differs from the last marker by more than a quarter of a mile. The viewport for this recipe is set in the meta tag for an iPhone width along with the CSS styles so that the recipe can be easily run on an iPhone to show the movement on the map. A line will be connected between the points to show a trail of the past points, and the map will be centered on the last point shown. When the user clicks the Clear Watch button, the watch process will end.

By checking the distance from the last point, this allows you to keep the map fairly clean as a person moves with their mobile device. If, however, you were working on a smaller scale such as directions in a city, a quarter-mile difference may be too large; if you were working with a fast means of transportation, a quarter mile may not be large enough. This difference check will be based on your own needs, but this recipe shows how you can filter location points using some quick distance calculations as the location changes.

1. Create the HTML page with the Start Watch and Clear Watch buttons, as shown in Listing 10.4.

2. Include the script for the Google Maps JavaScript API V3 Overlay with the geometry library and the global variables that will hold the watch ID, map, polyline, and last latitude and longitude coordinates.

3. Add the initMap function in the script, and set the load event to launch the initMap function.

4. Add the startWatch and clearWatch functions.

5. Add the successCallback and errorCallback functions.

Listing 10.4 Using watchPosition to Track Your Path


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
<title>10.4 Leaving My Mark</title>
<style>
  #container {
    width:300px;
  }

  #mapCanvas {
    width:300px;
    height:200px;
    border-style:solid;
    border-width:2px;
    margin: 22px 0;
  }

  #btnMap {
    float:left;
  }
  #location {
    float:right;
  }

  .numDistance {
    text-align:right;
  }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&libraries=geometry"></script>
<script>

// declare our variables
var watchId;  // our watchposition process id
var map;      // our map
var poly;     // our polyline for marking our path
var lastLatLng;  // the last lat and lng coordinate

// set constant for miles for computeDistanceBetween method
var conEarthMi  = 3963.19;

// initialize our map
function initMap() {

  // add the button listeners
  var btnStartWatch = document.getElementById('startWatch'),
  var btnStopWatch = document.getElementById('stopWatch'),
  btnStartWatch.addEventListener('click',startWatch,false);
  btnStopWatch.addEventListener('click',stopWatch,false);

  // set initial position to new york and create map
  lastLatLng = new google.maps.LatLng(40.7141667,-74.0063889); // new york
  var myOptions = {
    zoom: 14,
    center: lastLatLng,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  }
  map = new google.maps.Map(document.getElementById('mapCanvas'), myOptions);

  // set our polyline for showing the path
  var polyOptions = {
    strokeColor: '#00FF00',
    strokeOpacity: 1.0,
    strokeWeight: 3
  }
  poly = new google.maps.Polyline(polyOptions);
  poly.setMap(map);
}

// success handler for geolocation watch position
function successCallback(position) {

  // get our latitude and longitude
  var posLat = position.coords.latitude;
  var posLng = position.coords.longitude;

  // create a new google maps latlng object
  var newLatLng = new google.maps.LatLng(posLat,posLng);

  // calculate distance from last point
  var distFromLast = google.maps.geometry.spherical.computeDistanceBetween(newLatLng, lastLatLng, conEarthMi);

  // verify distance greater than a quarter of a mile
  if (distFromLast > 0.25) {

    // get the polyline path array
    var path = poly.getPath();

    // Add the new coordinate to our path array
    path.push(newLatLng);

    // Add a new marker at the new coordinate
    var marker = new google.maps.Marker({
      position: newLatLng,
      title: '#' + path.getLength(),
      map: map
    });

    // recenter the map on the new coordinate
    map.setCenter(newLatLng);

    // update our display
    document.getElementById('myPosLat').innerHTML = posLat.toFixed(8);
    document.getElementById('myPosLng').innerHTML = posLng.toFixed(8);
    document.getElementById('watchStatus').innerHTML = 'Updated Position (#' + path.getLength() + ')';

    // set our last coordinate to the new coordinate
    lastLatLng = newLatLng;

  }
}
// error handler for geolocation watchposition
function errorCallback(error) {

  // initialize our error message
  var errMessage = 'ERROR: ';
  var divWatchStatus = document.getElementById('watchStatus'),

  // based on the error code parameter set the message
  switch(error.code)
  {
    case error.PERMISSION_DENIED:
      errMessage += 'User did not share geolocation data.';
      break;
    case error.POSITION_UNAVAILABLE:
      errMessage += 'Could not detect current position.';
      break;
    case error.TIMEOUT:
      errMessage += 'Retrieving position timed out.';
      break;
    default:
      errMessage += 'Unknown error.';
      break;
  }

  // update our status
  divWatchStatus.innerHTML = errMessage;
}

// button start watch handler
function startWatch() {

  var divWatchStatus = document.getElementById('watchStatus'),

  // verify geolocation is available
  if (navigator.geolocation) {

    // make sure only one watch
    if (watchId == null) {

      // set our position options
      // maximum age 40 seconds
      // timeout of 20 seconds
      // enhanced accuracy on for mobile
      var posOptions = {maximumAge:40000,
        timeout:20000,
        enhancedAccuracy:true}
      // start our watch
      watchId = navigator.geolocation.watchPosition(successCallback,
        errorCallback,
        posOptions);

      // update our status
      divWatchStatus.innerHTML = 'Watching Location ('+watchId+')';
    }
  } else {
    // update status that geolocation is not available
    divWatchStatus.innerHTML = 'Geolocation Not Supported';
  }
}

// button stop watch handler
function stopWatch() {

  // verify that we have a watch currently on
  if (watchId != null) {
    // clear our watch
    navigator.geolocation.clearWatch(watchId);

    // set the watchId flag to null
    watchId = null;

    // update our status
    document.getElementById('watchStatus').innerHTML = 'Off';
  }
}

// Initialize the page
window.addEventListener('load',initMap,false);

</script>
</head>
<body>
<div id="container">
  <div id="mapCanvas"></div>
  <div id="btnMap">
    <button id="startWatch">Start Watch</button>
    <br>
    <button id="stopWatch">Stop Watch</button>
  </div>
  <div id="location">
    <table id="status">
      <tr>
        <td>Latitude:</td>
        <td class="numDistance"><div id="myPosLat"></div></td>
      </tr>
      <tr>
        <td>Longitude:</td>
        <td class="numDistance"><div id="myPosLng"></div></td>
      </tr>
      <tr>
        <td colspan="2"><div id="watchStatus"></div></td>
      </tr>
    </table>
  </div>
</div>
</body>
</html>


Let’s look at Listing 10.4 in a bit of detail. When the page loads, the initMap function is called and initializes the map on the page. We have chosen coordinates for New York City, but you could easily set a different starting center for the map or load the map only when the watch starts. In the initMap, you create a polyline layer on top of the map, which will allow you to connect the coordinates from the watch and display the “path” to the viewer. The map is now initialized, and you can begin the watch of the position.

To start, the Start Watch button is clicked or tapped. As normal, you should be asked to confirm you want to share your location information. Once clicked, the startWatch function will be called. In the startWatch function, you check that there is no watch process already running by checking the global watch ID variable. This prevents more than one watch process from being started. You then set the position options and call the watchPosition method. The return of watchPosition, the watch ID, is then stored in the watchId variable so that you can use it to stop the process later with the clearWatch.

When the watchPosition returns with a location object, you pull out the latitude and longitude properties, create a latitude and longitude object, and then calculate the distance with the last latitude and longitude coordinates with the Google computeDistanceBetween method. In this case, you pass into the method the new coordinate, the last coordinate, and a constant value, informing the function that you want the return value in miles. You then check this distance to see whether it is greater than a quarter of a mile so that you do not flood the map with markers. If the next point is more than the distance, then you push onto the poly path array of points the new coordinate and add a new marker. Pushing the coordinate onto the array updates the line on the map, so with each new point the path will grow, showing where you have been, as shown in Figure 10.4.

Figure 10.4 Growing the path with coordinate points

image

This recipe shows how you can access the browser’s location and be notified whenever that position changes. This information can be used for a wide array of applications including integration with databases of location-based services such as the Yahoo Query Language and the beta of Google Places to show nearby places of interest.


Note

One thing to consider as you are working with devices, especially mobile devices, and retrieving geolocation information is that the continual retrieval of device information will accelerate the use of the device’s battery. This should be taken into account when you design your application and used only when needed in order to minimize the usage.


Summary

The Geolocation API provides an easy interface for adding location-specific and position-aware functionality to websites and applications. Some of the solutions that can be designed include the following:

• Display of location specific information

• Proximity awareness

• Dynamic adjustment to a locale, such as language and currency

• Map and route integration

• Geotagging data, pictures, and other items with location information

In this chapter, you learned the getCurrentPosition, watchPosition, and clearWatch methods along with the success and error callbacks from these methods. The possibilities are endless, and it is exciting to have this option now in browsers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset