Geo exif data in WordPress

15 December 2008 in Tutorials

tagged with , , ,

Did you know that WordPress imports EXIF data from your images when they’re uploaded?

If your image contains certain information (aperture, shutter speed, ISO, focal length, camera, creation time, credit, copyright, caption, title), it is in the database and you can use it as I do on my photoblog, Matt Mullenweg uses on his photo pages, and as described on Blogging Tips in WordPress Gallery and EXIF and Mixing Gallery and Blog Posts.

I’ve been thinking for a while that it would be cool to geotag images and show their location on a map. I finally got around to doing it (with some help from my husband, thanks honey!). Here’s how…

EXIF location on map

Before I get started, this post is PHP heavy and I’m not really going to explain the elements it uses (otherwise it would take me an age to write, and you just as long to read). Other than the posts I’ll link to, tizag.com has a good introductory PHP tutorial.

Geotagging

There’s a few different devices that you can connect to your camera to add GPS data to your photos and some cameras can do it themselves. I found an Aperture plugin, Maperture, which allows you to add the location of your images via Google maps.

I found the EXIF specifications and discovered the EXIF classes to get a map location are GPSLatitude and GPSLongitude (which store degrees, minutes and seconds), and GPSLatitudeRef and GPSLongitudeRef (which store N or S and E or W).

Importing EXIF data to the WordPress database

The information in this section is better handled by a function in a more recent post Add EXIF metadata to WordPress. Unlike what’s outlined below, you don’t have to change core WordPress files with the updated method. The rest of this post is up to date, though.

The file that determines which information is imported from images is wp-admin/includes/image.php. At line 231 (in WP 2.7) is a function called wp_read_image_metadata. To get WP to import the GPS data I added some values to the array:

	$meta = array(
		'aperture' => 0,
		'credit' => '',
		'camera' => '',
		'caption' => '',
		'created_timestamp' => 0,
		'copyright' => '',
		'focal_length' => 0,
		'iso' => 0,
		'shutter_speed' => 0,
		'title' => '',
	);

became,

	$meta = array(
		'aperture' => 0,
		'credit' => '',
		'camera' => '',
		'caption' => '',
		'created_timestamp' => 0,
		'copyright' => '',
		'focal_length' => 0,
		'iso' => 0,
		'latitude' => 0,
		'latitude_ref' => '',
		'longitude' => 0,
		'longitude_ref' => '',
		'shutter_speed' => 0,
		'title' => '',
	);

Then I had to tell WordPress which EXIF data is to be added to the database as latitude, longitude and the references.

After the lines,

if (!empty($exif['ISOSpeedRatings']))
	$meta['iso'] = $exif['ISOSpeedRatings'];

I added,

if (!empty($exif['GPSLatitude']))
	$meta['latitude'] = $exif['GPSLatitude'] ;
if (!empty($exif['GPSLatitudeRef']))
	$meta['latitude_ref'] = trim( $exif['GPSLatitudeRef'] );
if (!empty($exif['GPSLongitude']))
	$meta['longitude'] = $exif['GPSLongitude'] ;
if (!empty($exif['GPSLongitudeRef']))
	$meta['longitude_ref'] = trim( $exif['GPSLongitudeRef'] );

It really just copies the method used for the other EXIF fields.

GPS EXIF format

The difference between the latitude and longitude EXIF, compared to the others (e.g., aperture and shutter speed), is that GPSLatitude and GPSLongitude are stored as 3 rationals, or 3 fractions, which represent degrees, minutes and seconds. WordPress has a function to convert fractions to decimals for the database, but because these have an array of three fractions, it was a little more complicated.

I decided to import them as fractions and divide them out later.

This is what the EXIF (and IPTC) info from the MySQL database looks like,

"image_meta";a:14:{s:8:"aperture";s:1:"8";s:6:"credit";s:0:"";s:6:"camera";s:9:"NIKON D50";s:7:"caption";s:0:"";s:17:"created_timestamp";s:10:"1228061663";s:9:"copyright";s:0:"";s:12:"focal_length";s:2:"50";s:3:"iso";s:3:"200";s:8:"latitude";a:3:{i:0;s:4:"33/1";i:1;s:8:"5237/100";i:2;s:3:"0/1";}s:12:"latitude_ref";s:1:"S";s:9:"longitude";a:3:{i:0;s:5:"151/1";i:1;s:7:"906/100";i:2;s:3:"0/1";}s:13:"longitude_ref";s:1:"E";s:13:"shutter_speed";s:5:"0.004";s:5:"title";s:0:"";}

Unless you’re familiar with viewing database info, it may look like a bunch of jibberish at first, but if you look through, you can see that latitude has the values 33/1, 5237/100 and 0/1, and latitude_ref is S. So, the latitude of where that photo was taken is 33° 52.37′ 0″ S. Likewise for the longitude, which is 151° 9.06′ 0″ E.

Calling the metadata from the database

As shown in WordPress Gallery and EXIF, you can get the information in the database using
$imgmeta = wp_get_attachment_metadata( $imgid );
$imgmeta['image_meta']['shutter_speed']
, for example.

If I just use that for latitude and longitude, it returns the word “array” because the values are stored as an array. So, I converted the individual array values into slightly friendlier decimals.

I’m using the Thesis theme, so the following goes in my custom_functions.php file. If you’re using another theme you could put it in your theme’s functions.php (if your theme doesn’t have one, create one).

I copied the exif_frac2dec function from image.php (I’m not sure why, but trying to use that function from custom_functions.php didn’t work), and then applied the function to each value of the arrays.

function geo_frac2dec($str) {
	@list( $n, $d ) = explode( '/', $str );
	if ( !empty($d) )
		return $n / $d;
	return $str;
}

function geo_pretty_fracs2dec($fracs) {
	return	geo_frac2dec($fracs[0]) . '° ' .
		geo_frac2dec($fracs[1]) . '′ ' .
		geo_frac2dec($fracs[2]) . '″ ';
}

Then, in the loop that calls the exif info from the database, after the bit that says $imgmeta = wp_get_attachment_metadata( $imgid );, I defined where the latitude and longitude info comes from and ran it through the geo_pretty_fracs2dec function.

$imgmeta = wp_get_attachment_metadata( $imgid );

$latitude = $imgmeta['image_meta']['latitude'];
$longitude = $imgmeta['image_meta']['longitude'];
$lat_ref = $imgmeta['image_meta']['latitude_ref'];
$lng_ref = $imgmeta['image_meta']['longitude_ref'];

Using geo_pretty_fracs2dec($latitude). $lat_ref . ' ' . geo_pretty_fracs2dec($longitude) . $lng_ref outputs something that looks like 33° 52.37′ 0″ S 151° 9.06′ 0″ E.

Google uses a full decimal latitude and longitude; you need to divide minutes by 60 and seconds by 3600 and add them all together. Google also uses negative values to determine South and West.

The following function extracts the array values, divides them accordingly, and adds them together.

function geo_single_fracs2dec($fracs) {
	return	geo_frac2dec($fracs[0]) +
		geo_frac2dec($fracs[1]) / 60 +
		geo_frac2dec($fracs[2]) / 3600;
}

After the other variable definitions from before,

$lat = geo_single_fracs2dec($latitude);
$lng = geo_single_fracs2dec($longitude);
if ($lat_ref == 'S') { $neg_lat = '-'; } else { $neg_lat = ''; }
if ($lng_ref == 'W') { $neg_lng = '-'; } else { $neg_lng = ''; }

The whole list item to call the location reference and link to the map looks like,

if ($latitude != 0 && $longitude != 0) echo '<li>Location: <a href="http://maps.google.com/maps?q=' . $neg_lat . number_format($lat, 6) . '+' . $neg_lng . number_format($lng, 6) . '&z=11">' . geo_pretty_fracs2dec($latitude). $lat_ref . ' ' . geo_pretty_fracs2dec($longitude) . $lng_ref . '</a></li>';

Where the if != 0 says to only do this stuff if the location is not equal to zero, because there’s no point sending people to the South Atlantic Ocean. Would be a problem if that’s where you actually were though… The z=11 bit is the zoom on the map, play around with it until it suits you. Everything else is PHP or stuff I mentioned before, but feel free to ask what stuff does if you don’t understand it.

This will work for other maps, such as Yahoo, you just need to find out what the URL structure for the other map is.

EXIF location displayed

That’s it. My photoblog EXIF uses a combination of the above, the code at Blogging Tips and also some from A Moment of EnLitenment to show shutter speed as fractions.

Have a look at my complete EXIF code to see it all combined, and if you made it to the end of this post, congratulations!

{ 3 trackbacks }

adampetrie.com | HOW TO: Plugin Free Photo Galleries in WordPress
5 March 2009 at 02:47
Add image EXIF metadata to WordPress — kristarella.com
3 April 2009 at 00:20
Add Shutter Speed, Aperture, EXIF, Or IPTC Data To WordPress Gallery
25 June 2009 at 04:04

{ 23 comments… read them below or add one }

1 Max Weir 15 December 2008 at 08:40

This sounds and looks amazing! This is something that I will keep in the back of my mind for any future projects that may require a solution like this.

Thanks.

2 kristarella 19 December 2008 at 19:08

Cheers Max! Yeah, it’s really one of those back-burner things, but I thought it would be useful for anyone who searches for this sort of thing. :)

3 Ed 7 January 2009 at 15:51

I have the new image.php page getting the new geo data. I confirmed it is going into the database when I upload an image as well.

Quesion, can this work by just posting an image in a blog post and not using the Gallery option?

4 kristarella 7 January 2009 at 19:30

Ed, you have to upload the image using WordPress’ image uploader to get the data into the database. If you want to show the image and its data in a post it needs to be attached to the post, which is done either by uploading with that post or navigating the media library while editing a post and attaching it to that post. The way I retrieve info in my photoblog is via the attachments of the photoblog posts.
Hope that answers your question.

5 Sean 26 January 2009 at 16:15

Great tutorial. One question though… do you know of any way to add this exif data to the caption of an image in a regular post? I found that the caption gets built in wp-includes/media.php, but can’t figure out how to add your exif info.

6 kristarella 26 January 2009 at 18:09

Sean — The caption code has a filter built in wp-includes/media.php at line 545.

I was able to replace the image caption with the following code in my functions.php

function geo_frac2dec($str) {
	@list( $n, $d ) = explode( '/', $str );
	if ( !empty($d) )
		return $n / $d;
	return $str;
}
function geo_pretty_fracs2dec($fracs) {
	return	geo_frac2dec($fracs[0]) . '° ' .
		geo_frac2dec($fracs[1]) . '′ ' .
		geo_frac2dec($fracs[2]) . '″ ';
}

function coords() {
	global $post;
		$args = array(
			'post_type' => 'attachment',
			'numberposts' => -1,
			'post_status' => null,
			'post_parent' => $post->ID
		);
		$attachments = get_posts($args);
		foreach ($attachments AS $attachment) {
			$imgid = $attachment->ID;
			$imgmeta = wp_get_attachment_metadata( $imgid );

			$latitude = $imgmeta['image_meta']['latitude'];
			$longitude = $imgmeta['image_meta']['longitude'];
			$lat_ref = $imgmeta['image_meta']['latitude_ref'];
			$lng_ref = $imgmeta['image_meta']['longitude_ref'];

if ($latitude != 0 && $longitude != 0) return geo_pretty_fracs2dec($latitude). $lat_ref . ' ' . geo_pretty_fracs2dec($longitude) . $lng_ref;
		}
}

function add_to_caption($deprecated, $attr, $content) {
$coords = coords();

	return '<div ' . $id . 'class="wp-caption ' . $align . '" style="width: ' . (10 + (int) $width) . 'px">'
	. $content . '<p class="wp-caption-text">' . $caption . $coords . '</p></div>';
}
add_filter('img_caption_shortcode', 'add_to_caption', 10, 3);

I left out the link to Google maps, but you could easily add back that code from the post. I couldn’t get it to add the coordinates instead of replacing the whole caption. If I manage to do that I’ll let you know.

7 Sean 27 January 2009 at 03:25

Excellent! Thanks for helping me get on the right track. I dropped in your code and was able to see why the caption wasn’t showing up. None of the $attr attributes were actually making it into your new return statement. So, I took the code above the return statement in the media.php and added it above your return statement – then it worked…

I added this above your return statement…


extract(shortcode_atts(array(
'id' => '',
'align' => 'alignnone',
'width' => '',
'caption' => ''
), $attr));

if ( 1 > (int) $width || empty($caption) )
return $content;

if ( $id ) $id = 'id="' . $id . '" ';

8 kristarella 27 January 2009 at 09:16

Sean — nice! I wondered about that. Some of the combos I tried removed the image as well, so I thought the attrs must have been working, but I guess not. ;)
Thanks for the question. It was a learning experience for me!

9 Don Komarechka 30 January 2009 at 03:28

Thanks very much for sharing this! Although I have a light programming background, I’m unlikely to try this myself unless it gets added to a plugin (or becomes one itself). I geotag most of my out-of-home photos, and that little bit of information I’ve always wanted to share with others. Having the link to google maps is great too – but in my mind I want it to go one step further.

Similar to what Google’s own Picasa Web Albums does, put a thumbnail on the world map. It would be fantastic to have a small little map showing exactly where the photo was taken, embedded beside or below the image itself.

Another useful extension of this idea would be to somehow query google maps or a similar service to provide town/city, province/state, and country information. GPS info is a little cryptic to look at just off the bat, imagine it says this instead: Location: NSW, Australia (33° 52.37? 0? S 151° 9.06? 0? E).

Food for thought, keep up the great work!

10 kristarella 30 January 2009 at 08:51

Hey Don,

I’ve considered making it into a plugin, it’s definitely possible. I’m not too good with plugins though. I guess I need some practice to learn them better. Perhaps if I have time. :)

Yeah, the map is a good idea, I just didn’t have time, or necessarily the desire, to implement it. It has been done with the code (or inspiration) from this tute though at sleepingmonkey.co.uk.

Cheers!

11 Sean 2 February 2009 at 05:20

I’ve got this working, but I’ve noticed that it doesn’t work if there’s more than 1 photo in a post. See this example…. http://seanpopke.com/blog/?p=443

Photos that have a blue globe in the caption indicate geotag information (which you can click on the bring up a map)

In this post, only the first and last photos actually have geotag information, but all photos are taking on the geotag information of the last photo. Any ideas on how to make this work with multiple photos in one post?

Thanks!!!

12 kristarella 2 February 2009 at 09:57

Sean — It’s not only that they’re all taking on the location data, but they’re all taking on data from the last image in San Francisco.

I think it’s most likely a problem with the loop needed to call the image meta. I always wondered what I would do if I wanted to post more than one photo to a photoblog post! I’m can’t promise I’ll be able to help, but if I am able to help I think I’ll need to see your code regarding the images and metadata. Can you post it here or in a pastebin?

13 Sean 2 February 2009 at 12:03

Sure, here is the code I’m using in my functions.php file. Let me know if you’d like to see anything else.


function geo_frac2dec($str) {
@list( $n, $d ) = explode( '/', $str );
if ( !empty($d) )
return $n / $d;
return $str;
}
function geo_pretty_fracs2dec($fracs) {
return geo_frac2dec($fracs[0]) . '° ' .
geo_frac2dec($fracs[1]) . '? ' .
geo_frac2dec($fracs[2]) . '? ';
}

function geo_single_fracs2dec($fracs) {
return geo_frac2dec($fracs[0]) +
geo_frac2dec($fracs[1]) / 60 +
geo_frac2dec($fracs[2]) / 3600;
}

function coords() {
global $post;
$args = array(
'post_type' => 'attachment',
'numberposts' => -1,
'post_status' => null,
'post_parent' => $post->ID
);
$attachments = get_posts($args);
foreach ($attachments AS $attachment) {
$imgid = $attachment->ID;
$imgmeta = wp_get_attachment_metadata( $imgid );

$latitude = $imgmeta['image_meta']['latitude'];
$longitude = $imgmeta['image_meta']['longitude'];
$lat_ref = $imgmeta['image_meta']['latitude_ref'];
$lng_ref = $imgmeta['image_meta']['longitude_ref'];
$lat = geo_single_fracs2dec($latitude);
$lng = geo_single_fracs2dec($longitude);
if ($lat_ref == 'S') { $neg_lat = '-'; } else { $neg_lat = ''; }
if ($lng_ref == 'W') { $neg_lng = '-'; } else { $neg_lng = ''; }

if ($latitude != 0 && $longitude != 0) {
return ' ';
}
else {

return '';
}
}
}

function add_to_caption($deprecated, $attr, $content) {
$coords = coords();

extract(shortcode_atts(array(
'id' => '',
'align' => 'alignnone',
'width' => '',
'caption' => ''
), $attr));

if ( 1 > (int) $width || empty($caption) )
return $content;

if ( $id ) $id = 'id="' . $id . '" ';

return ''
. $content . '' . $coords . '' . $caption . '';

}
add_filter('img_caption_shortcode', 'add_to_caption', 10, 3);

14 Sean 2 February 2009 at 12:06

Actually, that didn’t paste well. Try this instead…

http://pastebin.com/m36d5310b

15 kristarella 3 February 2009 at 09:54

Sean — the only idea I had was to try to condense the functions together so that you’re using the same ID when you call the location data and rewrite the caption, but I didn’t get it to work with the playing that I did. I’ve gotta get back to work though. Let me know if you make any progress and I’ll have another look if I get the chance.

16 Adam Petrie 2 March 2009 at 12:25

Kristarella,

Thank you so much for this post. It is incredibly informative and easy to follow. I’ve managed to implement this code in less than an hour and it works perfectly.

Cheers!

17 kristarella 2 March 2009 at 15:06

Thanks for the feedback Adam! Glad you found it useful. I’m hoping to make a light plugin to get WordPress to extract the extra EXIF so we don’t have to edit image.php every time WordPress upgrades, haven’t quite got it going yet though.

18 Khürt Williams 2 May 2009 at 16:15

I’m pretty clueless. I got the code to add exit data from the image placed in my custom_functions.php file (I’m using Thesis 1.5). I know PHP, I just don’t know Wordpress.
I added this hook:
add_action(‘thesis_hook_after_post’, ‘insert_exif’);
but it produces strange results.

19 Khürt Williams 2 May 2009 at 16:16
20 kristarella 2 May 2009 at 16:23

Khürt — Looks like a normal result to me if you have three images attached to that post. I only post one image at a time and have one image attached to each photoblog post so I set the numberposts in the custom query for the attachments to 1.

21 Khürt Williams 3 May 2009 at 11:53

Got it!!! Thanks.

22 Adam Dorrell 10 December 2009 at 00:52

Thanks for the tips. Well done for putting it all together! Saved me a few hours of work on getting the data out. Did you work out a way to put multiple points on google maps?

23 kristarella 10 December 2009 at 08:58

Adam — I haven’t tried to put multiple points on a map, it may just be a mater of figuring out the URL to do so.

You might be interested in my plugin Thesography which enables the addition of geo data to the database upon image upload and makes it easier to display EXIF in posts.

Leave a Comment

You can use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Want to show PHP or XHTML code in your comment? Encode it first so that WordPress doesn't strip it away.

Read the comment policy.

Previous post:

Next post: