We Create Games

iPhone Geotags

Posted June 16th by Jay Crossler in Code, iPhone, Maps, Web

The new iPhone OS3.0 will be coming out soon, and one of my favorite features is updating Mobile Safari to have much better support for HTML5. HTML5 is still in draft, but basically makes HTML much smarter and more powerful – by adding features like GeoLocation, 3D-animations, Offline Storage, and more.

This is the first in a series of posts about how to write HTML5 applications and the power that they bring to the iPhone and iPod Touch. I’ll mostly be concentrating on iPhone applications (ones that use a GPS), and will try to build something useful that you can all have to make even better applications with. Like everything on this site, we’ll be releasing this as Open Source – so feel free to edit, sell it, make it better – but please just let me know what you create with it.

Nearly, version 1 (4 of the closest images pulled from Flickr)

For the first writeup in this series, we’ll create a simple application that uses HTML 5 to ask your browser for the location, and then passes that to Flickr to ask for 4 images. There is an example at: http://wecreategames.com/apps/nearly1/ – you can use this in your iPhone, or in Safari or Firefox Beta (it will ask permission to get your location, which you have to allow)

To get the code working on your own PHP server:

  1. Download the Nearly1.zip file, unzip it.
  2. Upload this to a directory in a php server that you control. (notice, this could easily be written in RoR, .Net, or anything else).
  3. Register for a Flickr API, and then copy the API Key (which is a text string)
  4. Edit the ws.php file and replace the API key in quotes with yours.
  5. Optionally, add a cache directory, which will make requests run faster
  6. Navigate to http://your.web.server/nearly (or whatever you named it) on a newer browser or your iPhone (notice, doesn’t work with IE)

Here are relevant and important pieces of code that get this to work:

The first line tells Mobile Safari not to allow the user to scroll or zoom the window using touch events when shown on an iPhone. The second says that it’s OK that when added to a home page it should function like an app, and not show the navigation bar on an iPhone:
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0">
<meta name="apple-mobile-web-app-capable" content="yes">

To enable geocoding, we first check if the browser has a navigator object. If so, we ask it for the current position and pass in two functions: what to do if the location is found, and what to do if there’s an error. In all error cases, I have it fake a location (which makes testing much easier). Notice that just for simplicity sake, I’m writing out error messages within the innerHTML of the rectangles on screen.
function getLocOnce() {
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition(function(position) {
current_position=position;
showLoc(position);
}, function(error) {
document.getElementById('image1').innerHTML = "You are off the radar! Faking location. Error:" + error.code + error.message;
var position = new Object();
position.coords = new Object();
position.coords.latitude = DEFAULT_LAT;
position.coords.longitude = DEFAULT_LON;
showLoc(position);
}, {enableHighAccuracy:true}
);
} else {
document.getElementById('image1').innerHTML = "No location on your browser";
var position = new Object();
position.coords = new Object();
position.coords.latitude = DEFAULT_LAT;
position.coords.longitude = DEFAULT_LON;
showLoc(position);
}
}

Notice that because we’re passing in functions, we’re setting up the JavaScript to make Asynchronous calls when results are known. The process is:

  1. Ask the browser for the location
  2. When the browser responds (seconds later), give it a block of code to run
  3. Show the results of the location

HTML5 has many of these similar asynchronous call structures in it – which is more complex but enables a lot of advanced functionality.

The showLoc function pulls in values and coordinates from the browser:
function showLoc(position){
if (position.coords) {
var pos1 = "Lat: " + position.coords.latitude + "<br/>Long:" + position.coords.longitude + "<br/>Accuracy:" + position.coords.accuracy;
var pos2 = "Altitude: " + position.altitude + "<br/>Accuracy:" + position.altitudeAccuracy;
var pos3 = "Speed: " + position.coords.speed + "<br/> Heading: " + position.coords.heading;
var pos4 = "Timestamp: " + position.timestamp;
document.getElementById('image1').innerHTML = pos1;
document.getElementById('image2').innerHTML = pos2;
document.getElementById('image3').innerHTML = pos3;
document.getElementById('image4').innerHTML = pos4;
}
}

The last piece of geoPosition code that we’ve added is the watchLoc and clearWatch functions. When calling watchLoc() function, you’re setting up another asynchronous callback function. Basically, you’re asking for a continuous GPS update whenever the phone can give you one. It’s very important that you turn off this watching process as it really drains the batteries quickly. I make sure to clear it when the page ends and as often as possible.
var watchId = null;
function watchLoc() {
btn = document.getElementById('geo_btn');
if (btn.value == 'Track') {
watchId = navigator.geolocation.watchPosition(showLoc);
btn.value='Stop';
} else {
clearWatch();
}
}
function clearWatch(){
if(watchId) {
navigator.geolocation.clearWatch(watchId);
watchId = null;
}
document.getElementById('geo_btn').value='Track';
}

Telling the page to always unload the request for GPS polling:
<body onunload="clearWatch()">

The final piece that might require some explanation is not new in HTML5 – it’s a standard JSON query back to a server to ask for data. In this case, we’re making an XMLHttpRequest call to ask for a list of nearby Flickr Images. The results are returned to the browser and eval()ed – which isn’t the safest way to write JavaScript but works for this demo.

Because you can’t make requests to multiple servers in the same JavaScript page, I’ve put in ws.php, which uses the phpFlickr library to ask Flickr for images. I’ve found there are a number of places (the horror!) that don’t have Flickr images geotagged nearby, so the php code tries a number of times to ask for images, each time widening the radius of allowed results.

It’s a really good idea to turn caching on (uncomment the line) in the ws.php file, as that will speed up response times and lighten the load on Flickr. I like to join multiple strings together to build a querystring, as it’s just easier to maintain in the long run.
var api_req = 'ws.php?';
var opt_lat = 'lat='+ lat;
var opt_lon = 'lon='+ lon;
var opt_num = 'num=4';
var opt_tag = 'tags=fun,travel,iphone';
var requestString = api_req + [opt_lat, opt_lon, opt_tag].join('&amp;');
loadJavascript(requestString);

Flickr has a pretty useful tool for testing these REST-based API calls. I spent a lot of time testing with it, which helped me realize there must be tags sent in with the calls, or else no photos are returned.

(Next post in the series: An Updated Flickr Proxy)


4 comments to... “iPhone Geotags”
Avatar
AbigailG

Looks nifty! FYI, You can use a date parameter to constrain the call to Flickr if you don’t want to use tags.

From http://www.flickr.com/services/api/flickr.photos.search.htm

“Geo queries require some sort of limiting agent in order to prevent the database from crying. This is basically like the check against “parameterless searches” for queries without a geo component.

A tag, for instance, is considered a limiting agent as are user defined min_date_taken and min_date_upload parameters — If no limiting factor is passed we return only photos added in the last 12 hours (though we may extend the limit in the future).”


Avatar
Jay Crossler

Cool! I think for tomorrow’s post, I’ll modify it to be date constrained… search for the past few days and keep adding a week and increase the radius until you find enough pics.


[...] (Back to last post in the series: Nearly1, the iPhone app) [...]


Avatar
Jay Crossler

I made those changes to the Flickr Proxy, so now it looks for dates instead of tags (or both). I posted it in a new blog post.




(required)



(required) (Won't be displayed)


Your Comment: