Chapter 2. Geolocation for Publishers

Location-based web sites have become so commonplace that we frequently take their functionality for granted. Type Starbucks into your Google search bar, and you’ll get a list of numerous store locations within your immediate vicinity, without you even needing to specify a town or city. Flickr’s map page has a “Find my location” button that will show you pictures taken in areas near you. And if you want to geotag your blog entries, WordPress has a plugin for that.

With the advent of HTML5, building geolocation functionality into web content has become incredibly easy, thanks to the release of the Geolocation API, which provides a standardized mechanism across all major web browsers for querying and receiving user location data.

Obtaining location data via the web browser requires adding just one line of JavaScript code to your script:

navigator.geolocation.getCurrentPosition(callback_function);

Where callback_function is the function that will be called by the browser when it completes its attempt to retrieve location data. Not every browser supports the geolocation API, however, and geolocation services are not available at all times in all locations, so you’ll probably want to build in a bit more error handling—for example:

if (Modernizr.geolocation) {
    navigator.geolocation.getCurrentPosition(callback_function, throw_error);
} else {
    alert('Your browser/ereader does not support geolocation. Sorry.');
}

function throw_error(position) {
    alert('Unable to geolocate you. Sorry.');
}

The Geolocation API returns two properties that contain location data to your callback function: position.coords.latitude, which contains the user’s latitude, and position.coords.longitude, which contains his longitude. That’s neat, but unless your users are geography savants, the values (35.046872, -90.024971) probably mean far less to them than 3734 Elvis Presley Blvd, Memphis, TN.

Luckily, there are many great web services out there that will translate latitude/longitude coordinates into information far more transparent and valuable to humans: addresses, street maps, weather data, and more. Google Maps has a set of APIs available for obtaining location data and embedding maps right in your HTML documents, and in the next section, we’ll query the GeoNames database to add real-time geographical data to a work of fiction.

A Geolocated Tale

Wouldn’t it be great if authors could tailor their short stories, novels, and poems to the hometown, state, and country of each and every one of their readers? Instead of The Merchant of Venice, you could have The Merchant of Dallas, or The Merchant of Yonkers. Whether you find the idea enthralling or a bit appalling, the Geolocation API makes it possible.

To illustrate what’s feasible on a smaller scale, we’ll take the introduction to a short story, and geolocate it with details about the reader’s current location. We’ll start with some skeleton paragraphs that include placeholders for street address, city name, and current temperature—styled in bold red for emphasis. Example 2-1 shows the HTML, and Figure 2-1 shows it displayed in Safari for Mac.

Example 2-1. HTML for our story skeleton (geolocation_example.html)
<!DOCTYPE html>
<html lang="en">
<head>
<title>A Geolocated Tale</title>
<script src="modernizr-1.6.min.js"></script>
<script src="geolocation-story.js"></script>
<script src="jquery-1.6.2.min.js"></script>
<style media="screen" type="text/css">
body {
 margin: 10px 5px 10px 5px;
}

em {
 font-weight: bold;
 font-style: normal;
 color: red;
}
</style>
</head>
<body>
<h1>A Geolocated Tale</h1>
<p>It was your typical <em id="weather_temp">LOADING
 TEMPERATURE</em>&#xb0;F day in <em id="city">LOADING CITY NAME</em> when
Muffin Bukowski was roused from decadent slumber by the
ear-throttling shriek of an unidentified avian trespassing on the
grounds of her otherwise-serene home, clearly violating Section I,
Article 246 of her condo documents.</p>

<p>Groggily stumbling to her bedroom window, Muffin peered
through the pristine glass out at <em id="street_address">LOADING STREET
NAME</em>.</p>

<p>&#8220;Are my eyes deceiving me?&#8221; Muffin muttered as she lightly
  rapped her knuckles against her forehead, unable to process the
  miraculous scene unfolding before her...</p> 
</body>
</html>
Our skeleton story in Safari
Figure 2-1. Our skeleton story in Safari

Now, we’ll need some JavaScript code to do the following:

  1. Query the Geolocation API for the reader’s latitude and longitude.

  2. Use the latitude and longitude values to then query the GeoNames database for the reader’s current temperature, and fill in the corresponding placeholder in the story.

  3. Use the latitude and longitude values to query GeoNames for the reader’s street address and city, and again fill in the corresponding placeholders.

GeoNames has several dozen web services available for getting different types of geographical data. For our example, we can use their extendedFindNearby service to get street-address and city data, and their findNearByWeather service to get the temperate data. For most of their web services, GeoNames makes data available in both XML and JSON formats, but in the case of extendedFindNearby, only XML data is available. So, to make things simple, we’ll query both services for XML. And to make things even easier, we’ll use the jQuery JavaScript library to help us interface with GeoNames and update our HTML placeholders (jQuery offers a set of convenience functions that greatly simplify both these tasks).

The code for Step 1 should look familiar:

window.addEventListener('load', eventWindowLoaded, false);

function eventWindowLoaded() {
    get_location();

    function get_location() {
        if (Modernizr.geolocation) {
            navigator.geolocation.getCurrentPosition(geolocate_story, throw_error);
        } else {
            alert('Your browser/ereader does not support geolocation. Sorry.');
        }

    function throw_error(position) {
        alert('Unable to geolocate you. Sorry.');
    }
}

As we saw in the beginning of the chapter, this code calls the getCurrentPosition() function to obtain latitude/longitude, with some error handling in place in case the user’s environment doesn’t support geolocation, or the geolocation attempt fails. This time, however, if geolocation succeeds, we’ll call the geolocate_story() function to perform Steps 2 and 3.

In Step 2, we query GeoNames for temperature info:

function geolocate_story(position) {
    var geo_lat = position.coords.latitude;
    var geo_long = position.coords.longitude;
    // Get weather information
    $.ajax({
        type: 'GET',
        url: 'http://ws.geonames.org/findNearByWeatherXML?lat=' + geo_lat + '&lng=' + geo_long,
        dataType: 'xml',
        success: function (weather_resp, xmlstatus) {
            var temperature_celsius = $(weather_resp).find("temperature").text();
            if (temperature_celsius != "") {
                // Weather temp data given in Celsius; convert to Fahrenheit, 
                // because I'm American
                var temperature_fahrenheit = 9/5*temperature_celsius + 32;
                $('#weather_temp').text(temperature_fahrenheit);
            } else {
                $('#weather_temp').text("TEMP NOT FOUND");
            }
        },
        error: function (xhr, status, error) {
            alert(error);
            $('#weather_temp').text("TEMP NOT FOUND");
        }
    })

The geolocate_story() function receives the latitude and longitude data in position, which is passed to it from the Geolocation API, and then we store that data in geo_lat and geo_long, respectively. To interface with GeoNames, we call jQuery’s $.ajax() function, which lets us set up an XML query with the following parameters:

type: 'GET'

Specifies that we’ll make a HTTP GET request (as opposed to a POST request), which is compatible with the GeoNames API

url: 'http://ws.geonames.org/findNearByWeatherXML?lat=' + geo_lat + '&lng=' + geo_long

Specifies the URL to be queried. For the findNearByWeather service, the URL is http://ws.geonames.org/findNearByWeatherXML, followed by the lat parameter for latitude and the lng parameter for longitude, where we supply the geo_lat and geo_long values we got from the Geolocation API.

datatype: 'xml'

GeoNames is going to return XML to us, so this tells the $.ajax() function to parse the incoming data accordingly

success: ...

Specifies what to do if our API call is successful; here, we’ll call a function to process the weather data, which performs the following three steps:

  1. Grabs the value of the <temperature> element in the XML returned from GeoNames ($(weather_resp).find("temperature").text();)

  2. If the temperature value is present, converts it from Celsius to Fahrenheit (var temperature_fahrenheit = 9/5*temperature_celsius + 32;)

  3. Updates the weather_temp <span> in the HTML with the Fahrenheit temperature ($('#weather_temp').text(temperature_fahrenheit);), or if no temperature value was returned, inserts the text “TEMP NOT FOUND”

error: ...

Specifies what to do if our API call fails; here, we’ll call a function that updates the weather_temp <span> in the HTML with the text “TEMP NOT FOUND” ($('#weather_temp').text("TEMP NOT FOUND");)

In Step 3 we again query the GeoNames API in similar fashion, but this time we get the user’s location data (street address and city):

// Get full location information
$.ajax({
    type: 'GET',
    url: 'http://ws.geonames.org/extendedFindNearby?lat=' + geo_lat + '&lng=' + geo_long,
    dataType: 'xml',
    success: function (loc_resp, xmlstatus) {
        var city_name = $(loc_resp).find("placename").text();
        if (city_name != "") {
            $('#city').text(city_name);
        } else {
            $('#city').text("CITY NOT FOUND");
        }
        var street_address = $(loc_resp).find("streetNumber").text() + " " + $(loc_resp).find("street").text();
        if (street_address != "") {
            $('#street_address').text(street_address);
        } else {
            $('#street_address').text("ADDRESS NOT FOUND");
        }
    },
    error: function (xhr, status, error) {
        alert(error);
        $('#city').text("CITY NOT FOUND");
        $('#street_address').text("ADDRESS NOT FOUND");
    }
})

Most of the $.ajax() parameters are identical to those for the temperature query. For the url parameter, we substitute in the extendedFindNearby URL, which is http://ws.geonames.org/extendedFindNearby. For our success function, we get the values for the placename (typically corresponds to city), streetNumber, and street elements in the XML from GeoNames, and update the corresponding <span>s in the HTML. For our error function, we update the <span>s with boilerplate “NOT FOUND” text.

Figure 2-2 shows what the final story looks like, post-geolocation, if you happen to be visiting O’Reilly Media’s Cambridge, Massachusetts, office on a warm, late-summer day.

A Geolocated tale
Figure 2-2. A Geolocated tale

Click here to try out the geolocation example in your ereader. You can also download the full code from GitHub.

HTML5 Geolocation, EPUB, and Ereader Compatibility

As with HTML5 Canvas, Geolocation support is not yet widespread in EPUB readers. At the time of publication (January 2013), among the major ereaders, iBooks is again the only one that supports the Geolocation API.

However, it’s important here to draw a distinction between “supports the Geolocation API” and “supports querying geolocation web services (like GeoNames).” While iBooks can query the Geolocation API and will return the user’s latitude/longitude coordinates, it does not support the necessary XMLHttpRequest functionality for querying Internet web services, throwing an ABORT_ERR: XMLHttpRequest Exception 102 error (see Figure 2-3)

Geolocation XMLHttpRequest Exception 102 error in iBooks
Figure 2-3. Geolocation XMLHttpRequest Exception 102 error in iBooks

So at this time, it’s not possible to embed our Geolocated Tale in an EPUB and have it successfully render in iBooks. However, you can instead post the story on the Web, and link to it within your EPUB (Mobile Safari on iPhone/iPod/iPad will indeed render the story successfully).

That said, it’s still a bit disappointing that geolocation support really isn’t available in ereaders at the present time. And what’s even more unfortunate is that because the Geolocation API is not technically part of the HTML5 specification (it’s its own separate W3C spec), it’s also not technically a requirement of the EPUB 3 spec that ereaders support the Geolocation API. And of course, geolocation support is arguably much more controversial than support for Canvas, due to very legitimate concerns regarding security and privacy.

Also potentially a bit controversial is whether the use of geolocation services in EPUB runs counter to the specifications of the format, which maintain that all resources included directly in the book content need to be embedded directly in the EPUB file, and referenced in the EPUB’s manifest. The philosophy here is that whether the user is online or offline, she should be able to access and view all the book content; a lack of Internet access should not cripple the reading experience. Does a geolocated work of fiction violate this precept? The answer to this question is a bit subjective, and likely depends on how integral a role geolocation plays in the book content, the type of fallbacks that are in place, etc.

Regardless, we’ve already reached a point on the Web where geolocation functionality is omnipresent and often taken for granted. So it seems likely that EPUB content creators and ereader developers alike will be strongly motivated to move toward a future that allows for geolocation-enhanced ebooks. The potential inherent in geolocated travel and restaurant guides alone seems huge, not to mention the opportunities for more avant-garde experimentation.

Bibliography/Additional Resources

Here’s a list of additional Geolocation resources you may find useful:

HTML5 Geolocation by Anthony T. Holdener III

Great primer that covers both how geolocation technology works, and provides many examples of how to harness it in your HTML5 applications

Who’s using the W3C Geolocation API?

Nice guide to which prominent websites are using the Geolocation API, their privacy policies, and whether users gets a heads-up that they are being geolocated

Geo-aware ebook demo by Liza Daly

Cutting-edge geolocation ebook demo in which the book’s text adapts as the user’s location changes.

Get HTML5 for Publishers now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.