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

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

(2025年8月13日).pahooEnv導入
(2025年6月14日)GoogleMaps JavaScript APIの変更に対応した.

目次

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

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

サンプル・プログラム

圧縮ファイルの内容
photomap.phpサンプル・プログラム本体
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。
photomap.php 更新履歴
バージョン 更新日 内容
1.1.0 2025/08/13 .pahooEnv導入
1.0 2022/03/06 初版
pahooGeoCode.php 更新履歴
バージョン 更新日 内容
6.8.0 2025/08/10 アクセスキーなどを ".env" に分離
6.7.1 2025/07/26 jsLine_Gmap() - bug-fix
6.7.0 2025/07/20 drawJSmap,drawGMap -- 引数 $markerLevel 追加
6.6.0 2025/07/19 drawJSmap,drawGMap,drawLeaflet -- マップ中心マーカー表示引数を追加
6.5.0 2025/06/14 GoogleMaps JavaScript APIの変更に対応

準備:GDライブラリ

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

準備:PHP の https対応

クラウド連携や相手先サイトのデータを読み込むのに https通信を使うため、PHPに OpenSSLモジュールが組み込まれている必要がある。関数  phpinfo  を使って、下図のように表示されればOKだ。
OpenSSL - PHP
そうでない場合は、次の手順に従ってOpenSSLを有効化し、PHPを再起動させる必要がある。

Windowsでは、"php.ini" の下記の行を有効化する。
extension=php_openssl.dll
Linuxでは --with-openssl=/usr オプションを付けて再ビルドする。→OpenSSLインストール手順

これで準備は完了だ。

準備:pahooInputData 関数群

PHPのバージョンや入力データのバリデーションなど、汎用的に使う関数群を収めたファイル "pahooInputData.php" が同梱されているが、include_path が通ったディレクトリに配置してほしい。他のプログラムでも "pahooInputData.php" を利用するが、常に最新のファイルを1つ配置すればよい。

また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。

準備:pahooGeoCode クラス

pahooGeoCode.php

  40: class pahooGeoCode {
  41:     public $items;      // 検索結果格納用
  42:     public $error;      // エラー・フラグ
  43:     public $errmsg;     // エラー・メッセージ
  44:     public $hits;       // 検索ヒット件数
  45:     public $webapi// 直前に呼び出したWebAPI URL
  46: 
  47:     // -- 以下のデータは .env ファイルに記述可能
  48:     // Google Cloud Platform APIキー
  49:     // https://cloud.google.com/maps-platform/
  50:     // ※Google Maps APIを利用しないのなら登録不要
  51:     public $GOOGLE_API_KEY_1 = '';      // HTTPリファラ用
  52:     public $GOOGLE_API_KEY_2 = '';      // IP制限用
  53:     public $GOOGLE_MAP_ID    = '';      // GoogleMaps ID
  54: 
  55:     // Yahoo! JAPAN Webサービス アプリケーションID
  56:     // https://e.developer.yahoo.co.jp/register
  57:     // ※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
  58:     public $YAHOO_APPLICATION_ID = '';
  59: 
  60:     // OSM Nominatim Search API利用時に知らせるメールアドレス
  61:     // https://wiki.openstreetmap.org/wiki/JA:Nominatim#.E6.A4.9C.E7.B4.A2
  62:     // ※OSM Nominatim Search APIを利用しないのなら登録不要
  63:     public $NOMINATIM_EMAIL = '';
  64: 
  65:     // IP2Location.io APIキー
  66:     // https://www.ip2location.io/
  67:     // ※IP2Location.ioを利用しないのなら登録不要
  68:     public $IP2LOCATION_API_KEY = '';

GoogleマップやLeafletなどによる地図描画や住所検索を行うためのクラスが pahooGeoCode である。同梱のクラス・ファイル "pahooGeoCode.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooGeoCodeクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。
PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

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

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

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

photomap.php

  48: // 地図描画サービスの選択
  49: //    0:Google
  50: //    2:地理院地図・OSM
  51: define('MAPSERVICE', 2);
  52: 
  53: // 逆ジオコーディングサービスの選択
  54: //    0:Google
  55: //    1:Yahoo!JAPAN
  56: //   11:HeartRails Geo API
  57: //   21:簡易ジオコーディングサービス
  58: define('REVGEOSERVICE', 11);

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

解説:初期設定

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

photomap.php

  60: // マップの表示サイズ(単位:ピクセル)
  61: define('MAP_WIDTH',  600);
  62: define('MAP_HEIGHT', 400);
  63: // マップID
  64: define('MAPID', 'map_id');
  65: // 初期値
  66: define('DEF_LATITUDE',  35.4658);       // 緯度
  67: define('DEF_LONGITUDE', 139.6223);      // 経度
  68: define('DEF_TYPE',      'roadmap');     // マップタイプ
  69: define('DEF_ZOOM',      13);            // ズーム

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

photomap.php

 301: /**
 302:  * Exif情報から緯度・経度を取り出す
 303:  * @param   array  $exif Exif情報
 304:  * @return  list(緯度,経度)/(FALSE,FALSE):取得失敗
 305: */
 306: function exif2location($exif) {
 307:     $pat = "/([0-9]+)\/([0-9]+)/ui";
 308: 
 309:     // Exif情報に緯度・経度があるかどうか
 310:     if (! isset($exif['GPSLatitude.0']) || ! isset($exif['GPSLongitude.0'])) {
 311:         return array(FALSE, FALSE);
 312:     }
 313: 
 314:     // 緯度の計算
 315:     $latitude = 0;
 316:     for ($i = 0$i < 5$i++) {
 317:         $key = 'GPSLatitude.' . (string)$i;
 318:         if (isset($exif[$key])) {
 319:             if (preg_match($pat, $exif[$key], $arr> 0) {
 320:                 $latitude += (float)$arr[1] / (float)$arr[2] / pow(60, $i);
 321:             }
 322:         }
 323:     }
 324:     // 北緯・南緯の計算
 325:     if (isset($exif['GPSLatitudeRef'])) {
 326:         if (! preg_match('/N/ui', $exif['GPSLatitudeRef']) > 0) {
 327:             $latitude = 0 - $latitude;
 328:         }
 329:     }
 330: 
 331:     // 経度の計算
 332:     $longitude = 0;
 333:     for ($i = 0$i < 5$i++) {
 334:         $key = 'GPSLongitude.' . (string)$i;
 335:         if (isset($exif[$key])) {
 336:             if (preg_match($pat, $exif[$key], $arr> 0) {
 337:                 $longitude += (float)$arr[1] / (float)$arr[2] / pow(60, $i);
 338:             }
 339:         }
 340:     }
 341:     // 東経・西経の計算
 342:     if (isset($exif['GPSLongitudeRef'])) {
 343:         if (! preg_match('/E/ui', $exif['GPSLongitudeRef']) > 0) {
 344:             $longitude = 0 - $longitude;
 345:         }
 346:     }
 347: 
 348:     return array($latitude, $longitude);
 349: }

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

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

photomap.php

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

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

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

file2img.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