Putting Your Geolocation iPhone Photos On A Google Map Using ColdFusion And jQuery
I've been playing around with Google's map API lately and I have to say that I'm just blown away by how easy it is to use. And, not only how easy it is to use, but how easy it is to use in the context of your own applications. I've been looking for ways to play around with mapping and this morning, I started to think about my iPhone photos. I know that when it can, the iPhone geo-tags its photos with a latitude and longitude coordinates. So, I thought it would be cool to see if I could extract this geolocation information from my iPhone photos using ColdFusion and then plot them on a Google map using jQuery.
To get the geolocation information from an image in ColdFusion, all we have to do is use the ImageGetEXIFMetaData() function. This function takes an image object and returns all of the Exchangeable Image File Format (EXIF) headers stored in the image data. The structure returned from this method call looks something like this:
As you can see, it returns a struct containing EXIF headers, of which several pertain to the geolocation at which the photo was taken by the iPhone. Now, the headers displayed in the CFDump output above are just some of the headers that can exist. As far as I understand (from my brief research), EXIF is an open standard and there is no requirement for all or any of the above headers to be present in a photo's EXIF data. As such, when we extract location from a photo, we have to actually check to see if it exists first.
In the following demo, the user can upload an iPhone photo (from their hard drive). ColdFusion then extracts the photo's geolocation and passes it off to jQuery which then adds a marker to the Google map, zooms in, and re-centers.
<!--- Param the photo upload form field. --->
<cfparam name="form.photo" type="string" default="" />
<!---
Set default values for latitude and longitude. We're going
to be expecting numeric values for this; so, if they are
empty strings, we haven't uploaded a photo yet.
--->
<cfset latitude = "" />
<cfset longitude = "" />
<!---
Set the default value for the photo path. We're expecting a
source path; so, if it is empty, we haven't uploaded a photo.
--->
<cfset imageSource = "" />
<!---
Check to see if the user has uploaded a photo (this will
be true if the photo field has a length).
--->
<cfif len( form.photo )>
<!--- Upload the photo. --->
<cffile
result="uploadResult"
action="upload"
filefield="photo"
destination="#expandPath( './upload/' )#"
nameconflict="makeunique"
/>
<!--- Store the image source. --->
<cfset imageSource = "./upload/#uploadResult.serverFile#" />
<!--- Read the uploaded photo in. --->
<cfset image = imageNew( imageSource ) />
<!---
Get the EXIF meta data from the image. This is a list of
tags in the EXIF image data.
--->
<cfset exifTags = imageGetEXIFMetadata( image ) />
<!---
Check to make sure the image has all the needed
geolocation data for latitude and longitude.
--->
<cfif (
structKeyExists( exifTags, "GPS Latitude" ) &&
structKeyExists( exifTags, "GPS Latitude Ref" ) &&
structKeyExists( exifTags, "GPS Longitude" ) &&
structKeyExists( exifTags, "GPS Longitude Ref" ) &&
len( exifTags[ "GPS Latitude" ] ) &&
len( exifTags[ "GPS Latitude Ref" ] ) &&
len( exifTags[ "GPS Longitude" ] ) &&
len( exifTags[ "GPS Longitude Ref" ] )
)>
<!---
This photo contians all of the geolocation information
that we need to map it. Let's extract the informaiton
and turn it into decimal format lat/long (which we can
then put on the Google map).
--->
<!---
Get the parts of the sexagesimal latitude (degrees,
minues, seconds). We can think of this as a list that
is delimited by single and double quotes.
--->
<cfset latitudeParts = listToArray(
exifTags[ "GPS Latitude" ],
"'"""
) />
<!--- Get the latitude. --->
<cfset latitude = (
latitudeParts[ 1 ] +
(latitudeParts[ 2 ] / 60) +
(latitudeParts[ 3 ] / 3600)
) />
<!---
Check to see if we need to negate this value. For
the Ref value, N (north) is positive and S (south)
is negative.
--->
<cfif (exifTags[ "GPS Latitude Ref" ] eq "S")>
<!--- Negate the latitude. --->
<cfset latitude *= -1 />
</cfif>
<!---
Get the parts of the sexagesimal longitude (degrees,
minues, seconds). We can think of this as a list that
is delimited by single and double quotes.
--->
<cfset longitudeParts = listToArray(
exifTags[ "GPS Longitude" ],
"'"""
) />
<!--- Get the latitude. --->
<cfset longitude = (
longitudeParts[ 1 ] +
(longitudeParts[ 2 ] / 60) +
(longitudeParts[ 3 ] / 3600)
) />
<!---
Check to see if we need to negate this value. For
the Ref value, E (east) is positive and W (west)
is negative.
--->
<cfif (exifTags[ "GPS Longitude Ref" ] eq "W")>
<!--- Negate the longitude. --->
<cfset longitude *= -1 />
</cfif>
</cfif>
</cfif>
<cfoutput>
<!DOCTYPE HTML>
<html>
<head>
<title>Get GeoLocation From iPhone Photo</title>
<style type="text/css">
body {
background-color: ##333333 ;
margin: 0px 0px 0px 0px ;
padding: 0px 0px 0px 0px ;
}
##photo {
color: ##F0F0F0 ;
left: 0px ;
line-height: 200px ;
position: fixed ;
text-align: center ;
top: 0px ;
width: 400px ;
}
##photo img {
border: 1px solid ##F0F0F0 ;
display: block ;
margin: 0px auto 0px auto ;
}
##upload-form {
background-color: ##000000 ;
bottom: 0px ;
left: 0px ;
margin: 0px 0px 0px 0px ;
padding: 15px 0px 15px 0px ;
position: fixed ;
text-align: center ;
width: 400px ;
}
##map {
background-color: ##252525 ;
border-left: 1px solid ##F0F0F0 ;
left: 400px ;
position: fixed ;
top: 0px ;
}
</style>
<script
type="text/javascript"
src="http://www.google.com/jsapi?key=#request.googleAPIKey#">
</script>
<script type="text/javascript">
// Load the jQuery library.
google.load( "jquery", "1.4.0" );
// Load the maps.
google.load( "maps", "2" );
// Store the latitude / longitude value (so that
// we can refernce in subsequent javascript files).
window.latitude = "#latitude#";
window.longitude = "#longitude#";
</script>
<script type="text/javascript" src="photo_location.js"></script>
</head>
<body>
<!-- BEGIN: Photo Preview. -->
<div id="photo">
<!--- Check to see if we have a photo. --->
<cfif len( imageSource )>
<img
src="#imageSource#"
width="300"
/>
<cfelse>
<em>Upload your iPhone photo below.</em>
</cfif>
</div>
<!-- END: Photo Preview. -->
<!-- BEGIN: Upload Form. -->
<form
id="upload-form"
action="#cgi.script_name#"
method="post"
enctype="multipart/form-data">
<input type="file" name="photo" size="33" />
<input type="submit" value="Upload" />
</form>
<!-- END: Upload Form. -->
<!-- BEGIN: Google Map. -->
<div id="map">
<!--
To be loaded dynamically based on the geolocation
of the uploaded iPhone photo.
-->
</div>
<!-- END: Google Map. -->
</body>
</html>
</cfoutput>
As you can see, the page has a Form. When this form is submitted, the iPhone photo is uploaded and ColdFusion extracts its geolocation which it then converts into latitude and longitude coordinates. These lat/long coordinates are then output to a Javascript tag such that jQuery can access them when updating the Google map. Here is the Javascript file that executes the jQuery code and updates the Google map:
photo_location.js
// When the WINDOW is ready, initialize page. We are going
// with Window rather than DOM to make sure that the Google
// APIs have loaded successfully.
$( window ).load(function(){
// Get a reference to the DOM elements we are going to
// need to reference.
var photoContainer = $( "#photo" );
var photo = photoContainer.find( "img" );
var formContainer = $( "#upload-form" );
var mapContainer = $( "#map" );
// Get a referenced to the jQuery'ized window so we can
// get its dimensions.
//
// NOTE: This will influence ALL future reference to the
// window object inside this scope. Since we are in the
// load() event handler, however, we can can always use
// THIS if we want the direct window reference.
var window = $( this );
// Resize the container to fit the window.
photoContainer.height(
window.height() - formContainer.outerHeight()
);
// Check to see if we have a photo (uploaded).
if (photo.size()){
// Vertically center photo.
photo.css(
"margin-top",
((photoContainer.height() - photo.height()) / 2)
);
}
// Resize map to take up the screen.
mapContainer.height( window.height() );
mapContainer.width( window.width() - photoContainer.width() - 1 );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Create the new Goole map controller using our container.
var map = new google.maps.Map2( mapContainer[ 0 ] );
// Center the map. We are going to re-center it based on the
// latitude and longitude; but for default pursposes, let's
// center it on the US.
map.setCenter(
new google.maps.LatLng( 38.925229, -96.943359 ),
5
);
// Set the default UI elements (zoom + map types).
map.setUIToDefault();
// Now that we have loaded the map, let's check to see if we
// have a latitude / longitude value (produced from the photo
// upload and processing.
if (this.latitude.length && this.longitude.length){
// Add Title to image (for debugging).
photo.attr(
"title",
(this.latitude + " / " + this.longitude)
);
// The phot was uploaded, so let's recenter the map to
// the given location.
// Create a new positional point.
var point = new google.maps.LatLng(
parseFloat( this.latitude ),
parseFloat( this.longitude )
);
// Create a new marker to display on the map.
var marker = new google.maps.Marker( point );
// Add the marker to the map.
map.addOverlay( marker );
// Pan to the new marker (we can get the latitude and
// longitude point from the marker.
map.setZoom( 10 );
map.panTo( marker.getLatLng() );
// Open the HTML info box with the current latitude and
// longitude values.
map.openInfoWindowHtml(
marker.getLatLng(),
(
("Latitude: " + this.latitude + "<br />") +
("Longitude: " + this.longitude + "<br />") +
("<img src='" + photo.attr( "src" ) + "' width='100' />")
)
);
}
});
This Javascript waits until the Window loads, rather than simply waiting for the DOM. I am doing this to ensure that all of the remote Javascript files (Map API / jQuery) have been loaded. This probably isn't necessary (I'm sure Google's google.load() method executes in-line); but, since I've had trouble with this in the past, I figured I would stay on the cautious side. Once the window has loaded, however, I resize the page elements to fill out the entire page. Then, I create a Google map instance and add a marker based on the extracted latitude and longitude coordinates.
For amount of functionality that Google's map API gives us, it's pretty wild how relatively easy it is to implement; bring ColdFusion into the picture, and it's like you have a powerhouse of features with a slim, intuitive API. Now, I just have to see what other kinds of stuff I can do with it.
Want to use code from this post? Check out the license.
Reader Comments
Although I am not much into CF I like the implementation.
Sad that it works just with IPhones as I am not aware of other phones that insert GPS coordinates into the image headers.
@Robert,
I would assume that any phone with GPS would do this... but that is just an assumption - not based on any kind of fact :)
Good stuff Ben!!
@Billy,
Thanks my man.
@Ben
Ahh yes, it seems that now everything that has an GPS attached to it can geotagg an image.
I would try to work on a ruby implementation and see what I can get.
@Robert,
Awesome - let us know what you come up with.
It is possible, I just used my blackberry camera to do the same. Thanks ben for a very nice implementation.
One question, is it possible for coldfusion to SET the image metadata. Im thinking along the lines of a reverse google maps, where a photographer could use some mapping api to return gpc co-ordinates and then set the image matedata.
@Steven: Flickr does this pretty well... it actually handles both cases very well. You can drag a photo onto a map and the drop event will set the geocoordinates for the image and map your photos for you. Whether or not it writes it directly into the EXIF or stores it off into a DB is unknown, but that's probably the best implementation for something like that.
My Moto Droid also attaches GPS data to the pictures it takes. Can't wait to play with it some day, maybe even make a Droid app or mobile web page that can accept my photos directly from the phone.
Nice work as usual, Ben. It makes me sick to see how much time you have to learn new things and experiment... you demonstrate a goal I hope to achieve by the end of this year -- more time for personal growth and education, instead of simply working all the time!
@Steven,
ColdFusion doesn't expose any direct way to update the EXIF data. After you asked, I tried to do some research and it looks like there is no "easy" way to do this. Apparently, there is a Java ImageIO class that will allow you to compile images with a given MetaData object, but I got frustrated trying to figure it out... perhaps I'll pick it up again some other time.
@Marc,
I certainly do try to squeeze the most of the day :) Of course, to be fair, I don't do a whole lot else, so you have to understand the context I'm operating in.
Thanks ben, you gave me a new geo tagging idea for my search project. Now my image search can show map location too...
@Anooj,
Awesome my man; glad to inspire.
Awesome Ben!
Very nice app, Ben.
To the Update Topic, I dont think that there is a chance to update the Metadata of an image. I'm pretty sure Flickr uses a DB for storing the locations wich are manually set.
I'll ask a friend of mine (some pro photographers) about this. I think it is like "hacking" trying to update this data. If this is so easy to do, I wouldn't trust any pictures anymore.
lil bit ot: Isn't it possible that for example the geotag is used in law situations? Is there maybe a hash to proof the origin of the metadata of an image?
@Roman,
Yeah, it definitely appears to be *very* hard to overwrite. I did a little Google research and all I found were a few overly complicated, hacky solutions - like replacing an existing tag with one you want (but not replacing the name of it). It seems to be something that is either very hard or not meant to be done.
hi
i found your artical very good
do u know of a similar script which only get the mobile geolocation to be used in teh website? maybe one that works not only with an iphone
@Erik,
You can get this information from any photo that is tagged with geolocation information. This is not specific to iPhones; it's just that I know that iPhones definitely support this feature (when you allow it to). So, there is nothing iPhone-specific about this script.
Nice implementation.
However having problem with CF8 reading EXIF info. Took few snap shots using iPhone but seems like "exifTags" unable to retrieve GPS pointing.
Any idea? Thanks.
@Joe,
Does the imageGetEXIFMetadata() work at all (ie. does it return any data)? Or does that data that it returns simply not including GPS information.
If there is EXIF data, but simply no GPS, then you might just have the location services turned off for the camera. In your iPhone, go to:
Settings > General > Location Services
In that screen, you should see a number of applications and Toggle ON/OFF switch that indicate as to whether or not the given application is using geolocation tagging. If your camera is "off", turn it "on".
Can you explain this process to someone who doesn't know how to program?
I am trying to display my iphone 4 pictures on googlemap. Do i need my own website to host google API?
Hi Ben -
Good Stuff! I was looking into this and wanted to know if there is a way to loop over a directory with images and extract the latitude & longitude to use in CFMAP Item? I am trying to setup something like the iphone image album by places. As you zoom in it will split the markes based on images in the set. Just thought I would ask.
Thank you,
-Randy
Hi Ben,
In your experiments. Did you notice if it was possible to plot the location of a photo using a UK postcode?
I have a charitable project coming up that will involve a lot of local photographers taking photos and then uploading to a website.
It would be good if there was an easy way to plot on a map the location as part of the upload, allowing the markers to be clicked to view the photo.
Any thoughts very welcome.
Simon
Hello,
I have a lot of pics taken with my work travel. All of which appear on my iPhone map locater. Is there a way to mass export all the geo locations onto a map? From what I read of the above the extraction is on a pic by pic basis.
I would love to somehow plot out all of them at one time I a map that I could print.
Regardless, great work :-)
Thank you,
Torin
Ben,
Why does this work with my photos that I move to my computer and upload, but the metadata doesn't appear to be available if I upload directly from my phone? I'm really trying to get it to work with a picture taken right from my phone for real time?
Thanks man!
Steve
Hey Ben,
The sexagesimal to decimal logic was fab. Used it in an Import Flickr photoset application and carved it out into a UDF I submitted to cflib.
Good stuff man, good stuff.
John