EXIF Data and the Map Object

a JPEG image stores location information if the user allows that feature. Let’s look at an example in which the user can choose an image from the camera roll, read its location information, and display the corresponding map:

import com.google.maps.Map;
import com.google.maps.MapEvent;
import com.google.maps.LatLng;
import com.google.maps.MapType;
import com.google.maps.overlays.Marker;
import com.google.maps.overlays.MarkerOptions;
import flash.events.Event;
import flash.events.MediaEvent;
import flash.media.CameraRoll;
import flash.net.URLRequest;
import jp.shichiseki.exif.*;
public static KEY:String = YOUR_API_KEY;
public static SITE:String = YOUR_SITE;

var cameraRoll:CameraRoll;
var exifLoader:ExifLoader;
var map:Map;

Create your Map object as before:

map = new Map();
map.url = SITE;
map.key = KEY;
map.sensor = “false”;
map.setSize(new Point(stage.stageWidth, stage.stageHeight));
map.addEventListener(MapEvent.MAP_READY, onMapReady);
addChild(map);

Get an image from the device Gallery using the CameraRoll API:

function onMapReady(event:MapEvent):void {
map.setCenter
(new LatLng(40.736072, -73.992062), 14, MapType.NORMAL_MAP_TYPE);
if (CameraRoll.supportsBrowseForImage) {
var camera:CameraRoll = new CameraRoll();
camera.addEventListener(MediaEvent.SELECT, onImageSelected);
camera.browseForImage();
}
}

After the user selects an image, create an instance of the ExifLoader class and pass it the photo url. It will load the image and read its EXIF data:

function onImageSelected(event:MediaEvent):void {
exifLoader = new ExifLoader();
exifLoader.addEventListener(Event.COMPLETE, onExifRead);
exifLoader.load(new URLRequest(event.data.file.url));
}

If the image contains geolocation information, it is used to draw the map and a marker at the exact location:

function onImageLoaded(event:Event):void {
var exif:ExifInfo = reader.exif;
if (exif.ifds.gps) {
var gpsIfd:IFD = exif.ifds.gps;
var exifLat:Array = gpsIfd[“GPSLatitude”] as Array;
var latitude:Number = shorten(exifLat, gpsIfd[“GPSLatitudeRef”]);
var exifLon:Array = gpsIfd[“GPSLongitude”] as Array;
var longitude:Number = shorten(exifLon, gpsIfd[“GPSLongitudeRef”]);
var marker:Marker;
var parts:Array;
marker = new Marker(new LatLng(latitude, longitude));
map.addOverlay(marker);
map.setCenter(new LatLng(latitude, longitude));
}
}

function shorten(info:Array, reference:String):Number {
var degree:Number = info[0] + (info[1]/60) + (info[2]/3600);
// position from Greenwich and equator
if (reference == “S” || reference == “E”) {
degree * -1;
}
return degree;
}

Camera

All Windows Phone 7 physical devices include a high resolution camera. This is managed by the operating system, but your applications can invoke the camera using the CameraCaptureTask chooser.

To use the camera, you must do the following:

  • Add a reference to the assembly Microsoft.Phone.Tasks to your project.
  • Create a new CameraCaptureTask chooser instance as a global scoped variable (the application will be deactivated when the chooser is launched).
  • Specify a callback in the form of an event handler, or a lambda expression that will receive the picture when the user closes the chooser.
  • Show the chooser.

The camera is designed to be used in landscape mode, and it generates a picture in landscape format. The Exchangeable Image File (EXIF) headers in the image file indicate the correct orientation of the picture, and the photos application in the camera will automatically display the image in the correct orientation. However, in your own applications, you may need to rotate the image to obtain the correct orientation.

A library that you can use to read EXIF information in an image is available from “Understanding and Reading Exif Data” on The Code Project website (http://www.codeproject.com/KB/silverlight/Exif_Data.aspx). Details of how to rotate an image can be found in post, “Handling picture orientation in CameraCaptureTask in Windows Phone 7,” on Tim Heuer’s blog (http://timheuer.com/blog/archive/2010/09/23/working-with-pictures-in-camera-tasks-in-windows-phone-7-orientation-rotation.aspx).

The following code example shows how you can use the Camera CaptureTask chooser to capture a photo.

C#
CameraCaptureTask ctask = new CameraCaptureTask();
ctask.Completed += new EventHandler<PhotoResult>
(ctask_Completed);
ctask.Show();

You then access the picture that the user captured in your event handler. The user may have taken and discarded several photos, and the chooser returns only the one the user decided to keep. The following code example shows how you can access the chosen photo and save it into isolated storage on the device as a JPEG image. To use this code, you must add references to the namespaces System. Windows.Media.Imaging, System.IO.ISolatedStorage, and Microsoft. Phone (for the PictureDecoder class).

C#
void ctask_Completed(object sender, PhotoResult e)
{
WriteableBitmap theImage = PictureDecoder.DecodeJpeg
(e.ChosenPhoto);
String pictureName = “MyPhoto.jpg”;
// Create a virtual store and file stream.
// Check for an existing duplicate name.
var myStore = IsolatedStorageFile.GetUserStoreForApplication();
if (myStore.FileExists(pictureName))
{
myStore.DeleteFile(pictureName);
}
IsolatedStorageFileStream myFileStream = myStore.
CreateFile(pictureName);
// Encode the WriteableBitmap into the JPEG stream and
// place it into isolated storage.
Extensions.SaveJpeg(theImage, myFileStream,
theImage.PixelWidth, theImage.PixelHeight, 0, 85);
myFileStream.Close();
}

You can also use interop with the XNA operating system classes in Windows Phone 7 to save the photo to the Media Library, or to interact with the Media Library.

For more information about using the camera in your applications, see “CameraCaptureTask Class” on MSDN (http://msdn.microsoft.com/en-us/library/microsoft.phone.tasks.cameracapturetask(VS.92). aspx). You can also download a code sample that demonstrates capturing camera images from “Code Samples for Windows Phone”
on MSDN (http://msdn.microsoft.com/en-us/library/ff431744(VS.92).aspx).

 

EXIF Data

EXIF stands for Exchangeable Image File. EXIF data is low-level information stored in JPEG images. EXIF was created by the Japan Electronic Industries Development Association (JEIDA) and became a convention adopted across camera manufacturers, including on mobile devices. You can read about the EXIF format at http://en.wikipedia.org/wiki/Exchangeable_image_file_format and http://www.exif.org/Exif2-2.PDF.

EXIF data can include the date and time the image was created, the camera manufacturer and camera settings, location information, and even a thumbnail image. Visit Jeffrey Friedl’s website at http://regex.info/exif.cgi and load a JPEG image to see the information it contains.

In AIR for Android, you could use the geolocation API to get location information and associate it with the photo you just shot, but it is more efficient to get this information directly from the image if it is available. To store image location on an Android device when taking a picture, the user must have Location & Security→Use GPS Satellites selected and then turn on the camera’s Store Location option.

Several open source AS3 libraries are available for reading EXIF data. I chose the one by Kenichi Ishibashi. You can download his library using Subversion at http://code.shichiseki.jp/as3/ExifInfo/. Ishibashi’s Loader class uses the loadBytes function and passes its data as a ByteArray to access the raw data information. Import his package to your class.

Our first example loads an image from the Gallery, reads its thumbnail data, and displays it. Note that thumbnail creation varies among devices and is not always available. Check that it exists before trying to display it:

import flash.display.Loader;
import flash.display.MovieClip;
import flash.media.CameraRoll;
import flash.media.MediaPromise;
import flash.events.MediaEvent;
import flash.events.Event;
import flash.net.URLRequest
import jp.shichiseki.exif.*;
var loader:ExifLoader;
var cameraRoll:CameraRoll;
function Exif1() {
if (CameraRoll.supportsBrowseForImage) {
init();
}
}
function init():void {
cameraRoll = new CameraRoll();
cameraRoll.addEventListener(MediaEvent.SELECT, onSelect);
cameraRoll.browseForImage();
}
function onSelect(event:MediaEvent):void {
var promise:MediaPromise = event.data as MediaPromise;
loader = new ExifLoader();
loader.addEventListener(Event.COMPLETE, imageLoaded);
loader.load(new URLRequest(promise.file.url));
}
function imageLoaded(event:Event):void {
var exif:ExifInfo = loader.exif as ExifInfo;
if (exif.thumbnailData) {
var thumbLoader:Loader = new Loader();
thumbLoader.loadBytes(exif.thumbnailData);
addChild(thumbLoader);
}
}

The next example also lets you choose an image from the device’s Gallery and display its geographic information. The user must have GPS enabled and must have authorized the camera to save the location when the picture was taken:

import flash.display.Loader;
import flash.display.MovieClip;
import flash.media.CameraRoll;
import flash.media.MediaPromise;
import flash.events.MediaEvent;
import flash.events.Event;
import flash.net.URLRequest
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFieldAutoSize;
import jp.shichiseki.exif.*;
var cameraRoll:CameraRoll;
var loader:ExifLoader;
if (CameraRoll.supportsBrowseForImage) {
cameraRoll = new CameraRoll();
cameraRoll.addEventListener(MediaEvent.SELECT, onSelect);
cameraRoll.browseForImage();
}
function onSelect(event:MediaEvent):void {
var promise:MediaPromise = event.data as MediaPromise;
loader = new ExifLoader();
loader.addEventListener(Event.COMPLETE, onImageLoaded);
loader.load(new URLRequest(promise.file.url));
}
function onImageLoaded(event:Event):void {
var exif:ExifInfo = loader.exif as ExifInfo;
var textFormat:TextFormat = new TextFormat();
textFormat.size = 40;
textFormat.color = 0x66CC99;
var where:TextField = new TextField();
where.x = 50;
where.y = 200;
where.defaultTextFormat = textFormat;
where.autoSize = TextFieldAutoSize.LEFT;
addChild(where);
if (exif.ifds.gps) {
var gpsIfd:IFD = exif.ifds.gps;
var exifLat:Array = gpsIfd[“GPSLatitude”] as Array;
var latitude:Number = shorten(exifLat, gpsIfd[“GPSLatitudeRef”]);
var exifLon:Array = gpsIfd[“GPSLongitude”] as Array;
var longitude:Number = shorten(exifLon, gpsIfd[“GPSLongitudeRef”]);
where.text = latitude + “n” + longitude;
} else {
where.text = “No geographic information”;
}
}
function shorten(info:Array, reference:String):Number {
var degree:Number = info[0] + (info[1]/60) + (info[2]/3600);
// position from Greenwich and equator
if (reference == “S” || reference == “E”) {
degree * -1;
}
return degree;
}

Base 60 is commonly used to store geographic coordinates in degrees. Degrees, minutes, and seconds are stored separately. Put them back together and sign them depending on whether they are south of the equator and east of Greenwich Mean Time.

Displaying latitude and longitude is not very helpful, nor is it interesting for most users. But you can render a static map using latitude and longitude or retrieve an address.