Определение координат места съемки из EXIF на PHP

  2016-06-11 06:06:54

Окончательно решив аппаратную задачу записи GPS-координат в файлы, осталось решить программную задачу их чтения. Вариант на Ассемблере у меня уже давно есть, а вот на PHP нормальных решений не было. Мне стало интересно самостоятельно распарсить дополнительные секции EXIF, основываясь на их спецификации.

  1. // Имя файла для обработки
  2. $file_path='DSC_1234.JPG';
  3.  
  4. // Массив с GPS-данными
  5. $gps_data=array();
  6.  
  7. $f=fopen($file_path,'r');
  8. $tmp=fread($f2);
  9. if ($tmp==chr(0xFF).chr(0xD8)) {
  10.   $section_id_stop=array(0xFFD8,0xFFDB,0xFFC4,0xFFDD,0xFFC0,0xFFDA,0xFFD9);
  11.   while (!feof($f)) {
  12.     $tmp=unpack('n',fread($f,2));
  13.     $section_id=$tmp[1];
  14.     $tmp=unpack('n',fread($f,2));
  15.     $section_length=$tmp[1];
  16.  
  17.     // Началась секция данных, заканчиваем поиск
  18.     if (in_array($section_id$section_id_stop)) {
  19.         break;
  20.     }
  21.  
  22.     // Найдена EXIF-секция
  23.     if ($section_id==0xFFE1) {
  24.       $exif=fread($f,($section_length-2));
  25.  
  26.       // Это действительно секция EXIF?
  27.       if (substr($exif,0,4)=='Exif') {
  28.         // Определить порядок следования байт
  29.         switch (substr($exif,6,2)) {
  30.           case 'MM': {
  31.             $is_motorola=true;
  32.             $mask1='n';
  33.             $mask2='N';
  34.             break;
  35.           }
  36.           case 'II': {
  37.             $is_motorola=false;
  38.             $mask1='v';
  39.             $mask2='V';
  40.             break;
  41.           }
  42.         }
  43.         // Количество тегов
  44.         $tmp=unpack($mask2,substr($exif,10,4));
  45.         $offset_tags=$tmp[1];
  46.         $tmp=unpack($mask1,substr($exif,14,2));
  47.         $num_of_tags=$tmp[1];
  48.  
  49.         if ($num_of_tags==0) { return true; }
  50.  
  51.         $offset=$offset_tags+8;
  52.  
  53.         // Поискать тег GPSInfo
  54.         for ($i=0$i<$num_of_tags$i++) {
  55.           $tmp=unpack($mask1,substr($exif,$offset,2));
  56.           $tag_id=$tmp[1];
  57.           $tmp=unpack($mask2,substr($exif,$offset+8,4));
  58.           $value=$tmp[1];
  59.  
  60.           $offset+=12;
  61.  
  62.           // GPSInfo
  63.           if ($tag_id==0x8825) {
  64.             $gps_offset=$value+6;
  65.             // Количество GPS-тегов
  66.             $tmp=unpack($mask1,substr($exif,$gps_offset,2));
  67.             $num_of_gps_tags=$tmp[1];
  68.  
  69.             $offset=$gps_offset+2;
  70.  
  71.             if ($num_of_gps_tags>0) {
  72.               // Обработка GPS-тегов
  73.               for ($i=0$i<$num_of_gps_tags$i++) {
  74.                 $tmp=unpack($mask1,substr($exif,$offset,2));
  75.                 $tag_id=$tmp[1];
  76.                 $tmp=unpack($mask2,substr($exif,$offset+8,4));
  77.                 $value=$tmp[1];
  78.  
  79.                 // GPSLatitudeRef или GPSLongitudeRef
  80.                 if ($tag_id==0x0001 || $tag_id==0x0003) {
  81.                   $tmp=unpack('V',substr($exif,$offset+8,4));
  82.                   $value=$tmp[1];
  83.                   if ($value!=0) {
  84.                     if ($tag_id==0x0001) {
  85.                       $gps_data['GPSLatitudeRef']=chr($value);
  86.                     }
  87.                     else {
  88.                       $gps_data['GPSLongitudeRef']=chr($value);
  89.                     }
  90.                   }
  91.                 }
  92.                 // GPSLatitude или GPSLongitude
  93.                 if ($tag_id==0x0002 || $tag_id==0x0004) {
  94.                   $rational_offset=$value+6;
  95.                   $tmp=unpack($mask2,substr($exif,$rational_offset+4*0,4));
  96.                   $val1=$tmp[1];
  97.                   $tmp=unpack($mask2,substr($exif,$rational_offset+4*1,4));
  98.                   $div1=$tmp[1];
  99.                   $tmp=unpack($mask2,substr($exif,$rational_offset+4*2,4));
  100.                   $val2=$tmp[1];
  101.                   $tmp=unpack($mask2,substr($exif,$rational_offset+4*3,4));
  102.                   $div2=$tmp[1];
  103.                   $tmp=unpack($mask2,substr($exif,$rational_offset+4*4,4));
  104.                   $val3=$tmp[1];
  105.                   $tmp=unpack($mask2,substr($exif,$rational_offset+4*5,4));
  106.                   $div3=$tmp[1];
  107.                   if ($div1!=&& $div2!=&& $div3!=0) {
  108.                     $tmp=intval($val1/$div1).'.';
  109.                     $tmp.=intval(($val2/$div2/60+$val3/$div3/3600)*1000000);
  110.                     if ($tag_id==0x0002) {
  111.                       $gps_data['GPSLatitude']=$tmp;
  112.                     }
  113.                     else {
  114.                       $gps_data['GPSLongitude']=$tmp;
  115.                     }
  116.                   }
  117.                 }
  118.                 // GPSSatellites
  119.                 if ($tag_id==0x0008) {
  120.                   $tmp=intval(substr($exif,$offset+8,4));
  121.                   if ($tmp>0) {
  122.                     $gps_data['GPSSatellites']=$tmp;
  123.                   }
  124.                 }
  125.  
  126.                 $offset+=12;
  127.               }
  128.             }
  129.             break;
  130.           }
  131.         }
  132.       }
  133.     }
  134.     else {
  135.       // Пропустить секцию
  136.       fseek($f, ($section_length-2), SEEK_CUR);
  137.     }
  138.     // Тег GPSInfo найден
  139.     if (count($gps_data)!=0) { break; }
  140.   }
  141. }
  142. fclose($f);
  143.  
  144. // Данные GPS
  145. print_r($gps_data);

 

В файле значения координат хранятся в нескольких параметрах, но для удобства при обработке они автоматически пересчитываются и возвращаются уже в десятичных градусах. Используется обычная для этой операции формула:

Десятичные градусы = Градусы + Минуты/60 + Секунды/3600
Обратите внимание, что значение широты и долготы получается беззнаковым, поэтому для определения знака надо обязательно смотреть на значения GPSLatitudeRef и GPSLongitudeRef. Если направление широты равняется "S", то при установке метки на карте значение широты меняется на отрицательное, и аналогично, знак долготы меняется на минус, если направление долготы равно "W".

Приведенный в статье код можно очень легко модифицировать, чтобы извлекать не только GPS-координаты, но и любые другие данные из EXIF-секций графических файлов.