今回は、前回取得したディレクトリ内の画像ファイルの情報(JSON形式)をJavascript側で受け取り、GoogleMapsAPIにマーカー配置させてみます。

 まずは、Exif情報の取得・出力を行うPHP。前回作成のプログラム(getexiflisttest.php)では対象ディレクトリをPHP内にて指定する形でしたが、今回はJavascriptからパラメータとして受け取る形式に変更しました(4,5行目)。さらにエラートラップの追加等していますが(20行目等)、その他は前回と同じなので解説は省略します。
 
<getexiflist.php>

<?php
  $jsondata="";
  $images = array();
  if (isset($_REQUEST['dirname'])) {
     $imagedir = $_REQUEST['dirname'];
     if ($handle = @opendir($imagedir)) {
        while (false !== ($file = readdir($handle))) {
           if (is_file($imagedir."/".$file) && exif_imagetype($imagedir."/".$file) == 2) {
              if ($imgexif = getImageInfo($imagedir."/".$file)) {
                 array_push($images, $imgexif); 
              }
           }
        }
        closedir($handle);
        if (count($images)) {
           $jsondata = json_encode($images);
        }
        print $jsondata;
     } else {
        print "err:指定したディレクトリを開けませんでした。存在しない可能性があります。";
     }
  } else {
     print "err:画像の取得に失敗しました。";
  }
  exit();

  //画像のExif情報を取得し、クラス「ImageInfo」に収納
  function getImageInfo($filename) {

     $exif = exif_read_data($filename, 0, true);
     $imginfo = new ImageInfo();
     if (isset($exif['FILE']) && isset($exif['FILE']['FileName'])) {
        $imginfo->FileName = $exif['FILE']['FileName'];
     }
     if (isset($exif['IFD0'])) {
        if (isset($exif['IFD0']['Make'])) {
           $imginfo->Make = $exif['IFD0']['Make'];
        }
        if (isset($exif['IFD0']['Model'])) {
           $imginfo->Model = $exif['IFD0']['Model'];
        }
     }
     if (isset($exif['EXIF'])) {
        if (isset($exif['EXIF']['DateTimeOriginal'])) {
           $imginfo->DateTimeOriginal = $exif['EXIF']['DateTimeOriginal'];
        }
     }
     if (isset($exif['GPS'])) {
        $gps = $exif['GPS'];
        //緯度
        if (isset($gps['GPSLatitude']) && isset($gps['GPSLatitudeRef'])) {
           $lat = $gps['GPSLatitude'];
           $lat_d = explode("/",$lat[0]); //度
           $lat_m = explode("/",$lat[1]); //分
           $lat_s = explode("/",$lat[2]); //秒
           //60進数→10進数
           $imginfo->Lat = intval($lat_d[0])/intval($lat_d[1])
                         + (intval($lat_m[0])/intval($lat_m[1]))/60
                         + (intval($lat_s[0])/intval($lat_s[1]))/3600;
           if ($gps['GPSLatitudeRef'] == "S") {
              $imginfo->Lat = $imginfo->Lat * -1; //南半球の場合マイナスにする
           }
        }
        //経度
        if (isset($gps['GPSLongitude']) && isset($gps['GPSLongitudeRef'])) {
           $lng = $gps['GPSLongitude'];
           $lng_d = explode("/",$lng[0]); //度
           $lng_m = explode("/",$lng[1]); //分
           $lng_s = explode("/",$lng[2]); //秒
           //60進数→10進数
           $imginfo->Lng = intval($lng_d[0])/intval($lng_d[1])
                         + (intval($lng_m[0])/intval($lng_m[1]))/60
                         + (intval($lng_s[0])/intval($lng_s[1]))/3600;
           if ($gps['GPSLongitudeRef'] == "W") {
              $imginfo->Lng = $imginfo->Lng * -1; //西経の場合マイナスにする
           }
        }
        if (isset($gps['GPSImgDirection'])) {
           $degreeArray = explode("/",$gps['GPSImgDirection']);
           $degree = intval($degreeArray[0]) / intval($degreeArray[1]);
           $imginfo->Direction = getDirection($degree);
        }
     }
     if ($imginfo->FileName && !is_null($imginfo->Lat) && !is_null($imginfo->Lng) 
                            &&  abs($imginfo->Lat) <= 90 && abs($imginfo->Lng) <= 180) {
        return $imginfo;
     } else {
        return false;
     }
  }

  //方向データ(北からの時計回りの角度)を16方位テキストに変換
  function getDirection($degree) {
     $directionArray = array('北','北北東','北東','東北東','東','東南東','南東','南南東',
                                   '南','南南西','南西','西南西','西','西北西','北西','北北西');
     $directionNo = (int)(($degree+11.25)/22.5) % 16;
     return $directionArray[$directionNo];
  }

  class ImageInfo {
     public $FileName = "";         //ファイル名
     public $DateTimeOriginal;      //撮影日時
     public $Lat = NULL;            //緯度
     public $Lng = NULL;            //経度
     public $Direction="";          //方角
     public $Make = "";             //カメラ製造元
     public $Model = "";            //カメラのモデル
  }
?>

 次はJavascriptのソース
 
<exifmap.js>

  var map;
  var PhotoMarker = new Array();
  var currentMarkerNo = -1;
  var currentInfoWindow = null;
  var currentCharNo = 0;

  //マップのタイトル
  var mapTitle = "ディレクトリ内のGPS情報つき画像をGoogleMapに配置";

  //初期値
  var initialLocation = new google.maps.LatLng(36.63, 137.80); //中心となる緯度経度
  var initialZoom = 13; //ズームレベル

  //画像ファイルのディレクトリ
  var imageDir = "ディレクトリ名"; //phpファイルの置き場所から見た相対パスで記述

  //マーカー画像
  var markerImage = "icon/s_green.png"

  //htmlのbody読込み時に行う処理
  function initialize() {

     var titleFld = document.getElementById("map_title");
     titleFld.innerHTML = mapTitle;
     document.title = mapTitle;

     var myOptions = {
        zoom: initialZoom,

        center: initialLocation,
        mapTypeControl: true,
        mapTypeId: google.maps.MapTypeId.ROADMAP
     };
     map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

     PhotoMarker.length = 0;

     google.maps.event.addListener(map, 'zoom_changed', function() {
        currentZoom = map.getZoom();
        if (currentInfoWindow) {
           currentInfoWindow.close();
           currentInfoWindow = null;
        }
     });

     google.maps.event.addListener(map, 'idle', function() {
        currentLocation = map.getCenter();
     });

     getExifData();

  }

  //サーバーサイド(PHP)よりフォルダ内の画像情報を取得
  function getExifData() { 

     var json;
     var param = "dirname=" + imageDir; //ディレクトリ名をパラメータとしてPHPへ渡す

     jQuery.ajax({
        url : "getexiflist.php?",
        type : "post",
        data: param,
        success: function(request){
           var res = request;
           if (res.substring(0,4) == "err:"){
              alert("エラー:"+res.substring(4));
           }else{
              json = eval(res);
              PhotoMarker.length = 0;
              for (var i = 0; i < json.length; i++) {
                  setPhotoMarker(json[i]);
              }
           }
        },
        error: function() {
           alert('データ取得に失敗しました');
        }
     });
  } 

  //画像のExif情報から取得した位置にマーカーを設置
  function setPhotoMarker(myExifInfo) {

     var markerNo = PhotoMarker.length;
     var myPoint = new google.maps.LatLng(myExifInfo.Lat,myExifInfo.Lng);
     var myTitle;

     PhotoMarker[markerNo] = new google.maps.Marker({
        position: myPoint,
        map: map,
        title: myExifInfo.FileName,
        visible: true,
        draggable: false,
        icon: markerImage
     });

     PhotoMarker[markerNo].Info = myExifInfo;

     google.maps.event.addListener(PhotoMarker[markerNo], 'click', function() {
        setInfoWindow(map,markerNo);
     });
  }

  //情報ウィンドウの設定
  function setInfoWindow(myMap, markerNo) {

     if (currentInfoWindow) {
        currentInfoWindow.close();
     }
     var contentString = "";
     contentString += " ファイル名:"+PhotoMarker[markerNo].Info["FileName"]+"<br />";
     contentString += " 撮影日時:"+PhotoMarker[markerNo].Info["DateTimeOriginal"]+"<br />";
     contentString += " 緯度:"+PhotoMarker[markerNo].Info["Lat"]+"<br />";
     contentString += " 経度:"+PhotoMarker[markerNo].Info["Lng"]+"<br />";
     contentString += " カメラ方角:"+PhotoMarker[markerNo].Info["Direction"]+"<br />";
     contentString += "<p><a href= '"+imageDir+"/"+PhotoMarker[markerNo].Info["FileName"]+"' target='_blank'>";
     contentString += "<img src='"+imageDir+"/"+PhotoMarker[markerNo].Info["FileName"]+"' width='280'>";
     contentString += "</a></p>";

     var infowindow = new google.maps.InfoWindow(
        {  
           content: contentString
        }
     );
     infowindow.open(myMap, PhotoMarker[markerNo]);
     currentInfoWindow = infowindow;
  }

 関数「getExifData」(55行~80行目)でサーバサイド(PHP)とクライアントサイド(Javascript)の通信を行っています。オーソドックスにjQuery.ajaxを使用することにしました。
 58行目で var param = “dirname=” + imageDir とパラメータの文字列を生成し、62,63行目で
 type : “post”, data: param, とPOST送信しています(GET送信でもOKです)。

 PHPから受け取るデータは配列(JSON形式)となっていますので

              for (var i = 0; i < json.length; i++) {
                  setPhotoMarker(json[i]);
              }

 と、要素数(=緯度・経度情報を取得出来た画像数)回ループ処理を行っています。ここで呼び出している関数「setPhotoMarker」(83~103行目)では、マーカーを各画像の緯度・経度に設置しています。
 86~96行目に&nsbp;PhotoMarker[markerNo] = new google.maps.Marker ~ とあるように変数「PhotoMarker」はマーカーオブジェクトの配列ですが、その他の情報を追加することも出来るので
    PhotoMarker[markerNo].Info = myExifInfo;(98行目)
 と、「Info」というプロパティを作成して取得したExif情報を全て収納しています。このInfoプロパティ収納の情報は、情報ウィンドウ生成時に使用します。
 その情報ウィンドウの設定は、関数「setInfoWindow」(106~128行目)で行っています。
 
 最後に出力用HTMLのソース。Javascriptを別ファイルに分けているのでとてもシンプルです。
 
<exifmap.html>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta http-equiv="Content-Style-Type" content="text/css">
<link href="css/map.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="js/jquery-1.10.2.js"></script>
<script type="text/javascript" src="js/exifmap.js"></script>
<style type="text/css">
  html { height: 100% }
  body { height: 100%; margin: 0; padding: 0 }
</style>
</head>
<body onload="initialize()">
  <div id="map_title" name="map_title" style="width:100%; height:3%; padding:5px 10px;font-weight:bold;"></div>
  <div id="map_canvas" style="width:100%; height:97%"></div>
</body>
</html>

 
 このHTMLを開くと、次のようにマップが表示されます。

MAP出力例

MAP出力例

 
 GPSロガーで記録したかのようにマーカーが山行ルートに沿って配置され、なんだか嬉しいです。もちろん、各マーカーをクリックすると、下図のように情報ウィンドウで画像が表示されます。

マーカークリックで画像表示

マーカーをクリックすると画像が表示されます


 以上で当初の目的は達成出来ました。ただ、実際にマップ表示させて気づいたことなのですが、同じ場所で何枚も写真撮影した時にはマーカーが重なってしまいます。食べログのマップ等でも商業施設等の飲食店が密集している所ではマーカーが重なってしまい下側の店がクリックし辛いということが良くあります(マーカーがクリック出来なくてもサイドに表示されるリストをクリックすれば良いのですが・・・)。登山の場合、特に山頂等では各方向の景色を撮影するので必然的に同じ位置の写真が複数枚ということになります。また、山間部では市街地のように詳細な地図が用意されていないのでズームレベルをあまり大きくせずに見る機会が多いかと思います。そのため同じ位置ではなくてもマーカーが重なってしまい、下側の画像を選択するためにわざわざズームレベルを変更するというのも面倒です。
 そこで次回では、同じ位置または近い位置で撮影された複数の画像をまとめてマーカー表示させる方法を考えてみることにしました。

ソース一式ダウンロード(ZIP圧縮)
※当圧縮ファイルにはjQuery(jquery-1.10.2.js)が同梱されています(MITライセンス)。著作権など詳細は
jQueryのサイトにてご確認下さい(当ファイルの冒頭にも記載されています)。
※JavaScriptソース(exifmap.js)の7~18行目にてマップタイトルや初期表示位置・ズーム、画像ファイルのあるディレクトリの設定をしています。ご使用の環境に合わせて編集して下さい。
※PHP5.2未満のバージョンではJSONライブラリが標準搭載されていないため、追加で入手・設置する必要があります。 

 先日、GPS機能付きのデジタルカメラ(Panasonic DMC-TZ40)を購入し、山で写真をたくさん撮影してきました。この写真をマップ表示させようと思いアプリケーションを探してみたのですが、いくつか見つかったものの自分の望むものとはどうも少し異なる・・・。それならばと、学習を兼ねて自作してみることにしました。

 実現したいことは以下のとおり
・ディレクトリ内の画像ファイルのうち、GPS情報を持ったものを全てピックアップ
・上記画像の緯度・経度を取得しGoogleMapsAPIにマーカー配置
・そのマーカーをクリックすると情報ウインドウが開き、画像が表示される。

 プログラミングに取りかかる前に、画像ファイルに含まれる情報について調べてみました。恥ずかしいことに今まで知らなったのですが、デジタルカメラで撮影された画像ファイルにはExif(Exchangeable image file format)という様々な撮影情報が埋め込まれています。これら情報は、特に専用のアプリケーションがなくても、対象ファイルのアイコン右クリック→プロパティ→詳細タブで見ることが出来ます。  

画像ファイルのプロパティ1
 上図はGPS機能のないデジカメで撮影した写真のプロパティですが、カメラの製造元や機種名、焦点距離といった撮影情報が確認出来ます。
 GPS情報つきの画像では下図のように緯度・情報も表示されます。※良く言われていることですが、スマホ(大抵はGPS機能つきですよね)で撮影した写真をブログ等に安易に公開してしまうと、これらの情報がだだ漏れになってしまうリスクがあるので注意が必要です。「部屋の写真で外観は分からないから大丈夫」なんて思っていても、緯度経度が分かれば住所が特定出来てしまいます(まあ、通常はアップロード時に画像サイズが縮小されると同時に位置情報も消去されることが多いとは思いますが)。
画像ファイルのプロパティ2
 

 今度は、上記のExif情報をプログラム(PHP)で取得・確認してみます。
 これはとても簡単!PHPには「exif_read_data」という便利な関数があるので、次のようなシンプルなソースでExif情報を取得出来てしまいます。
 (サンプル画像:http://www.eclip.jp/blog/wp-content/uploads/GoryuKashimayari/s-P1000230.jpg

<?php
   $file='http://www.eclip.jp/blog/wp-content/uploads/GoryuKashimayari/s-P1000230.jpg';
   $exif = exif_read_data($file, 0, true);
   echo "<pre>";
   print_r($exif);
   echo "</pre>";
?>

上記ソースの実行

 取得データは連想配列となっています。メーカーの固有情報なのか文字化けして読み取れない項目(MakerNote他)もありますが、今回の取得対象ではないので特に問題ありません。
 出力結果のうちGPSに関する箇所は以下の通り

    [GPS] => Array
        (
            [GPSLatitudeRef] => N
            [GPSLatitude] => Array
                (
                    [0] => 36/1
                    [1] => 39/1
                    [2] => 1846/100
                )
            [GPSLongitudeRef] => E
            [GPSLongitude] => Array
                (
                    [0] => 137/1
                    [1] => 48/1
                    [2] => 126/100
                )
            [GPSAltitudeRef] => 
            [GPSAltitude] => 0/1
            [GPSImgDirectionRef] => M
            [GPSImgDirection] => 33750/100
        )
  ※取得データにはこの他、GPSStatus(GPSの状態)、GPSDOP(測位精度?)、GPSTimeStamp(GPS時刻)などもありますが、今回は関係なさそうなので省略しました。

・GPSLatitude(緯度)
 60進数(度・分・秒)の要素数=3の配列の形で収納されています。
 各データはスラッシュで区切られていて、これはスラッシュ左側数値を右側数値で割った値が実数値という意味合いと思われますので、上記サンプルの
    [0] => 36/1
    [1] => 39/1
    [2] => 1846/100
 は、36度39分18.46秒となります。GoogleMapsAPIでは緯度・経度は10進数(度)で扱いますので、これを次のように変換する必要があります。
    36+39/60+18.46/3600 ≒ 36.65513度
・GPSLatitudeRef(北緯/南緯の判別)
 GoogleMapsAPIでは南緯(S)はマイナス数値で扱いますが、今回のサンプルは北緯(N)なのでそのままでOK
・GPSLongitude(経度)
 データの収納形式は緯度と同じなので、サンプルの
    [0] => 137/1
    [1] => 48/1
    [2] => 126/100
 は、137度48分1.26秒 → 137+48/60+1.26/3600 = 137.80035度となります。
・GPSLongitudeRef(東経/西経の判別)
 GoogleMapsAPIでは西経(W)はマイナス数値で扱いますが、上記サンプルは東経(E)なのでそのままでOK
・GPSAltitude(高度)
 私のカメラには高度計はついていないので0となっています。今回不使用
・GPSImgDirection(撮影方向)
 北からの時計回りの角度で収納されるようです。
 緯度・経度同様に値はスラッシュで区切られて、上記サンプルの値は 33750/100 なので337.5度となります。これは16方位にすると北北西となります。私のカメラでは16方位に丸められた形で記録されるようで、数値は必ず22.5(360÷16)の倍数となっていますが、丸めずに正確な角度のまま記録される機種もあるようです。
・GPSImgDirectionRef(撮影方向の基準)
 ここにはT(True)とM(Magnetics)のいずれかが入るようです。方向の基準を地軸(北極点―南極点)とするのがT、地磁気の極(N極、S極)のがMだと思われますが、今回は緯度・経度の取得が主目的なのでこの誤差は無視します。

 さて、ようやくここから本題ですが、まずはExif情報のうち必要な情報のみをピックアップ・加工する関数を作成します。画像1つのみであれば全情報をそのままGoogleMapsAPIに渡してしまっても良いのですが、今回はディレクトリ内の全画像を対象にするので、数が多くなると重くなりそうな気がします。そこで、以下のようなクラスを作成しデータ収納することにしました。

   class ImageInfo {
     public $FileName = "";         //ファイル名
     public $DateTimeOriginal;      //撮影日時
     public $Lat = NULL;            //緯度
     public $Lng = NULL;            //経度
     public $Direction="";          //方角
     public $Make = "";             //カメラ製造元
     public $Model = "";            //カメラのモデル
  }

 当然ながら緯度・経度は必須。方角は記録されない機種も多いようで実際あまり意味がない情報だと思ってましたが、これは山での撮影時には便利ですね。「あそこに見えるのは何山だろう・・・分からないなあ」といった時でも、撮影位置(緯度・経度)と方角が分かれば、その山が何山であるか地図上で推定出来るからです。
 この他、撮影日時とファイル名も加えました。カメラ製造元とモデルは今回は特に必要ない情報なので参考程度。  

 次は、画像ファイルからExif情報を取得・加工し、上記クラスに収納する関数

  //画像のExif情報を取得し、クラス「ImageInfo」に収納
  function getImageInfo($filename) {

     $exif = exif_read_data($filename, 0, true);
     $imginfo = new ImageInfo();
     if (isset($exif['FILE']) && isset($exif['FILE']['FileName'])) {
        $imginfo->FileName = $exif['FILE']['FileName'];
     }
     if (isset($exif['IFD0'])) {
        if (isset($exif['IFD0']['Make'])) {
           $imginfo->Make = $exif['IFD0']['Make'];
        }
        if (isset($exif['IFD0']['Model'])) {
           $imginfo->Model = $exif['IFD0']['Model'];
        }
     }
     if (isset($exif['EXIF'])) {
        if (isset($exif['EXIF']['DateTimeOriginal'])) {
           $imginfo->DateTimeOriginal = $exif['EXIF']['DateTimeOriginal'];
        }
     }
     if (isset($exif['GPS'])) {
        $gps = $exif['GPS'];
        //緯度
        if (isset($gps['GPSLatitude']) && isset($gps['GPSLatitudeRef'])) {
           $lat = $gps['GPSLatitude'];
           $lat_d = explode("/",$lat[0]); //度
           $lat_m = explode("/",$lat[1]); //分
           $lat_s = explode("/",$lat[2]); //秒
           //60進数→10進数
           $imginfo->Lat = intval($lat_d[0])/intval($lat_d[1])
                         + (intval($lat_m[0])/intval($lat_m[1]))/60
                         + (intval($lat_s[0])/intval($lat_s[1]))/3600;
           if ($gps['GPSLatitudeRef'] == "S") {
              $imginfo->Lat = $imginfo->Lat * -1; //南半球の場合マイナスにする
           }
        }
        //経度
        if (isset($gps['GPSLongitude']) && isset($gps['GPSLongitudeRef'])) {
           $lng = $gps['GPSLongitude'];
           $lng_d = explode("/",$lng[0]); //度
           $lng_m = explode("/",$lng[1]); //分
           $lng_s = explode("/",$lng[2]); //秒
           //60進数→10進数
           $imginfo->Lng = intval($lng_d[0])/intval($lng_d[1])
                         + (intval($lng_m[0])/intval($lng_m[1]))/60
                         + (intval($lng_s[0])/intval($lng_s[1]))/3600;
           if ($gps['GPSLongitudeRef'] == "W") {
              $imginfo->Lng = $imginfo->Lng * -1; //西経の場合マイナスにする
           }
        }
        if (isset($gps['GPSImgDirection'])) {
           $degreeArray = explode("/",$gps['GPSImgDirection']);
           $degree = intval($degreeArray[0]) / intval($degreeArray[1]);
           $imginfo->Direction = getDirection($degree);
        }
     }
     if ($imginfo->FileName && !is_null($imginfo->Lat) && !is_null($imginfo->Lng) 
                            &&  abs($imginfo->Lat) <= 90 && abs($imginfo->Lng) <= 180) {
        return $imginfo;
     } else {
        return false;
     }
  }

  //方向データ(北からの時計回りの角度)を16方位テキストに変換
  function getDirection($degree) {
     $directionArray = array('北','北北東','北東','東北東','東','東南東','南東','南南東',
                                   '南','南南西','南西','西南西','西','西北西','北西','北北西');
     $directionNo = (int)(($degree+11.25)/22.5) % 16;
     return $directionArray[$directionNo];
  }

 関数getImageInfoの引数$filenameは画像ファイルのパス(URL)です。
 25~37行目で緯度の取得・加工を行っています。前述したように緯度データは度・分・秒の配列で、各データはスラッシュ区切りの形式で収納されていますので、

         $lat_d = explode("/",$lat[0]); //度
         $lat_m = explode("/",$lat[1]); //分
         $lat_s = explode("/",$lat[2]); //秒

 このように度・分・秒の各々についてexplode関数でスラッシュ前・後に分け、

           $imginfo->Lng = intval($lng_d[0])/intval($lng_d[1])
                         + (intval($lng_m[0])/intval($lng_m[1]))/60
                         + (intval($lng_s[0])/intval($lng_s[1]))/3600;

 と10進数に変換しています。34~36行目は南半球の場合の処理。経度についての処理(39~51行目)も緯度と同様です。
 方角は、まず

         $degreeArray = explode("/",$gps['GPSImgDirection']);
         $degree = intval($degreeArray[0]) / intval($degreeArray[1]);

 と角度(数値)として取得しています。スラッシュ区切り形式から実数値への変換は緯度・経度と同様。
 この角度を関数getDirection(67~72行目)で、東、北北西などの16方位文字列に変換しています。0~360の数値を16分割するのですから、

      $directionNo = (int)(($degree+11.25)/22.5) % 16;

 このように22.5度の幅を持たせます。これで、角度が11.2°以上33.75°未満→北北東、168.75°以上191.25°未満→南・・・といった具合に変換されます。

 そして、関数getImageInfoの最後、以下の箇所でファイル名と緯度・経度が正しく取得出来たかをチェックし、取得出来なかった場合はFalseを返しています(方角は取得失敗しても地図上に落とせるのでチェック対象とはしませんでした)。

   if ($imginfo->FileName && !is_null($imginfo->Lat) && !is_null($imginfo->Lng)
                          &&  abs($imginfo->Lat) <= 90 && abs($imginfo->Lng) <= 180) {
      return $imginfo;
   } else {
      return false;
   }

 クラスの緯度・経度の初期値はNullにしているので本来はis_null関数だけで良いのですが、私のデジタルカメラではGPS機能オンでも位置捕捉に失敗した場合(電源を入れて間もない時など)には緯度・経度に「17056881.853375」という異常値が入るようなので、これを除外するために緯度の絶対値90以下、経度の絶対値180以下という条件を追加しました。

 ここまでで個々の画像ファイルの情報を取得する仕組みが出来ましたので、続いてこれをディレクトリ内の全ファイルに適用する処理です。

  $imagedir = "画像ファイルのあるディレクトリ名"; //phpファイルの置き場所から見た相対パスで記述
  $images = array();
  if ($handle = opendir($imagedir)) {
     while (false !== ($file = readdir($handle))) {
        if (is_file($imagedir."/".$file) && exif_imagetype($imagedir."/".$file) == 2) {
           if ($imgexif = getImageInfo($imagedir."/".$file)) {
              array_push($images, $imgexif); 
           }
        }
     }
  }
  closedir($handle);
  if (count($images)) {
     $jsondata = json_encode($images);
  }
  print $jsondata;

 3行目で指定のディレクトリを開き、4~10行目でその中の全ファイルについてループ処理しています。readdir関数ではカレントディレクトリやサブディレクトリなども拾ってしまうのでis_file関数でそれらを除外しています(今回はサブディレクトリ内のファイルについては考慮していません)。
   exif_imagetype($imagedir.”/”.$file) == 2
 exif_imagetypeは画像のタイプを調べる関数で、上式でファイルがJPEG形式(=2)であるかのチェックをしています。Exif情報は
JPEGの他にTIFFにも対応しているようですが、カメラの撮影画像がTIFF形式というのは聞いたことがないのでJPEGに限定しても問題ないでしょう。
 そして、対象となったJPEGファイル各々について前述したgetImageInfo関数で緯度・経度を取得に成功した場合に配列($images)に追加し、最後にGoogleMapsAPI(JavaScript)へのデータ引き渡しに便利なJSON形式に変換しています。

     $jsondata = json_encode($images);

  
 ここまでのソースをまとめて実行すると以下のように出力されます。
画像ファイルのプロパティ1
ソース(getexiflisttest.php)のダウンロード(ZIP圧縮)
※画像ファイルのあるディレクトリ名を当ソースの3行目にて設定しています(変数名:$imagedir)。ご使用の環境に合わせて編集して下さい。
※PHP5.2未満のバージョンではJSONライブラリが標準搭載されていないため、追加で入手・設置する必要があります。 

次回は、これらのデータをGoogleMapsAPIにマーカー配置します。

 ブリヂストン美術館へ「カイユボット展」の観覧に行ってきました。

 普段は収蔵作品の展示替えだけで特別展が成り立ってしまう当美術館ですが、本展では海外から来日した作品が大部分。そのためか観覧料は1500円といつもよりも高い設定ですが、それだけの価値はあると思います。最も有名な(と思われる)オルセー所蔵の「床削り」こそ来日していませんが、決して多いとはいえないカイユボットの作品を良くこれだけ集められたなと感心します。しかも、ただ数を揃えただけでなく質も高い。

 「印象派」の画家の1人に数えられるカイユボットですが、1876年代後半の印象派展に出品した「ヨーロッパ橋」「ピアノを弾く若い男」「昼食」などの作品は写実的なものばかりで、当時の印象派画家の作品の中にあっては異色だったはず。ドガあたりに批判されそうな作風ですが、資産家で経済的に仲間達を支えたカイユボットだけに、さすがに表立って批判されることはなかったでしょうかね? 仲間にしてパトロン・・・カイユボットと他の画家たちのお互いの心の奥底はどんな感じだったのでしょうか?ちょっと想像出来ません。

 1980年頃から徐々に印象派色が強まっていった感じで、解説には「モネの影響を受けて~」とありましたが、モネというよりもピサロの作風に近いと思いました。本展では他の印象派画家の作品も参考出品されているのですが、ピサロとカイユボットの作品が並んで展示されていると、どちらがカイユボット作なのか分からなくなってしまうことも・・・。ただ、印象派ではあってもどこか細かい部分にこだわりを持っているような・・・きっと几帳面な性格だったのではないかな?と勝手に想像しています。

 モネやルノワールと比べるとまだまだ知名度が低いカイユボットですが、ひょっとすると数十年後には逆転してるかもしれません。そのくらいポテンシャルの高さを感じさせる展覧会でした。必見です!
 会期は12月29日(日)まで。

自画像

自画像


昼食

昼食


ヨーロッパ橋

ヨーロッパ橋


シルクハットの漕手

シルクハットの漕手


向日葵、プティ・ジュヌヴィリエの庭

向日葵、プティ・ジュヌヴィリエの庭


セーヌのプティ・ブラ、アルジャントゥイユ、陽光

セーヌのプティ・ブラ、アルジャントゥイユ、陽光

コメント   

10月8日(火)

 ここ冷池山荘に限った話ではありませんが、山小屋の朝は早いですね。4時過ぎくらいから準備を始める人が出始め、5時には既に殆どの人が起床済み。朝食は5時半。私は夜型人間なので普段はこんな時間に朝食などあり得ないのですが、山では就寝時間が早いせいもあり何の問題もなく順応出来ます。食欲旺盛。

 荷造りは既に済ませていたので、食事後すぐに出発。小屋から見えた朝焼けやモルゲンロートの鹿島槍ヶ岳に元気をもらえました。この日は柏原新道を歩いて扇沢の登山口まで行くルート。基本は下りで距離も短いので楽な工程ですが、序盤は爺ヶ岳山頂までの登りなので「朝イチだと少しきついかな?」と思っていたのですが、とても緩やかな傾斜の山で全く苦になりませんでした。「こんな優しい斜面の印象から爺ヶ岳という名前がついたのだろうか?」なんて思いましたが、帰宅後調べてみると「種まき爺さんの雪形が現れることが由来」とのことで、山の形状は関係ありませんでした。

 天候に恵まれ爺ヶ岳(中峰)からの眺望は抜群で、富士山の姿も確認出来ました。槍ヶ岳の姿も五竜岳から見た時よりも随分近く感じられました。

 南峰の山頂を超えると、後は下りオンリー。種池山荘(ハイマツ越しに見える景色がメルヘンチックで印象的でした)から柏原新道へ。この道も紅葉の穴場ですが、遠見尾根と比べるとやや地味な印象を受けました。これは日当たりの関係かもしれません。柏原新道は山の斜面の西側に位置しているので、私が歩いた午前中にはあまり日が当たらず紅葉の光映えは殆どなかったので・・・。そう考えると午後に通ればまた違った印象を受けたのかもしれませんが、その場合も「ガスが出なければ」という条件がつきますね。

 登山口から扇沢のバスステーションまでは15分程一般道を歩きます。この道は信濃大町までのバス路線にもなっているので遡る形になります。「登山口にバス停があれば良いのに」と思うのですが、ひょっとすると登山口の所で待っててもバスは止まってくれるのかも?(未確認なのでご注意)。帰りは信濃大町駅に直行せずに、大町温泉郷の日帰り温泉施設「薬師の湯」でゆったり入浴。入館料は600円ですが、扇沢のバスチケット売り場で100円割引券をもらえます。

<この日の行程>
冷池山荘(5:45)-爺ヶ岳中峰山頂(6:31)-爺ヶ岳南峰山頂(6:45)-種池山荘(7:15)-柏原新道-登山口(9:06)-扇沢バスステーション(9:20)

冷池山荘より 朝焼け

冷池山荘より 朝焼け

モルゲンロートの鹿島槍

モルゲンロートの鹿島槍

剱岳

剱岳

立山

立山

鹿島槍(左端に冷池山荘)

鹿島槍(左端に冷池山荘)

爺ヶ岳中峰山頂

爺ヶ岳中峰山頂

爺ヶ岳中峰山頂からの眺望(立山・剱岳)

爺ヶ岳中峰山頂からの眺望(立山・剱岳)

爺ヶ岳中峰山頂からの眺望(穂高連峰・槍ヶ岳)

爺ヶ岳中峰山頂からの眺望(穂高連峰・槍ヶ岳)

爺ヶ岳中峰山頂からの眺望(蓮華岳・針ノ木岳・スバリ岳・薬師岳)

爺ヶ岳中峰山頂からの眺望
(蓮華岳・針ノ木岳・スバリ岳・薬師岳)

爺ヶ岳中峰山頂からの眺望(八ヶ岳・富士山・南アルプス)

爺ヶ岳中峰山頂からの眺望
(八ヶ岳・富士山・南アルプス)

爺ヶ岳中峰山頂からの眺望(四阿山・浅間山)

爺ヶ岳中峰山頂からの眺望(四阿山・浅間山)

爺ヶ岳中峰山頂からの眺望(富士山)

爺ヶ岳中峰山頂からの眺望(富士山)

爺ヶ岳中峰山頂からの眺望(八ヶ岳)

爺ヶ岳中峰山頂からの眺望(八ヶ岳)

蓮華岳・針ノ木岳・スバリ岳・薬師岳

蓮華岳・針ノ木岳・スバリ岳・薬師岳

メルヘンチックな景色(種池山荘)

メルヘンチックな景色(種池山荘)

柏原新道の紅葉(1)

柏原新道の紅葉(1)

柏原新道の紅葉(2)

柏原新道の紅葉(2)

柏原新道の紅葉(3)

柏原新道の紅葉(3)

柏原新道の紅葉(4)

柏原新道の紅葉(4)

柏原新道の紅葉(5)

柏原新道の紅葉(5)

柏原新道の紅葉(6)

柏原新道の紅葉(6)

薬師の湯

薬師の湯

コメント   

夏休みの代わりの休暇を取り、紅葉狩りを兼ねて後立山連峰(五竜岳・鹿島槍ヶ岳)を縦走してきました。

2013年10月7日(月)

 最初に目指すは五竜岳。五竜へ登るには八方尾根ルートもしくは遠見尾根ルートのほぼ2択。八方池にも魅力を感じましたが、「遠見尾根の紅葉は素晴らしい」という情報を得ていたので今回はこちらを選ぶことにしました。 このルートでは、山麓のとおみ駅(標高817m)からテレキャビン(ゴンドラ)でアルプス平(1,515m)まで行き、そこから登り始めるというのが一般的ですが、この時期は平日だとテレキャビンの始発時刻は8:15と遅め。山の景色を楽しむには午前中が勝負なので(午後になるとガスってくることが多い)、これでは存分に楽しめないと思い、ゲレンデを歩いて登ることにしました。

 ヘッドライトを点けてまだ暗いうちから登り始め・・・結構傾斜がきつくて朝一番からハードですが、ゲレンデ中腹で迎えた御来光に癒され6:30頃にアルプス平に到着。2時間の貯金が出来ました。この時間だと他の登山客は皆無で、下山される方とすれ違い始めるまでのしばらくは、一人静かに景色を独り占め出来ました。地蔵の頭から小遠見山、中遠見山を経て大遠見山辺りまで、まさに今が紅葉のピークといった感じで溜息が出るような美しさ。ダケカンバにナナカマド、山モミジ他、色とりどり・・・「山全体が紅葉で染まる」というのはこういうことなんですね。ここまで凄い紅葉はこれまで経験したことありませんでした。しかも、ここは紅葉が素晴らしいだけでなく、バックに五竜岳や鹿島槍ヶ岳の雄姿が加わるので贅沢の極みです。

 見頃となる期間は短いかもしれませんが、ここの紅葉は本当にお薦めです。大混雑必至の上高地・涸沢を避けて遠見尾根へという選択も大いに有りだと思います。前述のとおり、ゴンドラで高度を稼げるので登山をされない方でもちょっとしたハイキング感覚で紅葉を堪能出来ます。

 西遠見山辺りで紅葉は見納めとなりましたが、天気が最高だったので稜線の景色もまた格別。白岳、五竜山荘を通過して五竜岳山頂へ・・・。山頂付近の岩場には所々にラッカーでガイドの○印がつけられているのですが、どうも途中で見落としてしまったようで間違ったルートに入ってしまいました。しかし、そこでラッキーなことに雷鳥に初遭遇!トコトコと軽快に斜面を登る姿を追って歩いていくうちに正しいルートに復帰しました。この雷鳥は道案内をしてくれたようです。

 そんな嬉しいハプニングを経て10:10頃に五竜岳山頂へ到着。眺望は抜群で西には剱岳(カッコいい!)や立山、北には白馬三山。南には鹿島槍ヶ岳の他、遠くに槍ヶ岳の雄姿も確認出来ました。

 当初の予定ではこの日は唐松岳頂上山荘に泊まり、翌日は北(不帰キレット経由で白馬岳)へ向かうか南(五竜に戻り鹿島槍ヶ岳へ)へ向かうかはその時の気分で決めようと思っていたのですが、想定よりも随分早く着いたので予定を変更・・・この日のうちに鹿島槍へ登って一気に冷池山荘まで行くことにしました。翌々日が雨予報となったので、山で2泊の計画を1泊に短縮・・・という天気事情もありますが、双耳峰である鹿島槍ヶ岳の姿に見惚れてしまい、早く登ってみたくなったというのが一番の理由です。

 五竜―鹿島槍間の稜線は良く整備されていて歩きやすかったですが、中継点のキレット小屋が1週間前に閉鎖されてしまったせいなのか登山客は少なく、すれ違ったのは5人程(同方向の登山客を抜いたり抜かれたりはゼロ)。ここでも静かな山行を堪能出来ました。「後立山」と呼ばれる稜線だけに、右(西側)を向くと剱岳・立山の姿が常に確認出来て気分爽快。一方、東側の谷はずっと霧で覆われた状態。「安曇野」という地名の由来はこの霧にあるのでしょうか?(少し調べた限りでは諸説あるようで良くわかりませんでした)。

 途中、一番の難所とされる八峰キレットは「日本3大キレット」の1つだそうですが(後の2つは不帰キレットと槍ヶ岳-穂高間の大キレット)、危険を感じるようなことは殆どありませんでした。とはいえ足を踏み外せば一気に谷底へ・・・という箇所もあるので油断は禁物ですね(視界不良や強風などの悪天候時は特に)。

 鹿島槍ヶ岳山頂に到着した時(14時頃)にはガスが出始めていて眺望は今一つでした。「もしゴンドラの始発を待っていたら五竜岳山頂の展望も同様だったかも?」と考えると、やはりゲレンデを歩いて登ったのは正解でした。冷池山荘へは15時過ぎに到着。この時期の平日となるとガラガラなので予約なしでも余裕でOKでした。

<この日の行程>
白馬五竜エスカルプラザ(5:15)-アルプス平(6:25)-地蔵の頭(6:40)-小遠見山(7:24)-大遠見山(8:08)-西遠見山(8:45頃)-五竜山荘(9:25頃)-五竜岳山頂(10:10)-G5(10:45頃)-北尾根ノ頭(11:29)-キレット小屋(12:26)-鹿島槍ヶ岳北峰山頂(13:34)-鹿島槍ヶ岳南峰山頂(14:05)-布引岳(14:37)-冷池山荘(15:14)

まだ暗い中、登山開始(エスカルプラザ)

まだ暗い中、登山開始(エスカルプラザ)

朝一番のゲレンデ直登は結構ハード

朝一番のゲレンデ直登は結構ハード

ゲレンデで迎えた御来光

ゲレンデで迎えた御来光

ゲレンデ上部斜面の紅葉

ゲレンデ上部斜面の紅葉

地蔵の頭付近の紅葉

地蔵の頭付近の紅葉

地蔵の頭

地蔵の頭

遠見尾根の紅葉(1)

遠見尾根の紅葉(1)

遠見尾根の紅葉(2)

遠見尾根の紅葉(2)

遠見尾根の紅葉(3)

遠見尾根の紅葉(3)

遠見尾根の紅葉(4)

遠見尾根の紅葉(4)

遠見尾根の紅葉(5)

遠見尾根の紅葉(5)

遠見尾根の紅葉(6)

遠見尾根の紅葉(6)

遠見尾根の紅葉(7)

遠見尾根の紅葉(7)

遠見尾根の紅葉(8)

遠見尾根の紅葉(8)

五竜岳と唐松岳

五竜岳と唐松岳

白馬三山

白馬三山

五竜岳

五竜岳

小遠見山より五竜・鹿島槍を望む

小遠見山より五竜・鹿島槍を望む

小遠見山より唐松岳・白馬三山を望む

小遠見山より唐松岳・白馬三山を望む

鹿島槍

鹿島槍

中遠見山(1)

中遠見山(1)

中遠見山(2)

中遠見山(2)

紅葉+山(1)

紅葉+山(1)

紅葉+山(2)

紅葉+山(2)

紅葉+山(3)

紅葉+山(3)

紅葉+山(4)

紅葉+山(4)

紅葉+山(5)

紅葉+山(5)

五竜山荘と五竜岳

五竜山荘と五竜岳

雷鳥(1)

雷鳥(1)

雷鳥(2)

雷鳥(2)

五竜岳山頂

五竜岳山頂

五竜岳山頂からの眺望(剱岳)

五竜岳山頂からの眺望(剱岳)

五竜岳山頂からの眺望(立山)

五竜岳山頂からの眺望(立山)

五竜岳山頂からの眺望(鹿島槍ヶ岳)

五竜岳山頂からの眺望(鹿島槍ヶ岳)

五竜岳山頂からの眺望(槍ヶ岳・穂高連峰)

五竜岳山頂からの眺望(槍ヶ岳・穂高連峰)

五竜岳山頂からの眺望(北側 唐松岳・白馬三山)

五竜岳山頂からの眺望(北側 唐松岳・白馬三山)

G5近辺

G5近辺

右(西)にはいつも立山・剱岳の姿

右(西)にはいつも立山・剱岳の姿

霧で覆われる東側の谷

霧で覆われる東側の谷

北尾根ノ頭より 五竜岳

北尾根ノ頭より 五竜岳

北尾根ノ頭より 鹿島槍ヶ岳

北尾根ノ頭より 鹿島槍ヶ岳

楽しいクサリ場、岩登り

楽しいクサリ場、岩登り

キレット小屋は既に営業終了

キレット小屋は既に営業終了

八峰キレット 足を踏み外せば谷底

八峰キレット 足を踏み外せば谷底

八峰キレット

八峰キレット

鹿島槍ヶ岳 何とも寂しい北峰山頂

鹿島槍ヶ岳 何とも寂しい北峰山頂

鹿島槍ヶ岳(南峰)山頂

鹿島槍ヶ岳(南峰)山頂

鹿島槍ヶ岳山頂 展望は今一つ

鹿島槍ヶ岳山頂 展望は今一つ

爺ヶ岳。北峰、中峰(主峰)、南峰の3峰からなります。

爺ヶ岳。北峰、中峰(主峰)、南峰の3峰からなります。

冷池山荘

冷池山荘

コメント