image_location.module: Automatically fills in Location Latitude and Longitude fields based on Exif data

lewiz - May 10, 2007 - 21:47
Project:Image
Version:5.x-1.7
Component:image.module
Category:bug report
Priority:normal
Assigned:Unassigned
Status:patch (code needs review)
Description

Hi guys,

I have cooked up this small module that works in conjunction with location.module and PHP's exif library to automatically fill the lat/lon fields.
Really handy for users with GMap module wanting to display some geocoded images.

I'll personally continue with updates to this as I'm using it for a project. It would be great to get this included with Image!

Thanks a lot.

AttachmentSize
image_location.patch3.64 KB

#1

drewish - May 10, 2007 - 21:55

how about != for clarity?

if (!$node->type == 'image') {

Is this still true with the latest releases? It should be updating them as it goes...

/* $node->images is out of date as image.module calls image_insert()
+ to move the image from the temp directory to the final location before
+ we get a chance to read the Exif data.
+ We always call image_load() (as opposed to just on insert) to update
+ the $node->images array as the user could upload a replacement image.
+       */

Why strlen? couldn't you just use if (!trim()) ?

if (!strlen(trim($node->locations[$index]['latitude'])) && !strlen(trim($node->locations[$index]['longitude']))) {

So this uses the pecl module?

// Exif GPS tags: http://search.cpan.org/~exiftool/Image-ExifTool-6.90/lib/Image/ExifTool/... ($exif = exif_read_data($image, 'GPS')) {

If so it really needs to be documented and I'd like to see a hook_enalble in the .install file that posts a warning if the pecl module isn't installed. Otherwise it'll be a support nightmare with people wanting to know why it doesn't work.

I'm sort of torn. It seems useful but because of the library requirement I wonder how many people would be able to use it.

#2

drewish - May 10, 2007 - 22:03
Status:patch (code needs review)» patch (code needs work)

+  if (isset($value)) {
+    eval("\$result = 1.0*$value;");
+  }

oh sweet jesus you're calling eval() for this? seriously? eval is evil

#3

lewiz - May 10, 2007 - 22:21

No, it uses PHP Exif stuff, i.e. PHP configured with --enable-exif. The link was to a list of all of the GPS fields used for lon/lat info.

I'll tidy up the bits you pointed out and add an install file to check for the exif module -- first time I've done this so I'm not sure what the procedure is ;)

#4

drewish - May 10, 2007 - 22:27

cool, well first off get rid of that eval!

take a look at hook_enable. it needs to go in a .install file.
i guess you could call method_exists() to see if the function is defined. you can report back to the user using drupal_set_message()

also, throw in a README.txt explaining what the module is and does, how to enable it, etc.

#5

lewiz - May 10, 2007 - 23:43
Status:patch (code needs work)» patch (code needs review)

Updated patch. This fixes the bits you pointed out.

I didn't mention earlier that current wonky stuff does happen with the $node->locations data. It currently is necessary to call image_load() before reading the Exif data.

The image_location.install does a check for exif_read_data(); if it can't find it, it notifies the user and automatically disables the module.

Bed for me now. Thanks :)

#6

lewiz - May 10, 2007 - 23:44

ffs, here's an actual patch :P

AttachmentSize
image_location_0.patch5.27 KB

#7

lewiz - May 11, 2007 - 23:56

Please change line 21 to:

if (($node->type != 'image') || (!variable_get('location_maxnum_image', 0))) {

This makes sure that locations have been enabled for the image node type.

Thanks.

#8

Scott Falconer - June 22, 2007 - 19:20

I think the conversion may not be negating S and W coordinates... 84°W should end up as -84. I'm not sure how to do this myself...I took a look at how wikipedia does it here and the code is:

$latfactor = 1.0 ;
$lonfactor = 1.0 ;
if (strtoupper($latNS) == "S") {
$latfactor = -1.0 ;
#$this->latdeg = -$this->latdeg;
}

if (strtoupper($lonEW) == "W") {
$lonfactor = -1.0 ;
#$this->londeg = -$this->londeg;
}

Other than that, it seems to work great.

#9

Scott Falconer - June 29, 2007 - 21:31

Ok, I think I got the S and W coordinates to work..not sure on how legit my code is, but it gets it done. If anyone has any more advice on making this a bit cleaner, let me know...

Starting and line 45:

foreach ($node->locations as $index => $location) {
// check if the user has manually entered a lat/lon
if (!trim($node->locations[$index]['latitude']) && !trim($node->locations[$index]['longitude'])) {
  // attempt to populate locations with Exif info
  // Exif GPS tags: http://search.cpan.org/~exiftool/Image-ExifTool-6.90/lib/Image/ExifTool/...   if ($exif = exif_read_data($image, 'GPS')) {
    if (isset($exif['GPSLatitude']) && isset($exif['GPSLongitude'])) {
      // FIXME Future work: proper validity checking
    if (strtoupper($exif['GPSLongitudeRef']) == "W") {
      $node->locations[$index]['longitude'] = "-" . _image_location_dms_to_dd($exif['GPSLongitude']);
   }
  
   else {
      $node->locations[$index]['longitude'] =  _image_location_dms_to_dd($exif['GPSLongitude']);
      }
     
      if (strtoupper($exif['GPSLatitudeRef']) == "S") {
      $node->locations[$index]['latitude'] = "-" . _image_location_dms_to_dd($exif['GPSLatitude']);
   }
   else {
      $node->locations[$index]['latitude'] = _image_location_dms_to_dd($exif['GPSLatitude']);
      }

   }
  }
}
      }
    break;
  }
}

#10

raintonr - August 24, 2007 - 00:01

Subscribing.

Sounds useful. Could/should this be added to the Exif module somehow though? I'd be interested in seeing something that scans all existing images for when this is first installed.

#11

raintonr - September 24, 2007 - 11:53

I couldn't see where the patches on this thread were supposed to fit so hacked about with the EXIF.module.

Add this into the case in exif_nodeapi. Note that there is already a check in that code to ignore anything but images so it wasn't needed.

* For updates, extract the location from EXIF and save if none already given */

    case 'update':
      // Don't bother if no locations in array
      if (!array_key_exists('location', $node)) break;

      // Don't bother if location already set
      if ($node->location['lat'] != 0) break;

      // Access the file's EXIF data. Simply break when no EXIF data is found.
      $file = file_create_path($node->images['_original']);
      if (!file_exists($file)) {
        watchdog('exif', t('Image %file not found.', array('%file' => $file)), WATCHDOG_WARNING);
      }
      if (!is_file($file)) break;

      $jpeg = new PelJpeg($file);
      $exif = $jpeg->getExif();
      if (!$exif) break;
      $tiff = $exif->getTiff();
      if (!$tiff) break;
      $ifd0 = $tiff->getIfd();
      if (!$ifd0) break;
      $gps = $ifd0->getSubIfd(PelIfd::GPS);
      if (!$gps) break;

      function _DMS2D($entry) {
        $value = $entry->getValue();
        $degrees = $value[0][0] / $value[0][1];
        $minutes = $value[1][0] / $value[1][1];
        $seconds = $value[2][0] / $value[2][1];

        return $degrees + $minutes/60 + $seconds/3600;
      }

      $entry = $gps->getEntry(PelTag::GPS_LATITUDE);
      if (!$entry) break;
      $GPS_LATITUDE = _DMS2D($entry);
      $entry = $gps->getEntry(PelTag::GPS_LATITUDE_REF);
      if (!$entry) break;
      if ($entry->getValue() == 'S') $GPS_LATITUDE = -$GPS_LATITUDE;

      $entry = $gps->getEntry(PelTag::GPS_LONGITUDE);
      if (!$entry) break;
      $GPS_LONGITUDE = _DMS2D($entry);
      $entry = $gps->getEntry(PelTag::GPS_LONGITUDE_REF);
      if (!$entry) break;
      if ($entry->getValue() == 'W') $GPS_LONGITUDE = -$GPS_LONGITUDE;

      $node->locations[0]['latitude'] = $GPS_LATITUDE;
      $node->locations[0]['longitude'] = $GPS_LONGITUDE;
      $node->locations['lat'] = $GPS_LATITUDE;
      $node->locations['lon'] = $GPS_LONGITUDE;
      break;

Works here. Could do with more checking of course and perhaps some cleanup, but think this is a more appropriate place for this kind of code.

#12

mawds - April 7, 2008 - 20:28
Version:6.x-1.x-dev» 5.x-1.7
Category:feature request» bug report

The module doesn't work with image_import:

* warning: Invalid argument supplied for foreach() in /htdocs/modules/image/contrib/image_location/image_location.module on line 46.
* warning: Invalid argument supplied for foreach() in /mawdshome/htdocs/modules/location/location.module on line 857.

Can anyone suggest a workaround/bug fix?

Thanks

#13

elyobo - May 19, 2008 - 12:49

I'd also be very interested in using it if it could be made to work with the image import module. Sounds like a great idea :)

#14

rubenk - June 17, 2008 - 04:45

bump. i too would like to see this integrated with all the upcoming geotagging support from things like the iphone and other mobile phones.

#15

elyobo - June 21, 2008 - 10:33

Quick code snippet for reading GPS data from EXIF information... It appears that it's stored in degrees, minutes, seconds format, with the value stored as a fraction, e.g. 121.22 would be stored as 12122/100. The following snippet converts the DMS format to seconds after extracting it from the GPS data, if you don't happen to have the EXIF module to do your converting for you (EXIF requires PEL requires PHP5, which my server isn't running...).

function doEquation($e) {
  $p = split("/",$e);
  return $p[0]/$p[1];
}

function dmsToDecimal($c,$r) {
  if(!is_array($c)) {
    return;
  }
  $val = doEquation($c[0]) + doEquation($c[1])/60 + doEquation($c[2])*100/60;
  if(ucwords($r) == "S" || ucwords($r) == "W") {
    $val *= -1;
  }
  return $val;
}

$filename = "rimg0166.jpg";
$res = exif_read_data($filename, "GPS");
$lat = dmsToDecimal($res[GPSLatitude], $res[GPSLatitudeRef]);
$long = dmsToDecimal($res[GPSLongitude], $res[GPSLongitudeRef]);

print "$lat,$long\n";

I'll look into integrating this into the code. I'm working with D5, so probably won't be supplying any patches for D6/D7.

#16

elyobo - June 21, 2008 - 10:46

Bah, I see that the original patch already included this functionality. Ignore the above.

#17

elyobo - June 21, 2008 - 18:11

#18

elyobo - June 21, 2008 - 18:11

I've modified the patch so that it
- deals with N and W correctly (negative decimal numbers)
- works with image import - this requires a trivial change to image.module

See the attached patch.

In order for the image import to work, the location assignment has been split into a second function which is called from the image module (if image_location is enabled). To activate it, the following lines need to be added into image_create_node_from in image.module.

  if(module_exists("image_location")) {
    add_exif_location($node, $filepath);
  }

This should be added beneath the section assigning the taxonomy, i.e. beneath this snippet

  if (module_exists('taxonomy')) {
    $node->taxonomy = $taxonomy;
  }

AttachmentSize
image_location.patch5.99 KB
 
 

Drupal is a registered trademark of Dries Buytaert.