Skip to main content
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Sam Farmer
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Sam Farmer

Putting Your Geolocation iPhone Photos On A Google Map Using ColdFusion And jQuery

By
Published in , Comments (25)

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:

iPhone Photo Gelocation Data Stored In Image EXIF Headers (As Extracted By ColdFusion).

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

4 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.

15,848 Comments

@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 :)

4 Comments

@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.

61 Comments

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.

3 Comments

@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.

20 Comments

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!

15,848 Comments

@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.

4 Comments

Thanks ben, you gave me a new geo tagging idea for my search project. Now my image search can show map location too...

12 Comments

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?

15,848 Comments

@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.

1 Comments

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

15,848 Comments

@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.

1 Comments

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.

15,848 Comments

@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".

1 Comments

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?

1 Comments

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

1 Comments

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

1 Comments

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

1 Comments

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

3 Comments

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

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel