MastHead

 写真の Exif データや、撮影地の地図を表示する方法 (その5)


このページは「写真の Exif データや、撮影地の地図を表示する方法」のバージョン5です。
過去に1から4までがありますので、まず変更部分について解説しておきます。

(その1)は Jacob Seidelin 氏のサイト(今は閉鎖)を参考にして、私がコードのバグをつぶすなどしたものですが、コード手法が見通しの悪いものでした。
(その2)は私が全面的にコーディングしたもので、機能を限定して見通しが良く小型にしたものです。
(その3)は Google が 2018/7/16 に課金方法を変更したので、無課金になる方法を実装しました。
(その4)は3とほぼ同じですが map をバージョン 78 以前の Edge にも対応させて iframe で表示しました。  また、exif を alert box で出していたものを少しスマートにしました。  さらに、mineo のユーザーが特定の時間帯だけ EXIF/MAP を表示できなかった問題に対応しました。
(その5)は、Google Chrome が持っていたバグが無くなったことと、Map を URL を使って開いたときの余分な情報を消せるようになったことを受け:
・Pegman アイコンを出して Street View が使えるようにしました。
・pop-up を使わず iPhone の default の設定でも地図が出るようにしました。
・API キーが無くても使用できるようにしました。


-----------------------------------
動作の解説:
私の写真のサイトの各ページに掲載した絵の横に、「Exif」や「Map」という文字があります。  それをクリックするとシャッタ速度や絞り値の簡単な Exif データが現れたり、Google Maps をつかった地図が現れて撮影地点を表示したりします。 
とりあえず例を示します。



  Exif     Map


上の Exif の文字をクリックしますと、Exif を表示します。  また、Map の文字をクリックしますと、Map を表示します。

動かしているコードの中の最小限の部分を抜き出したものを下に示します。  お見せしてるコードでは Style や Javascript は html ファイルの中に埋め込まれていますが、実際には別ファイルにして読み込ませています。

<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<title>Test Case</title>
<style><!--
.msgbox{border:outset 2px white; background-color:gainsboro; color:#003399;
    position:fixed; top:50%; left:5%; width:345px; height:180px;
    font-family:"Lucida Console", monospace; font-size:13px;}
.cross{border:outset 2px white;
    width:15px; padding-left:5px; float:right; cursor:pointer;}
.innerText{width:100%; padding-top:4px; padding-left:10px; text-align:left; line-height:1.2;}
--></style>
</head>
<body>

<IMG src="AAA.jpg" id="pic2" style="float:left;margin:4px;" alt=""><br><br> 
<a href="javascript:show_exif(2)">Exif</a>,    
<a href="javascript:show_map(2)">Map</a>

<div id="msgzone" class="msgbox" style="display:none;">
<div class="cross" onclick="document.getElementById('msgzone').style.display='none';">X</div>
<div class="innerText" id="altmsg"></div></div>

<script><!--
// s-exif.js to show picture's EXIF and map       Ⓒ copyright T. Fujiwara  3/30/2017
// Save in UTF-8 format.
// mod: 7/20/2018 Change to Embed API (or URL API)
// mod: 9/23/2018 change for iPhone: outputs warning "Enable PopUp"
// mod: 3/26/2019 enable iframe for Edge
// mod: 5/4/2019  replace "alert" box with smarter msg box
// mod: 12/22/2019 extended II search area in response to mineo's compression
// mod: 3/7/2022 display map in current tab. no API key required.

var reqtype, url, tiff, TT, ET, GT, nWin, en, ww, hh, gmaps, gkey, ddu, mmu, ssu;
var EDeg, NDeg, adr, str2, i, IEoffset, version, req, copyright, model, shutter, iso, bias;
var focullength, lensmodel, uu, dd, fnumber, uadr; 
var imd=Array();   // image data in array format

//----------- entry of showEXIF/showMap -----------------
function show_exif(i) { reqtype="showexif"; loadXMLDoc(i,reqtype); }
function show_map(i)  { reqtype="showmap";  loadXMLDoc(i,reqtype); }

//----------- small common subroutines ------------------
function longint(p){          //make long integer number from 4 bytes at p
  if (en=="I") t1=imd[p+3]*16777216+imd[p+2]*65536+imd[p+1]*256+imd[p];
  else t1=imd[p]*16777216+imd[p+1]*65536+imd[p+2]*256+imd[p+3];
  return t1;}
function shortint(p){if (en=="I") t2=imd[p+1]*256+imd[p]; else t2=imd[p]*256+imd[p+1]; return t2;}
function getstr(p){           //make ascii string from [address p+8]
  str=""; start=longint(p+8)+tiff; end=start+longint(p+4);
  for (i=start; i<end; i++) {if (imd[i]!=0x00) str+=String.fromCharCode(imd[i]);}
  return str;}
function gettagadr(tag,p){    //search for Tag in Tiff, Exif, or Geo tables, then return Tag address
  t3="";start=p+2;end=start+12*shortint(p);
  for (i=start;i<end;i=i+12) {if (shortint(i)==tag) {t3=i; break;}}
  return t3;}

//----------- main routine, with AJAX -------------------
function loadXMLDoc(i,reqtype){
  ua=navigator.userAgent; IEoffset=ua.indexOf("MSIE ");
  if (IEoffset>=0) version=parseFloat(ua.substring(IEoffset+5,ua.indexOf(";",IEoffset)));
  if (IEoffset>=0 && version<10.0) alert("Your browser does not support this function. Please use Firefox.");
  else{
    url= document.getElementById("pic"+i).src;
    try {req=new XMLHttpRequest();}        //as for IE, ver 7 and later only
      catch(e){req=null; alert("Your browser does not support this function.");}
    if(req){
      req.open("GET",url,true);
      req.responseType="arraybuffer";      //this works on IE10 and later
      req.onload=function(oEvent) {        //run when data become available, let it stand-by
        var arrayBuffer=req.response;
        if (arrayBuffer){
          imd=new Uint8Array(arrayBuffer); //imd (image data) is a byte array, element is a number
          en="";i=2;
          for (j=1;j<4;j++){               // max test segments=3
            if (shortint(i)==0xFFE1){      // look for exif APP segment
              tiff=i+10;
              if      (shortint(tiff)==0x4949) en="I"; // Intel endianness
              else if (shortint(tiff)==0x4D4D) en="M"; // Motorola endianness
              break;
            }
            else i=i+2+shortint(i+2);      // next APP segment (skip 0xFFED added by mineo)
          }
          if (en=="") alert("Sorry, the image data is not compatible with this utility. "+i);
          else{
            TT=tiff+8;                     //set TT (Tiff Table) entry address
            if (reqtype == "showexif") displayEXIF();
            else displayMap();
          }
        }  //end arrayBuffer
      };  //end oEvent
      req.send(null);                      //go request the image data
    } //end req e.g. >=IE7 or valid browsers
  } //end >=IE10
}
//----------- display EXIF -----------------------------
function displayEXIF(){

  //tag=0x8769  subIFD, ExifIFDPointer
  ET=""; adr=gettagadr(0x8769,TT);
  if (adr) ET=longint(adr+8)+tiff;    //set ET (Exif Table) entry

  //tag=0x0110 Model in TIFF, ascii string, any length
  model=""; adr=gettagadr(0x0110,TT);
  if (adr) model=getstr(adr);

  //tag=0x829A ExposureTime in EXIF, unsigned rational, 8 bytes
  shutter=""; adr=gettagadr(0x829A,ET);
  if (adr){
    uadr=longint(adr+8)+tiff; uu=longint(uadr); dd=longint(uadr+4);
    if (uu%10==0 && dd%10==0){uu=uu/10;dd=dd/10;}
    shutter=uu+"/"+dd;
  }

  //tag=0x829D FNumber, unsigned rational, 8 bytes
  fnumber=""; adr=gettagadr(0x829D,ET);
  if (adr){
    uadr=longint(adr+8)+tiff; uu=longint(uadr); dd=longint(uadr+4);
    fnumber=uu/dd+"";
  }

  //tag=0x8827 ISO, unsigned short, 2 bytes
  iso=""; adr=gettagadr(0x8827,ET);
  if (adr) iso=shortint(adr+8)+"";

  //tag=0x9204 ExposureBias, signed rational, 8 bytes
  bias=""; adr=gettagadr(0x9204,ET);
  if (adr){
    uadr=longint(adr+8)+tiff; uu=longint(uadr); dd=longint(uadr+4);
    if (uu>=0x80000000) uu=uu-0xffffffff-1;
    bias=uu/dd; if (bias>0) bias="+"+bias; bias=(bias+"").substr(0,5);
  }

  //tag=0x920A FocalLength, unsigned rational, 8 bytes
  focallength=""; adr=gettagadr(0x920A,ET);
  if (adr){
    uadr=longint(adr+8)+tiff; uu=longint(uadr); dd=longint(uadr+4);
    focallength=uu/dd+"";
  }

  //tag=0xA434 LensModel, ascii string, as many bytes
  lensmodel=""; adr=gettagadr(0xA434,ET);
  if (adr) lensmodel=getstr(adr);
  if (lensmodel.length>25){
     blankl=lensmodel.indexOf(" ",20);
     if (blankl>=20){
       lensmodel=lensmodel.substr(0,blankl)+"<br>                 "+lensmodel.substr(blankl+1);
     }
  }

  //tag=0x8298 Copyright in TIFF, ascii string, as many bytes
  copyright=""; adr=gettagadr(0x8298,TT);
  if (adr) copyright=getstr(adr);

  str1="<pre>Model:           "+model+"<br>";
  str1+="Exposure Time:   "+shutter+" (sec)<br>";
  str1+="F Number:        "+fnumber+"<br>";
  str1+="ISO Speed Rating:"+iso+"<br>";
  str1+="Exposure Bias:   "+bias+"<br>";
  str1+="Focal Length:    "+focallength+" (mm)<br>";
  if (lensmodel) str1+="Lens Model:      "+lensmodel+"<br>";
  if (copyright) str1+="Ⓒ  "+copyright+"<br>;    //Ⓒ = U+24B8
  str1+="</pre>";

  document.getElementById("altmsg").innerHTML=str1;
  document.getElementById("msgzone").style.display='block';
}

//------------- display map ------------------------------
function displayMap(){
  //Tag 0x8825 Geo tag location
  GT=""; adr=gettagadr(0x8825,TT);
  if (adr) GT=longint(adr+8)+tiff;    //set GT (Geo Table) entry

  //tag=0x0001 GPSLatitudeRef
  latref="";
  if (GT){
    adr=gettagadr(0x0001,GT);
    if (adr) latref=String.fromCharCode(imd[adr+8]);
  }

  if(latref=="N" || latref=="S"){
    //tag=0x0002 GPSLatitude
    NDeg=0; adr=gettagadr(0x0002,GT);
    if (adr){
      p=longint(adr+8)+tiff;
      ddu=longint(p); ddd=longint(p+4);
      mmu=longint(p+8); mmd=longint(p+12);
      ssu=longint(p+16); ssd=longint(p+20);
      NDeg=(ssu/ssd/60+mmu/mmd)/60+ddu/ddd;
      if (latref=="S") NDeg=-NDeg;
    }

    //tag=0x0003 GPSLongitudeRef
    lonref=""; adr=gettagadr(0x0003,GT);
    if (adr) lonref=String.fromCharCode(imd[adr+8]);

    //tag=0x0004 GPSLongitude
    EDeg=0; adr=gettagadr(0x0004,GT);
    if (adr){
      p=longint(adr+8)+tiff;
      ddu=longint(p); ddd=longint(p+4);
      mmu=longint(p+8); mmd=longint(p+12);
      ssu=longint(p+16); ssd=longint(p+20);
      EDeg=(ssu/ssd/60+mmu/mmd)/60+ddu/ddd;
      if (lonref=="W") EDeg=-EDeg;
    }

    location.href="https://www.google.com/maps/place/"+NDeg+"+"+EDeg+"/"+"@"+NDeg+","+EDeg+",12z";

  } // end {if latref}
  else alert("Sorry, no map data is available for this picture.");
}
--></script>
</body>
</html>


=== Exif をクリックすると ===
絵の横にある Exif という文字列をクリックすると、
      <a href="javascript:show_exif(2)">Exif</a> 
によって 2 という引数をもって show_exif の関数が起動されます。 この 2 というのは pic2 の 2 です。
show_exif 関数は AJAX を使って絵のバイナリー・データを取得し、TIFF ヘッダや EXIF ヘッダからシャッタ・スピードなどの値を抜き出します。  そして <div id="msgzone" で作るメッセージ・ボックスを使って小さな窓にデータを表示します。
(この仕掛けはちょっと工夫しています。 alert の窓を使った場合は font の type や色が指定できませんので、それをやりたい方には参考になるかもしれません。)

=== Map をクリックすると ===
show_map の関数が起動されます。 show_exif の時と同様に画像データにアクセスし、緯度情報と経度情報を抜き出します。  ですから、あらかじめ画像ファイルの Exif の中に Latitude と Longitude の値を書き込んでおく必要があります。
取得した緯度経度情報は度分秒なので、これを十進数小数点形式の度の値に変換し、それを使って Google Maps を URL 形式で開きます。


-----------------------------------
Geo Tag について

画像ファイルに Latitude と Longitude を Geo Tag として埋め込むには各種のユーティリティがあります。 GPS Logger を購入すると、それに付属のソフトがある場合もあります。

手作業で書き込みたいときは、
  1.撮影場所の緯度経度の数値を知った上で、
  2.ユーティリティーを使って画像ファイルに書き込む
ことができます。
1については、 https://www.google.co.jp/maps/ を開いて任意の場所に行き、右クリックすると緯度と経度の数値が出ます。

2については、たとえば
   GPicSync   ( https://github.com/FrancoisSchnell/GPicSync )
の中にある Exif Writer や、
   Exif Pilot  ( http://www.colorpilot.com/exif.html )
というフリーウェアを使うと便利です。

私は、たくさんの写真を扱うときは GPS Logger からのデータを PhotoLocator というソフトに読み込ませ、たくさんの写真に一斉に書き込ませます。  また単独に地図上にマークを付けて、その位置をファイルに書き込むこともできます。 CR3 形式もサポートしています。
お金がかかってもいい方は Photoshop を買われると、Adobe Bridge に書き込む機能があります。 (下の左の図)
また、Lightroom をお使いの方は、「マップ」タブに入り、サムネイルの写真を選択して、地図の場所を右クリックすると GeoTag を書き込めます。 (下の右の図)




左の図は私が使っている GPS Logger の例です。 Firmware を 1.36 に上げると GPX のファイルを生成できるようになります。  上げないときは NMEA で書かれますので、GPS Babel を使って GPX に変換する場合もあるでしょう。
なお、この機械は1秒ごとにログを出しますが、撮影は徒歩ですので頻繁過ぎます。  私は自分でソフトを書いて5秒おきにするよう間引いています。 でないとファイルサイズが大きすぎて、ソフトによってはエラーになる場合があります。

撮影に出かける朝に、カメラの時刻を秒単位で合わせておいてください。 ロガーと同期させるためです。  よくそれを忘れますが、忘れたときは書き込みソフトで時間を調整する必要が出てきます。 



参照:
 1. ディジタルスチルカメラ用画像ファイルフォーマット規格Exif 3.0
 2. TIFF Revision 6.0 (pdf), 1992 JPG の EXIF は TIFF の EXIF の一部ですので、この仕様が必要です
 3. Description of Exif file format, by MIT, TsuruZoh Tachibanaya
 4. Google Maps Platform Documentation
 5. Google Maps Static API, Developper Guide
 6. Google Maps Embed API, Developper Guide (その4)で使っていた方法です 無料(例外あり)
 7. Google Maps JavaScript API Embed API よりも多彩なオプションが使えます 有料
 8. Google Maps URLs, Developper Guide Maps URL は無料ですが、左側に邪魔なペインが出ます(消せます)
 9. Google Maps API 価格表 どの方法が無料になるかわかります
10. Google Cloud Platform (要サインイン) API がどのぐらい使われたかが分かります


(3/7/2022)武        

Ⓒ Copyright 2022  T. Fujiwara      All rights reserved.
Archiving or copying this article without the author's permission is prohibited.

藤原 武 写真 集 アルバム Tak Fujiwara