The Camera API and Geolocation Exif Data on AIR for Android

As part of my MAX session, I’ve been playing around with the camera API and wanted to pull some of the Exif data around geolocation. My first thought was that I could use the geolocation APIs in AIR to inject Exif data into the image from the camera. But the way that the AIR for Android Camera API works (which makes sense) is that when you take a picture, it creates a MediaPromise, which is a new class in AIR for Android that is similar to a FilePromise. That code includes a bunch of Exif data that Android adds. The only issue with the GPS coordinates are that they’re in sexagesimal format and need to be converted into decimal.

There are actually two ways to get images into your AIR application. The first is using the flash.media.CameraUI class. That brings up the camera controls and lets the user take a picture directly. When the user clicks “OK” on the camera, it saves the file to the SD card and then passes the file reference back to AIR where you can program an event handler to respond:

public var cameraUI:CameraUI;
 
protected function onCreationComplete():void
{
 
if(CameraUI.isSupported)
{
cameraUI = new CameraUI();
cameraUI.addEventListener(MediaEvent.COMPLETE,onComplete);
}
 
}

Then all that has to be done is to call the launch() method and that pops up the camera controls. When the user confirms the photo, the MediaEvent.COMPLETE handler deals with the photo. Or in this case, the file reference.

protected function btn_clickHandler(event:MouseEvent):void
{
cameraUI.launch(MediaType.IMAGE);
}

The other way to get access to photos is to pull them from the phone’s memory. You can do that with the flash.media.CameraRoll API. It’s a very similar process to the CameraUI. Instead of a complete event, listen for a select event, and to bring up the library of images, call the browseForImage() method.

public var cameraRoll:CameraRoll;
 
protected function onCreationComplete():void
{
if(CameraRoll.supportsBrowseForImage)
{
cameraRoll = new CameraRoll();
cameraRoll.addEventListener(MediaEvent.SELECT,onSelect);
}
}
 
protected function btn_clickHandler(event:MouseEvent):void
{
cameraRoll.browseForImage();
}

Now once the image has been taken or selected, we can pull the Exif data from it. Both the complete event and the select event will look the same because they both create a MediaPromise. I’m using the Exif library here, which uses the load() method and an event handler to deal with the Exif data when it loads.

protected function onSelect(event:MediaEvent):void
{
var request:URLRequest = new URLRequest(event.data.file.url);
var exif:ExifLoader = new ExifLoader();
exif.addEventListener(Event.COMPLETE,onExifComplete);
exif.load(request);
}

The parsing of the Exif data is pretty straightforward. Once it has been parsed, pulling out the specific tags by name for Latitude and Longitude, all that is left is to convert the sexagesimal latitude and longitude to decimal degrees.

protected function onExifComplete(event:Event):void
{
var exif:ExifInfo = event.currentTarget.exif as ExifInfo;
var gpsIfd:IFD = exif.ifds.gps;
 
// get the array of GPS coordinates from the Exif data
var exifLat:Array = gpsIfd["GPSLatitude"] as Array;
var exifLon:Array = gpsIfd["GPSLongitude"] as Array;
 
// get the decimal degrees for latitude/longitude
var latitude:Number = convertSexagesimalToDecimal(exifLat[0],exifLat[1],exifLat[2],gpsIfd["GPSLatitudeRef"]);
var longitude:Number = convertSexagesimalToDecimal(exifLon[0],exifLon[1],exifLon[2],gpsIfd["GPSLongitudeRef"]);
}
 
protected function convertSexagesimalToDecimal(degrees:int,minutes:int,seconds:Number,reference:String):Number
{
// do the conversion to decimal degrees
var decimal:Number = degrees + (minutes/60) + (seconds / 3600);
 
// figure out whether we need to use negative latitude or longitude
if(reference == "S" || reference == "E")
{
return decimal * -1;
} else {
return decimal;
}
}

So that’s a crash course in using the camera API on AIR for Android and then extracting decimal degrees out of Android’s existing Exif data.

Slides and Demos from FITC San Francisco

FITC was an absolutely awesome event. Props to Shawn, Rick and team for another great event. I’m looking forward to them coming back to San Francisco next year. Thanks to everyone who attended my talk. I got some great questions and as I said in the sessions, if you have anything cool you’re doing with mapping and Flash, drop me an email.

Below are the slides I used and I’ve also posted all of the code for the demos I did. I haven’t really cleaned them up at all, but if you have any questions, let me know.

One More Day for FITC San Francisco Early Bird

It’s looking like FITC San Francisco is going to be awesome. The speaker lineup is bomber and the diversity of the sessions means that there’s going to be something for everyone. Plus the fact that we’re down in San Fran means you’ll get to see a lot of Adobeans in the halls. I’m going to be doing a talk on Geolocation and Mapping with the Flash Platform, a more in-depth version of what I did at Flash and the City.

The early bird tickets are available for one more day so definitely register soon and lock it in. You can spend the extra money on beer.

Slides and Demo from Mapping/Geolocation talk at Flash and the City

I gave a presentation on Mapping and Geolocation with the Flash Platform today at Flash and the City. Below I’ve embedded the slides and you can download the PDF here. When I get some downtime today I’ll be uploading my demos as well.

Using NativeProcess in AIR 2 with GPSBabel

GPSBabel is one of the coolest projects on the Internet. It takes basically any geolocation format and quickly lets you convert it into any other format. And the world of geodata is a wild west of various formats so it makes it super easy to bring all of that together in a handy command line tool.

Because it has a command line tool I figured it would make a pretty good (and easy) demo for AIR 2′s NativeProcess APIs. And it does! In fact, I started work on an ActionScript 3 wrapper that (should) make it easy to call GPSBabel from an AIR application and make it do your bidding. It’s in really, really rough shape, but here’s the beginning of it.

Getting Started

The first thing you need to do to use the NativeProcess API is to alter your app-xml file. After the initialWindow tag are a bunch of commented lines that talk about supportedProfiles. The NativeProcess API only works in the extendedDesktop profile so you need to enable it with this line.

<supportedProfiles>extendedDesktop</supportedProfiles>

With that, you can start testing the NativeProcess API.

Calling GPSBabel

Using the NativeProcess API is pretty simple. You first create a NativeProcessStartupInfo object, which is where you point to the executable binary as well as pass in any arguments. Then you set up event listeners on the NativeProcess object, and finally you call the start() method and pass in your startup object.

To convert one file type from another, GPSBabel takes a series of command line arguments. This is the command to convert a GPX file into a KML file.

gpsbabel -i gpx -f /tmp/myroute.gpx -o kml -F /tmp/myroute.kml

So to replicate this with the NativeProcess API, first create the startup object and pass in those arguments. The -i is the format of the original file, -f points to the original file, -o is the format to convert to, and -F is the converted file. All of those are pushed to the arguments property of the startup object.

var npsi:NativeProcessStartupInfo = new NativeProcessStartupInfo();
// The location of GPSBabel (a file object)
npsi.executable = _gpsBabelLocation;
var args:Vector.<String> = new Vector.<String>;
args.push("-i");
args.push("gpx");
args.push("-f");
args.push("/tmp/myroute.gpx");
args.push("-o");
args.push("kml");
args.push("-F");
args.push("/tmp/myroute.kml");
npsi.arguments = args;

The next step is to add event listeners to the NativeProcess object. Most of the time, GPSBabel just sends standard messages in UTF, so handling responses is easy. In the case of a conversion, the only event listener that really matters is the error listener.

_process = new NativeProcess();
_process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,onStandardErrorData);

Dealing with NativeProcess events was a little odd to me, but because GPSBabel just returns text, this is pretty easy. If it errors out, it dumps text to the screen. That text becomes part of the NativeProcess object. Depending on what the context is (if it’s an error, or just regular data output) it goes into a different variable. In this case, the event listener just reads the error data from the standardError property and traces it out

private function onStandardErrorData(event:ProgressEvent):void
{
trace(_process.standardError.readUTFBytes(_process.standardError.bytesAvailable));
}

The last thing to do is to go back up and start the process off by calling the start method and passing in the startup options that were created before.

_process.start(npStartupInfo);

And that’s it. I’ve got an example on GitHub as part of the GPSBabelOnAIR project. The mxml file in the examples folder uses the library, which has all of the NativeProcess code. The combination of those should provide a working basic example of using NativeProcess and GPSBabel.

HTML-Flash Geo

HTML and Flash working together to find each other.

Geolocation is a good example. HTML5 is going to get a geolocation API that works just beautifully even on devices with no GPS. Flash based applications will (currently) only get access to geolocation APIs when targeting the AIR runtime on mobile. Some browsers (I only know of Firefox 3.5 on Mac and the WebKit browser on the Nexus One) already support the HTML5 geolocation API… So why not use that to get geo information into your Flash based application?

There are few things I love more than geography and by extension, geolocation. It’s the digital overlay of the world. How can that not be cool? And this particular example is nice because it’s an area where HTML5 is ahead of Flash Player. Just like HTML can use Flash to implement ideas it hasn’t nailed down yet, Flash can use HTML to implement ideas that it hasn’t got yet. Bliss.