抽取PHP中的GPS EXIF数据

57

我希望使用PHP从图片中提取GPS EXIF标签。 我正在使用exif_read_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

我不知道如何解释46/1 5403/100和0/1?46可能是46度,但其余的特别是0/1呢?

angle/1 5403/100 0/1

这个结构是关于什么的?

如何将它们转换为“标准”格式(例如维基百科上的46°56′48″N 7°26′39″E)?我想将这些坐标传递给Google Maps API,以在地图上显示图片位置!


1
@Kami:我更新了我的回答并添加了一些代码。 - Kip
12个回答

102

这是我修改过的版本。其他的都对我没有用。它将为您提供GPS坐标的十进制版本。

处理EXIF数据的代码:

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

以这种格式输出:

float(-33.8751666667)
float(151.207166667)

以下是函数列表:

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]);
}

3
我发现PHP的最新版本在exif对象中增加了一个额外的数组层,需要像这样调用它们:getGps($exif['GPS']["GPSLongitude"], $exif['GPS']['GPSLongitudeRef'])。 - Orbiting Eden
@OrbitingEden 这种行为是由第三个参数启用的。默认情况下是禁用的。 - Max Tsepkov

32

这是 Gerald Kaszuba 代码的重构版本(目前最广泛接受的答案)。结果应该是相同的,但我进行了几个微小的优化,并将两个独立的函数合并为一个。在我的基准测试中,这个版本的运行时间缩短了约5微秒,对于大多数应用程序来说可能是可以忽略不计的,但对于涉及大量重复计算的应用程序可能会有用。

$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);
}

2
这个。我已经盲目地从Stack Overflow复制并粘贴PHP exif代码片段好几天了,而没有进行任何自己的研究。(真的)它们都是从有点错误到非常非常错误的范围内。产生了不同程度的不正确数据。这一个有效。我所做的唯一一件事就是验证输出与已知的良好来源相匹配。而这一个是100%正确的。10/10会再次复制粘贴。 - Adam
1
谢谢David(和Gerald)。你们的修改是迄今为止最准确的,并且可以直接100%地工作。我想指出的唯一一件事是,exif_read_data()现在以$ exif ['GPS'] ['GPSLatitude']的形式返回结果 - 因此您可能需要稍微修改调用代码。 - miCRoSCoPiC_eaRthLinG

23
根据http://en.wikipedia.org/wiki/Geotagging( [0] => 46/1 [1] => 5403/100 [2] => 0/1 )应该表示46/1度,5403/100分,0/1秒,即46°54.03′0″N。规范化后的结果为46°54′1.8″N。只要不出现负坐标(因为N/S和E/W是单独的坐标,所以不应该出现负坐标),下面的代码应该可以正常工作。如果有错误,请让我知道(我目前没有PHP环境可用)。
//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]);
}

6

这是一个老问题,但我认为它需要更优雅的解决方案(采用面向对象编程方法和lambda处理小数部分)

/**
 * 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;

}

5

我知道这个问题很久以前就被问过了,但是当我在谷歌上搜索时发现这里提出的解决方案对我没有用。所以,在进一步搜索后,我找到了解决方法。

我将它放在这里,以便通过谷歌搜索到这里的任何人都可以找到解决相同问题的不同方法:

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);
}

我使用了这个答案,非常好!只需要记住,新的PHP使exif数组具有自己的GPS键。例如 $gps['LatDegree']=$exif["GPSLatitude"][0]; 变成了 $gps['LatDegree']=$exif["GPS"]["GPSLatitude"][0]; - Osmium USA
1
这个解决方案对我很有效,我将这个答案制作成了一个composer包 - 做了一些更改。你可以在这里找到它:https://github.com/diversen/gps-from-exif - dennis

2
为了获取高度值,您可以使用以下3行代码:
$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];

2

我过去使用的代码类似于以下内容(实际上,它还会检查数据是否大致有效):

// 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 );

还有以下内容:

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

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

有什么想法为什么这个被踩了吗?如果需要,我很乐意修复我的代码。 - Rowland Shaw

1
如果您需要一个从Imagick Exif读取坐标的函数,这里有一个,我希望它能节省您的时间。在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]);
}

0

这是@Gerald上面发布的PHP代码的JavaScript版本。这样,您可以在不上传图像的情况下找出图像的位置,结合dropzone.jsJavascript-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
    };

});

0

我正在使用Gerald Kaszuba的修改版本,但它不够准确。所以我稍微改了一下公式。

原文:

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

改为:

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

对我来说是有效的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接