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

Определение координат места съемки из EXIF на PHP
Окончательно решив аппаратную задачу записи GPS-координат в файлы, осталось решить программную задачу их чтения. Вариант на Ассемблере у меня уже давно есть, а вот на PHP нормальных решений не было. Причину, по которой я не пользуюсь штатными функциями PHP, я изложил в предыдущей статье, повторяться не буду. К тому же мне стало интересно самостоятельно распарсить дополнительные секции EXIF, основываясь на их спецификации.
В результате у меня получился вот такой код, который находит и разбирает EXIF-секцию в файле формата JPEG, а затем извлекает из нее GPS-координаты места съемки и до кучи количество спутников, если такая информация в файле есть.
Code (PHP) : Убрать нумерацию
- // Имя файла для обработки
- $file_path='DSC_1234.JPG';
- // Массив с GPS-данными
- $gps_data=array();
- $f=fopen($file_path,'r');
- $tmp=fread($f, 2);
- if ($tmp==chr(0xFF).chr(0xD8)) {
- $section_id_stop=array(0xFFD8,0xFFDB,0xFFC4,0xFFDD,0xFFC0,0xFFDA,0xFFD9);
- while (!feof($f)) {
- $tmp=unpack('n',fread($f,2));
- $section_id=$tmp[1];
- $tmp=unpack('n',fread($f,2));
- $section_length=$tmp[1];
- // Началась секция данных, заканчиваем поиск
- if (in_array($section_id, $section_id_stop)) {
- break;
- }
- // Найдена EXIF-секция
- if ($section_id==0xFFE1) {
- $exif=fread($f,($section_length-2));
- // Это действительно секция EXIF?
- if (substr($exif,0,4)=='Exif') {
- // Определить порядок следования байт
- switch (substr($exif,6,2)) {
- case 'MM': {
- $is_motorola=true;
- $mask1='n';
- $mask2='N';
- break;
- }
- case 'II': {
- $is_motorola=false;
- $mask1='v';
- $mask2='V';
- break;
- }
- }
- // Количество тегов
- $tmp=unpack($mask2,substr($exif,10,4));
- $offset_tags=$tmp[1];
- $tmp=unpack($mask1,substr($exif,14,2));
- $num_of_tags=$tmp[1];
- if ($num_of_tags==0) { return true; }
- $offset=$offset_tags+8;
- // Поискать тег GPSInfo
- for ($i=0; $i<$num_of_tags; $i++) {
- $tmp=unpack($mask1,substr($exif,$offset,2));
- $tag_id=$tmp[1];
- $tmp=unpack($mask2,substr($exif,$offset+8,4));
- $value=$tmp[1];
- $offset+=12;
- // GPSInfo
- if ($tag_id==0x8825) {
- $gps_offset=$value+6;
- // Количество GPS-тегов
- $tmp=unpack($mask1,substr($exif,$gps_offset,2));
- $num_of_gps_tags=$tmp[1];
- $offset=$gps_offset+2;
- if ($num_of_gps_tags>0) {
- // Обработка GPS-тегов
- for ($i=0; $i<$num_of_gps_tags; $i++) {
- $tmp=unpack($mask1,substr($exif,$offset,2));
- $tag_id=$tmp[1];
- $tmp=unpack($mask2,substr($exif,$offset+8,4));
- $value=$tmp[1];
- // GPSLatitudeRef или GPSLongitudeRef
- if ($tag_id==0x0001 || $tag_id==0x0003) {
- $tmp=unpack('V',substr($exif,$offset+8,4));
- $value=$tmp[1];
- if ($value!=0) {
- if ($tag_id==0x0001) {
- $gps_data['GPSLatitudeRef']=chr($value);
- }
- else {
- $gps_data['GPSLongitudeRef']=chr($value);
- }
- }
- }
- // GPSLatitude или GPSLongitude
- if ($tag_id==0x0002 || $tag_id==0x0004) {
- $rational_offset=$value+6;
- $tmp=unpack($mask2,substr($exif,$rational_offset+4*0,4));
- $val1=$tmp[1];
- $tmp=unpack($mask2,substr($exif,$rational_offset+4*1,4));
- $div1=$tmp[1];
- $tmp=unpack($mask2,substr($exif,$rational_offset+4*2,4));
- $val2=$tmp[1];
- $tmp=unpack($mask2,substr($exif,$rational_offset+4*3,4));
- $div2=$tmp[1];
- $tmp=unpack($mask2,substr($exif,$rational_offset+4*4,4));
- $val3=$tmp[1];
- $tmp=unpack($mask2,substr($exif,$rational_offset+4*5,4));
- $div3=$tmp[1];
- if ($div1!=0 && $div2!=0 && $div3!=0) {
- $tmp=round(($val1/$div1+$val2/$div2/60+$val3/$div3/3600),6);
- if ($tag_id==0x0002) {
- $gps_data['GPSLatitude']=$tmp;
- }
- else {
- $gps_data['GPSLongitude']=$tmp;
- }
- }
- }
- // GPSSatellites
- if ($tag_id==0x0008) {
- $tmp=intval(substr($exif,$offset+8,4));
- if ($tmp>0) {
- $gps_data['GPSSatellites']=$tmp;
- }
- }
- $offset+=12;
- }
- }
- break;
- }
- }
- }
- }
- else {
- // Пропустить секцию
- fseek($f, ($section_length-2), SEEK_CUR);
- }
- // Тег GPSInfo найден
- if (count($gps_data)!=0) { break; }
- }
- }
- fclose($f);
- // Данные GPS
- print_r($gps_data);
Десятичные градусы = Градусы + Минуты/60 + Секунды/3600
Обратите внимание, что значение широты и долготы получается беззнаковым, поэтому для определения знака надо обязательно смотреть на значения GPSLatitudeRef и GPSLongitudeRef. Если направление широты равняется "S", то при установке метки на карте значение широты меняется на отрицательное, и аналогично, знак долготы меняется на минус, если направление долготы равно "W".
Приведенный в статье код можно очень легко модифицировать, чтобы извлекать не только GPS-координаты, но и любые другие данные из EXIF-секций графических файлов.
Просмотров: 5943 | Комментариев: 1

Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(25.06.2018 в 21:54):
Немного поправил код, благодарности loster.

Добавить комментарий
Заполните форму для добавления комментария
