PHPで撮影場所をマッピング

(1/1)
スマホやデジカメで撮影したJPEG画像ファイルには、撮影場所の緯度・経度がExif情報として記録されている。そこで、これまで作ってきた「PHPでExif情報を表示する」「PHPで画像ファイルを読み込んでimgタグに埋め込む」「PHPでマップを利用して緯度・経度や住所を求める」を活用し、Exif情報に埋め込まれている緯度・経度から、撮影場所をマップ上にプロットするPHPプログラムを作ってみることにする。

目次

サンプル・プログラムの実行例

PHPで撮影場所をマッピング

サンプル・プログラム

圧縮ファイルの内容
photomap.phpサンプル・プログラム本体
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。

準備:GDライブラリ

PHPで GDライブラリが有効になっていることが必要だ。
あらかじめ関数  phpinfo  を実行し、GD Support が enableになっていることを確認すること。

準備:pahooGeoCode クラス

  37: class pahooGeoCode {
  38:     var $items;     //検索結果格納用
  39:     var $error;     //エラー・フラグ
  40:     var $errmsg;    //エラー・メッセージ
  41:     var $hits;      //検索ヒット件数
  42:     var $webapi;    //直前に呼び出したWebAPI URL
  43: 
  44:     //Google Cloud Platform APIキー
  45:     //https://cloud.google.com/maps-platform/
  46:     //※Google Maps APIを利用しないのなら登録不要
  47:     var $GOOGLE_API_KEY_1 = '**************************';   //HTTPリファラ用
  48:     var $GOOGLE_API_KEY_2 = '**************************';   //IP制限用
  49: 
  50:     //Yahoo! JAPAN Webサービス アプリケーションID
  51:     //https://e.developer.yahoo.co.jp/register
  52:     //※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
  53:     var $YAHOO_APPLICATION_ID = '*****************************';

地図描画や住所検索を行うために、クラスファイル "pahooGeoCode.php" を使用する。組み込み関数  require_once  を使って読めるディレクトリに配置する。ディレクトリは、設定ファイル php.ini に記述されているオプション設定 include_path に設定しておく。
クラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

地図や住所検索として Google を利用するのであれば、Google Cloud Platform APIキー が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を、Yahoo!JAPAN を利用するのであれば、Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を、それぞれ参照されたい。

準備:地図サービス(WebAPI)の選択

  34: //地図描画サービスの選択
  35: //    0:Google
  36: //    2:地理院地図・OSM
  37: define('MAPSERVICE', 2);
  38: 
  39: //逆ジオコーディングサービスの選択
  40: //    0:Google
  41: //    1:Yahoo!JAPAN
  42: //   11:HeartRails Geo API
  43: //   21:簡易ジオコーディングサービス
  44: define('REVGEOSERVICE', 21);

表示する地図は、Googleマップ地理院地図・オープンストリートマップ(OSM)から選べる。あらかじめ、定数 MAPSERVIC に値を設定すること。
逆ジオコーディングサービスは、GoogleYahoo!JAPANHeartRails Geo API簡易ジオコーディングサービスから選べる。あらかじめ、定数 REVGEOSERVICE に値を設定すること。

解説:初期設定

表示に関わる初期値は自由に変更できる。

  46: //マップの表示サイズ(単位:ピクセル)
  47: define('MAP_WIDTH',  600);
  48: define('MAP_HEIGHT', 400);
  49: //マップID
  50: define('MAPID', 'map_id');
  51: //初期値
  52: define('DEF_LATITUDE',  35.4658);       //緯度
  53: define('DEF_LONGITUDE', 139.6223);      //経度
  54: define('DEF_TYPE',      'roadmap');     //マップタイプ
  55: define('DEF_ZOOM',      13);            //ズーム

解説:Exif情報から緯度・経度を取り出す

 320: /**
 321:  * Exif情報から緯度・経度を取り出す
 322:  * @param   array  $exif Exif情報
 323:  * @return  list(緯度,経度)/(FALSE,FALSE):取得失敗
 324: */
 325: function exif2location($exif) {
 326:     $pat = "/([0-9]+)\/([0-9]+)/ui";
 327: 
 328:     //Exif情報に緯度・経度があるかどうか
 329:     if (! isset($exif['GPSLatitude.0']) || ! isset($exif['GPSLongitude.0'])) {
 330:         return array(FALSE, FALSE);
 331:     }
 332: 
 333:     //緯度の計算
 334:     $latitude = 0;
 335:     for ($i = 0$i < 5$i++) {
 336:         $key = 'GPSLatitude.' . (string)$i;
 337:         if (isset($exif[$key])) {
 338:             if (preg_match($pat, $exif[$key], $arr> 0) {
 339:                 $latitude += (float)$arr[1] / (float)$arr[2] / pow(60, $i);
 340:             }
 341:         }
 342:     }
 343:     //北緯・南緯の計算
 344:     if (isset($exif['GPSLatitudeRef'])) {
 345:         if (! preg_match('/N/ui', $exif['GPSLatitudeRef']) > 0) {
 346:             $latitude = 0 - $latitude;
 347:         }
 348:     }
 349: 
 350:     //経度の計算
 351:     $longitude = 0;
 352:     for ($i = 0$i < 5$i++) {
 353:         $key = 'GPSLongitude.' . (string)$i;
 354:         if (isset($exif[$key])) {
 355:             if (preg_match($pat, $exif[$key], $arr> 0) {
 356:                 $longitude += (float)$arr[1] / (float)$arr[2] / pow(60, $i);
 357:             }
 358:         }
 359:     }
 360:     //東経・西経の計算
 361:     if (isset($exif['GPSLongitudeRef'])) {
 362:         if (! preg_match('/E/ui', $exif['GPSLongitudeRef']) > 0) {
 363:             $longitude = 0 - $longitude;
 364:         }
 365:     }
 366: 
 367:     return array($latitude, $longitude);
 368: }

画像ファイルからExif情報を取得する関数は、「PHPでExif情報を表示する」で作ったものをそのまま利用する。
取得したExif情報(配列)から、マッピングしやすいように緯度・経度情報を取り出すユーザー関数が exif2location である。

解説:画像情報の一部をTABLE化する

 454: /**
 455:  * 画像情報の一部をTABLE化する
 456:  * @param   object $pgc    pahooGeoCodeクラス
 457:  * @param   array  $exif   Exif情報
 458:  * @param   array  $items  画像ファイル情報
 459:  * @return  string HTMLタグ
 460: */
 461: function makeInfoTable($pgc, $exif, $items) {
 462:     $table = array(
 463:         //画像形式 => 読み込み関数
 464:         IMAGETYPE_BMP  => 'BMP',
 465:         IMAGETYPE_GIF  => 'GIF',
 466:         IMAGETYPE_JPEG => 'JPEG',
 467:         IMAGETYPE_PNG  => 'PNG',
 468:         IMAGETYPE_WEBP => 'WEBP'
 469:     );
 470: 
 471:     $html =<<< EOT
 472: <table class="exiflist">
 473: 
 474: EOT;
 475: 
 476:     //撮影日時
 477:     $dt = getExifData('DateTimeOriginal', $exif);
 478:     if ($dt == FALSE) {
 479:         $dt = '(不明)';
 480:     } else {
 481:         $dt = preg_replace('/([0-9]+)[\:\-\/]([0-9]+)[\:\-\/]([0-9]+) ([0-9\:\-\/]+)/ui', '$1-$2-$3 $4', $dt);
 482:     }
 483: 
 484:     //緯度・軽度
 485:     list($latitude, $longitude) = exif2location($exif);
 486:     if (($latitude !FALSE&& ($longitude !FALSE)) {
 487:         $latitudeRef  = ($latitude  >0? '北緯' : '南緯';
 488:         $longitudeRef = ($longitude >0? '東経' : '西経';
 489:         $latitudeStr  = $latitudeRef  . substr(deg2hexadec($latitude), 1);
 490:         $longitudeStr = $longitudeRef . substr(deg2hexadec($longitude), 1);
 491:         //住所
 492:         $address = (($arr = $pgc->getAddress3($latitude, $longitude, REVGEOSERVICE)) == FALSE? '' : $arr['address'];
 493:     } else {
 494:         $latitudeStr  = '(不明)';
 495:         $latitude     = '---';
 496:         $longitudeStr = '(不明)';
 497:         $longitude    = '---';
 498:         $address      = '(不明)';
 499:     }
 500: 
 501:     //画像形式
 502:     $imagetype = '';
 503:     if (isset($items['imagetype'])) {
 504:         foreach ($table as $key=>$val) {
 505:             if ($key == $items['imagetype']) {
 506:                 $imagetype = $val;
 507:                 break;
 508:             }
 509:         }
 510:     }
 511:     //画像幅
 512:     $width = '';
 513:     if (isset($items['width'])) {
 514:         $width = $items['width'];
 515:     }
 516:     //画像高さ
 517:     $height = '';
 518:     if (isset($items['height'])) {
 519:         $height = $items['height'];
 520:     }
 521: 
 522:     $html .=<<< EOT
 523: <tr>
 524: <td>緯度</td>
 525: <td>{$latitudeStr}<br />({$latitude})</td>
 526: </tr>
 527: <tr>
 528: <td>経度</td>
 529: <td>{$longitudeStr}<br />({$longitude})</td>
 530: </tr>
 531: <tr>
 532: <td>住所</td>
 533: <td>{$address}</td>
 534: </tr>
 535: <tr>
 536: <td>撮影日時</td>
 537: <td>{$dt}</td>
 538: </tr>
 539: <tr>
 540: <td>画像形式</td>
 541: <td>{$imagetype}</td>
 542: </tr>
 543: <tr>
 544: <td>画像サイズ</td>
 545: <td>{$width}×{$height}ピクセル</td>
 546: </tr>
 547: 
 548: EOT;
 549:     $html .=<<< EOT
 550: </table>
 551: 
 552: EOT;
 553:     return $html;
 554: }

ユーザー関数 makeInfoTable は、GDライブラリ やExif情報を参照し、読み込んだ画像情報のうち、撮影日時、緯度・経度、住所、画像形式、画像サイズ(幅・高さ)をHTMLタグに変換するものである。
緯度・経度から住所を求める仕組みは、「PHPで緯度・経度から住所を求める」をご覧いただきたい。

解説:ファイルのドロップ

  73: <script>
  74: /**
  75:  * ファイルのドロップ
  76:  * @param   evt  イベント
  77:  * @return  なし
  78: */
  79: function handleFileSelect(evt) {
  80:     evt.stopPropagation();
  81:     evt.preventDefault();
  82: 
  83:     var files = evt.dataTransfer.files; 
  84:     var output = [];
  85: 
  86:     document.getElementById('upload').files = files;
  87:     document.myform.submit();
  88: }
  89: 
  90: function handleDragOver(evt) {
  91:     evt.stopPropagation();
  92:     evt.preventDefault();
  93:     evt.dataTransfer.dropEffect = 'copy';
  94: }
  95: 
  96: function PageLoad(evt) {
  97:     var dropFrame = document.getElementById('DropFrame');
  98:     dropFrame.addEventListener('dragover', handleDragOver, false);
  99:     dropFrame.addEventListener('drop', handleFileSelect, false);
 100: }
 101: </script>

画像ファイルの選択は、input type="file" タグ、およびテキストボックスからURL指定できるほか、ファイルをドラッグする方式の3つを用意した。

ファイルをドラッグする処理は、JavaScriptによって実現している。
ドラッグする場所はオブジェクトとして用意し、そのID名をあらかじめ変数 dropFrame に代入しておく。
関数 PageLoad は bodyタグをonLoadするときに実行すること。

参考サイト

(この項おわり)
header