PHP extract GPS EXIF data

PhpMetadataGpsExif

Php Problem Overview


I would like to extract the GPS EXIF tag from pictures using php. I'm using the exif_read_data() that returns a array of all tags + data :

GPS.GPSLatitudeRef: N
GPS.GPSLatitude:Array ( [0] => 46/1 [1] => 5403/100 [2] => 0/1 ) 
GPS.GPSLongitudeRef: E
GPS.GPSLongitude:Array ( [0] => 7/1 [1] => 880/100 [2] => 0/1 ) 
GPS.GPSAltitudeRef: 
GPS.GPSAltitude: 634/1

I don't know how to interpret 46/1 5403/100 and 0/1 ? 46 might be 46° but what about the rest especially 0/1 ?

angle/1 5403/100 0/1

What is this structure about ?

How to convert them to "standard" ones (like 46°56′48″N 7°26′39″E from wikipedia) ? I would like to pass thoses coordinates to the google maps api to display the pictures positions on a map !

Php Solutions


Solution 1 - Php

This is my modified version. The other ones didn't work for me. It will give you the decimal versions of the GPS coordinates.

The code to process the EXIF data:

$exif = exif_read_data($filename);
$lon = getGps($exif["GPSLongitude"], $exif['GPSLongitudeRef']);
$lat = getGps($exif["GPSLatitude"], $exif['GPSLatitudeRef']);
var_dump($lat, $lon);

Prints out in this format:

float(-33.8751666667)
float(151.207166667)

Here are the functions:

function getGps($exifCoord, $hemi) {
   
    $degrees = count($exifCoord) > 0 ? gps2Num($exifCoord[0]) : 0;
    $minutes = count($exifCoord) > 1 ? gps2Num($exifCoord[1]) : 0;
    $seconds = count($exifCoord) > 2 ? gps2Num($exifCoord[2]) : 0;

    $flip = ($hemi == 'W' or $hemi == 'S') ? -1 : 1;

    return $flip * ($degrees + $minutes / 60 + $seconds / 3600);

}

function gps2Num($coordPart) {

    $parts = explode('/', $coordPart);

    if (count($parts) <= 0)
        return 0;

    if (count($parts) == 1)
        return $parts[0];

    return floatval($parts[0]) / floatval($parts[1]);
}

Solution 2 - Php

This is a refactored version of Gerald Kaszuba's code (currently the most widely accepted answer). The result should be identical, but I've made several micro-optimizations and combined the two separate functions into one. In my benchmark testing, this version shaved about 5 microseconds off the runtime, which is probably negligible for most applications, but might be useful for applications which involve a large number of repeated calculations.

$exif = exif_read_data($filename);
$latitude = gps($exif["GPSLatitude"], $exif['GPSLatitudeRef']);
$longitude = gps($exif["GPSLongitude"], $exif['GPSLongitudeRef']);

function gps($coordinate, $hemisphere) {
  if (is_string($coordinate)) {
    $coordinate = array_map("trim", explode(",", $coordinate));
  }
  for ($i = 0; $i < 3; $i++) {
    $part = explode('/', $coordinate[$i]);
    if (count($part) == 1) {
      $coordinate[$i] = $part[0];
    } else if (count($part) == 2) {
      $coordinate[$i] = floatval($part[0])/floatval($part[1]);
    } else {
      $coordinate[$i] = 0;
    }
  }
  list($degrees, $minutes, $seconds) = $coordinate;
  $sign = ($hemisphere == 'W' || $hemisphere == 'S') ? -1 : 1;
  return $sign * ($degrees + $minutes/60 + $seconds/3600);
}

Solution 3 - Php

According to http://en.wikipedia.org/wiki/Geotagging, ( [0] => 46/1 [1] => 5403/100 [2] => 0/1 ) should mean 46/1 degrees, 5403/100 minutes, 0/1 seconds, i.e. 46°54.03′0″N. Normalizing the seconds gives 46°54′1.8″N.

This code below should work, as long as you don't get negative coordinates (given that you get N/S and E/W as a separate coordinate, you shouldn't ever have negative coordinates). Let me know if there is a bug (I don't have a PHP environment handy at the moment).

//Pass in GPS.GPSLatitude or GPS.GPSLongitude or something in that format
function getGps($exifCoord)
{
  $degrees = count($exifCoord) > 0 ? gps2Num($exifCoord[0]) : 0;
  $minutes = count($exifCoord) > 1 ? gps2Num($exifCoord[1]) : 0;
  $seconds = count($exifCoord) > 2 ? gps2Num($exifCoord[2]) : 0;
  
  //normalize
  $minutes += 60 * ($degrees - floor($degrees));
  $degrees = floor($degrees);
  
  $seconds += 60 * ($minutes - floor($minutes));
  $minutes = floor($minutes);
  
  //extra normalization, probably not necessary unless you get weird data
  if($seconds >= 60)
  {
    $minutes += floor($seconds/60.0);
    $seconds -= 60*floor($seconds/60.0);
  }
  
  if($minutes >= 60)
  {
    $degrees += floor($minutes/60.0);
    $minutes -= 60*floor($minutes/60.0);
  }
  
  return array('degrees' => $degrees, 'minutes' => $minutes, 'seconds' => $seconds);
}

function gps2Num($coordPart)
{
  $parts = explode('/', $coordPart);
  
  if(count($parts) <= 0)// jic
    return 0;
  if(count($parts) == 1)
    return $parts[0];
  
  return floatval($parts[0]) / floatval($parts[1]);
}

Solution 4 - Php

I know this question has been asked a long time ago, but I came across it while searching in google and the solutions proposed here did not worked for me. So, after further searching, here is what worked for me.

I'm putting it here so that anybody who comes here through some googling, can find different approaches to solve the same problem:

function triphoto_getGPS($fileName, $assoc = false)
{
    //get the EXIF
    $exif = exif_read_data($fileName);

    //get the Hemisphere multiplier
    $LatM = 1; $LongM = 1;
    if($exif["GPSLatitudeRef"] == 'S')
    {
	$LatM = -1;
    }
    if($exif["GPSLongitudeRef"] == 'W')
    {
	$LongM = -1;
    }

    //get the GPS data
    $gps['LatDegree']=$exif["GPSLatitude"][0];
    $gps['LatMinute']=$exif["GPSLatitude"][1];
    $gps['LatgSeconds']=$exif["GPSLatitude"][2];
    $gps['LongDegree']=$exif["GPSLongitude"][0];
    $gps['LongMinute']=$exif["GPSLongitude"][1];
    $gps['LongSeconds']=$exif["GPSLongitude"][2];

    //convert strings to numbers
    foreach($gps as $key => $value)
    {
	$pos = strpos($value, '/');
	if($pos !== false)
	{
	    $temp = explode('/',$value);
	    $gps[$key] = $temp[0] / $temp[1];
	}
    }

    //calculate the decimal degree
    $result['latitude'] = $LatM * ($gps['LatDegree'] + ($gps['LatMinute'] / 60) + ($gps['LatgSeconds'] / 3600));
    $result['longitude'] = $LongM * ($gps['LongDegree'] + ($gps['LongMinute'] / 60) + ($gps['LongSeconds'] / 3600));

    if($assoc)
    {
	return $result;
    }

    return json_encode($result);
}

Solution 5 - Php

This is an old question but felt it could use a more eloquent solution (OOP approach and lambda to process the fractional parts)

/**
 * Example coordinate values
 *
 * Latitude - 49/1, 4/1, 2881/100, N
 * Longitude - 121/1, 58/1, 4768/100, W
 */
protected function _toDecimal($deg, $min, $sec, $ref) {

	$float = function($v) {
		return (count($v = explode('/', $v)) > 1) ? $v[0] / $v[1] : $v[0];
	};
	
	$d = $float($deg) + (($float($min) / 60) + ($float($sec) / 3600));
	return ($ref == 'S' || $ref == 'W') ? $d *= -1 : $d;
}


public function getCoordinates() {

    $exif = @exif_read_data('image_with_exif_data.jpeg');

    $coord = (isset($exif['GPSLatitude'], $exif['GPSLongitude'])) ? implode(',', array(
        'latitude' => sprintf('%.6f', $this->_toDecimal($exif['GPSLatitude'][0], $exif['GPSLatitude'][1], $exif['GPSLatitude'][2], $exif['GPSLatitudeRef'])),
	    'longitude' => sprintf('%.6f', $this->_toDecimal($exif['GPSLongitude'][0], $exif['GPSLongitude'][1], $exif['GPSLongitude'][2], $exif['GPSLongitudeRef']))
    )) : null;

}

Solution 6 - Php

The code I've used in the past is something like (in reality, it also checks that the data is vaguely valid):

// Latitude
$northing = -1;
if( $gpsblock['GPSLatitudeRef'] && 'N' == $gpsblock['GPSLatitudeRef'] )
{
    $northing = 1;
}

$northing *= defraction( $gpsblock['GPSLatitude'][0] ) + ( defraction($gpsblock['GPSLatitude'][1] ) / 60 ) + ( defraction( $gpsblock['GPSLatitude'][2] ) / 3600 );

// Longitude
$easting = -1;
if( $gpsblock['GPSLongitudeRef'] && 'E' == $gpsblock['GPSLongitudeRef'] )
{
    $easting = 1;
}

$easting *= defraction( $gpsblock['GPSLongitude'][0] ) + ( defraction( $gpsblock['GPSLongitude'][1] ) / 60 ) + ( defraction( $gpsblock['GPSLongitude'][2] ) / 3600 );

Where you also have:

function defraction( $fraction )
{
    list( $nominator, $denominator ) = explode( "/", $fraction );

    if( $denominator )
    {
        return ( $nominator / $denominator );
    }
    else
    {
        return $fraction;
    }
}

Solution 7 - Php

To get the altitude value, you can use the following 3 lines:

$data     = exif_read_data($path_to_your_photo, 0, TRUE);
$alt      = explode('/', $data["GPS"]["GPSAltitude"]);
$altitude = (isset($alt[1])) ? ($alt[0] / $alt[1]) : $alt[0];

Solution 8 - Php

In case you need a function to read Coordinates from Imagick Exif here we go, I hope it saves you time. Tested under PHP 7.

function create_gps_imagick($coordinate, $hemi) {

  $exifCoord = explode(', ', $coordinate);

  $degrees = count($exifCoord) > 0 ? gps2Num($exifCoord[0]) : 0;
  $minutes = count($exifCoord) > 1 ? gps2Num($exifCoord[1]) : 0;
  $seconds = count($exifCoord) > 2 ? gps2Num($exifCoord[2]) : 0;

  $flip = ($hemi == 'W' or $hemi == 'S') ? -1 : 1;

  return $flip * ($degrees + $minutes / 60 + $seconds / 3600);

}

function gps2Num($coordPart) {

    $parts = explode('/', $coordPart);

    if (count($parts) <= 0)
        return 0;

    if (count($parts) == 1)
        return $parts[0];

    return floatval($parts[0]) / floatval($parts[1]);
}

Solution 9 - Php

I'm using the modified version from Gerald Kaszuba but it's not accurate. so i change the formula a bit.

from:

return $flip * ($degrees + $minutes / 60);

changed to:

return floatval($flip * ($degrees +($minutes/60)+($seconds/3600)));

It works for me.

Solution 10 - Php

This is a javascript port of the PHP-code posted @Gerald above. This way you can figure out the location of an image without ever uploading the image, in conjunction with libraries like dropzone.js and Javascript-Load-Image

define(function(){

	function parseExif(map) {
		var gps = {
			lng : getGps(map.get('GPSLongitude'), data.get('GPSLongitudeRef')),
			lat : getGps(map.get('GPSLatitude'), data.get('GPSLatitudeRef'))
		}
		return gps;
	}

	function getGps(exifCoord, hemi) {
		var degrees = exifCoord.length > 0 ? parseFloat(gps2Num(exifCoord[0])) : 0,
			minutes = exifCoord.length > 1 ? parseFloat(gps2Num(exifCoord[1])) : 0,
			seconds = exifCoord.length > 2 ? parseFloat(gps2Num(exifCoord[2])) : 0,
			flip = (/w|s/i.test(hemi)) ? -1 : 1;
		return flip * (degrees + (minutes / 60) + (seconds / 3600));
	}

	function gps2Num(coordPart) {
		var parts = (""+coordPart).split('/');
		if (parts.length <= 0) {
			return 0;
		}
		if (parts.length === 1) {
			return parts[0];
		}
		return parts[0] / parts[1];
	}		

	return {
		parseExif: parseExif
	};

});

Solution 11 - Php

short story. First part N Leave the grade multiply the minutes with 60 devide the seconds with 100. count the grades,minuts and seconds with eachother.

Second part E Leave the grade multiply the minutes with 60 devide the seconds with ...1000 cöunt the grades, minutes and seconds with each other

Solution 12 - Php

i have seen nobody mentioned this: <https://pypi.python.org/pypi/LatLon/1.0.2>

from fractions import Fraction
from LatLon import LatLon, Longitude, Latitude

latSigned = GPS.GPSLatitudeRef == "N" ? 1 : -1
longSigned = GPS.GPSLongitudeRef == "E" ? 1 : -1

latitudeObj = Latitude(
              degree = float(Fraction(GPS.GPSLatitude[0]))*latSigned , 
              minute = float(Fraction(GPS.GPSLatitude[0]))*latSigned , 
              second = float(Fraction(GPS.GPSLatitude[0])*latSigned)
longitudeObj = Latitude(
              degree = float(Fraction(GPS.GPSLongitude[0]))*longSigned , 
              minute = float(Fraction(GPS.GPSLongitude[0]))*longSigned , 
              second = float(Fraction(GPS.GPSLongitude[0])*longSigned )
Coordonates = LatLon(latitudeObj, longitudeObj )

now using the Coordonates objecct you can do what you want: Example:

(like 46°56′48″N 7°26′39″E from wikipedia)

print Coordonates.to_string('d%°%m%′%S%″%H')

You than have to convert from ascii, and you are done:

('5\xc2\xb052\xe2\x80\xb259.88\xe2\x80\xb3N', '162\xc2\xb04\xe2\x80\xb259.88\xe2\x80\xb3W')

and than printing example:

print "Latitude:" + Latitude.to_string('d%°%m%′%S%″%H')[0].decode('utf8')

>> Latitude: 5°5259.88″N
 

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKamiView Question on Stackoverflow
Solution 1 - PhpgakView Answer on Stackoverflow
Solution 2 - PhpDavid JonesView Answer on Stackoverflow
Solution 3 - PhpKipView Answer on Stackoverflow
Solution 4 - PhpHassan Al-JeshiView Answer on Stackoverflow
Solution 5 - PhpTrent RenshawView Answer on Stackoverflow
Solution 6 - PhpRowland ShawView Answer on Stackoverflow
Solution 7 - PhpUstym UkhmanView Answer on Stackoverflow
Solution 8 - PhpJeromeView Answer on Stackoverflow
Solution 9 - PhpsapewadyView Answer on Stackoverflow
Solution 10 - PhpAksel N.View Answer on Stackoverflow
Solution 11 - Phpe anderView Answer on Stackoverflow
Solution 12 - PhpThanatos11thView Answer on Stackoverflow