JPGのExif情報に直接読み書きしてマップに表示。

PHPを使い、JPGのExif情報に直接読み書きしてマップに表示してみました。
こんな感じ。
http://www.moeten.info/flex/20081205_exifPHP/bin-release/main.html

前回との違いは直接JPGファイルに埋め込まれているExifをいじってます。
アイコンをドラッグするとJPGファイルのExifの緯度経度情報を更新します。
これだとデータベースが入らないのが便利ですね。
あとExifは撮影日や細かいカメラ設定も取得することができるので、旅記録としてはかなり便利。

メイン部分の簡単な説明

マーカードラッグ完了イベントでサーバーへ情報を送信

marker.addEventListener(MapMouseEvent.DRAG_END , function():void{
    htsFileUpdate.send({
        "fname" :myFname,
        "lat":marker.getLatLng().lat(),
        "lon":marker.getLatLng().lng()
    });
});

PHP側でJPG画像にExif部分に緯度経度を書き込み

if( $_GET['mode'] == "update" && $_GET['fname'] != "" && $_GET['lat'] != "" && $_GET['lon'] != "" ){
    //Exif情報の更新
    $safe_a = array( ".." , "/" );
    $safe_b = array( ""   , ""  );
    $fname = "image/" . str_replace( $safe_a , $safe_b , $_GET['fname'] );
    if( ! file_exists( $fname ) )exit;
    $lat = substr( $_GET['lat'] , 0 , 20 );
    $lon = substr( $_GET['lon'] , 0 , 20 );
    $gps = degree2dms( $lat , $lon );
    $lat_a = explode( "." , $gps["lat"] );
    $lon_a = explode( "." , $gps["lon"] );
    //GPS情報の書き込み
    $jpeg = new PelJpeg( $fname );
    $ifd0 = $jpeg->getExif()->getTiff()->getIfd();
    $ifd0->getSubIfd(PelIfd::GPS);
    $gps = new PelIfd(PelIfd::GPS);
    $ifd0->addSubIfd($gps);
    $gps->addEntry(new PelEntryRational(PelTag::GPS_LATITUDE, array($lat_a[0], 1), array($lat_a[1], 1), array($lat_a[2], 100)));
    $gps->addEntry(new PelEntryAscii(   PelTag::GPS_LATITUDE_REF, 'N'));
    $gps->addEntry(new PelEntryRational(PelTag::GPS_LONGITUDE, array($lon_a[0], 1), array($lon_a[1], 1), array($lon_a[2], 100)));
    $gps->addEntry(new PelEntryAscii(   PelTag::GPS_LONGITUDE_REF, 'E'));
    file_put_contents( $fname , $jpeg->getBytes() );
}

参考リンク

すべてのソースコードはこちら
Flex部分のソースコードはこちら

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    creationComplete="creationCompleteHandler(event)" backgroundGradientColors="[0xffffff,0x999999]" viewSourceURL="srcview/index.html">
<mx:Script>
<![CDATA[
import com.google.maps.MapEvent;
import com.google.maps.LatLng;
import com.google.maps.Map;
import com.google.maps.InfoWindowOptions;
import com.google.maps.styles.*;
import com.google.maps.interfaces.IPolyline;
import com.google.maps.overlays.*;
import com.google.maps.controls.*;
import com.google.maps.*;
import com.google.maps.services.*;
[Bindable]public var myXMLDATA:XMLList;
//マップ作製
private var map:Map;
private function creationCompleteHandler(e:Event):void{
    makeMap();
}
private function makeMap():void{
    map = new Map();
    map.key = "ABQIAAAAq9Z1Z3_S-AQpWPqQe5Gh1RRD4wMK23XJSE0pd0Xt9l-AeMpaYxRIezvnX8EKJ4gZ1xqmuo8WFOd9LA";
    myUI.addChild( map );
    map.language = "JP";
    map.width = myUI.width;
    map.height = myUI.height;
    map.addEventListener( MapEvent.MAP_READY, onMapReady );
}
private function onMapReady(e:Event):void{
    //装飾追加
    var zoomControl:ZoomControl = new ZoomControl()
    var z:ZoomControlOptions    = new ZoomControlOptions();
    map.addControl(zoomControl);
    var pos:PositionControl     = new PositionControl();
    map.addControl(pos);
    map.enableContinuousZoom();
    map.setCenter( new LatLng(34.50655662164561,134.835205078125)  , 7, MapType.NORMAL_MAP_TYPE );
    htsFileList.send();
}
private var markerArr:Array = new Array();
private function onHtsFileListResultHandler(e:Event):void{
    myXMLDATA = htsFileList.lastResult.item;
    for( var i:int = 0 ; i < myXMLDATA.length() ; i ++ ){
        if( myXMLDATA[i].lat != "" && myXMLDATA[i].lng != "" ){
            if(markerArr[myXMLDATA[i].id])return;
            var loader:Loader = new Loader();
            loader.name = "" + i + "-" + myXMLDATA[i].id + "-" + myXMLDATA[i].fname;
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE , function(e:Event):void{
                var myBitmap:Bitmap = Bitmap(LoaderInfo(e.currentTarget).content);
                myBitmap.width   = 40;
                myBitmap.height  = 40;
                myBitmap.filters = [gf];
                var myI:int        = int( String( LoaderInfo(e.currentTarget).loader.name ).split("-")[0]);
                var myId:int       = int( String( LoaderInfo(e.currentTarget).loader.name ).split("-")[1]);
                var myFname:String = String( LoaderInfo(e.currentTarget).loader.name ).split("-")[2];
                var marker:Marker = new Marker(
                                                new LatLng( myXMLDATA[myI].lat , myXMLDATA[myI].lng ) ,
                                                new MarkerOptions({
                                                    draggable :true,
                                                    icon:myBitmap,
                                                    iconAlignment: MarkerOptions.ALIGN_HORIZONTAL_CENTER,
                                                    hasShadow:true
                                                })
                                            );
                markerArr[myId] = marker;
                marker.addEventListener(MapMouseEvent.DRAG_END , function():void{
                    myXMLDATA[myI].lat = marker.getLatLng().lat();
                    myXMLDATA[myI].lng = marker.getLatLng().lng();
                    htsFileUpdate.send({
                        "fname" :myFname,
                        "lat":marker.getLatLng().lat(),
                        "lon":marker.getLatLng().lng()
                    });
                });
                map.addOverlay(marker);
            });
            loader.load( new URLRequest( myXMLDATA[i].image ) );
        }
    }
}
private function onItemSelected(e:Event):void{
    map.panTo( new LatLng( dg.selectedItem.lat , dg.selectedItem.lng ) );
}
]]>
</mx:Script>
<!--############  ############-->
<mx:GlowFilter id="gf" color="0xffffff" blurX="4" blurY="4" strength="10"/>
<!--############  ############-->
<mx:HTTPService id="htsFileList" url="http://www.moeten.info/flex/20081205_exifPHP/" resultFormat="e4x"
    result="onHtsFileListResultHandler(event)"/>
<mx:HTTPService id="htsFileUpdate" url="http://www.moeten.info/flex/20081205_exifPHP/?mode=update"
    resultFormat="e4x"/>
<!--############  ############-->
<mx:UIComponent id="myUI" width="507" height="533" x="10" y="66"/>
<mx:DataGrid id="dg" x="525" y="66" width="310" height="533" dataProvider="{htsFileList.lastResult.item}"
    backgroundAlpha="0.5" itemClick="onItemSelected(event)">
    <mx:columns>
        <mx:DataGridColumn headerText="画像">
            <mx:itemRenderer>
                <mx:Component>
                    <mx:Image width="50" height="50" source="{data.image}"/>
                </mx:Component>
            </mx:itemRenderer>
        </mx:DataGridColumn>
        <mx:DataGridColumn headerText="撮影日" dataField="date"/>
        <mx:DataGridColumn headerText="緯度" dataField="lat"/>
        <mx:DataGridColumn headerText="経度" dataField="lng"/>
    </mx:columns>
</mx:DataGrid>
<mx:Label x="10" y="10" text="Exif2Map(Direct)" color="0x999999" fontSize="30"/>
<mx:HRule x="10" y="52" width="825" height="8"/>
</mx:Application>

PHP部分のソースコードはこちら

<?php
$CodeInternal = 'SJIS';//内部処理コード
$CodeExport   = 'SJIS';//出力コード
mb_internal_encoding($CodeInternal);
mb_regex_encoding($CodeInternal);
function d() {
    echo '<pre style="background:#fff;color:#333;border:1px solid #ccc;margin:2px;padding:4px;font-family:monospace;font-size:12px">';
    foreach (func_get_args() as $v) var_dump($v);
    echo '</pre>';
}
function getexif($fname) {
    $cls = exif_read_data($fname, 'EXIF');
    foreach ($cls as $key=>$sect) {
        if (is_array($sect) == FALSE) {
            $exif[$key] = $sect;
        } else {
            foreach($sect as $name=>$val)$exif[$key . '.' . $name] = $val;
        }
    }
    return $exif;
}
//角度を度分秒表記に変換
function degree2dms( $lat , $lon ){
    $lat_lon = array(
                     "lat" => $lat,
                     "lon" => $lon,
                     );
    foreach( $lat_lon as $key => $degree ){
        $d = floor($degree);
        $m = floor(($degree-$d)*60);
        $s = floor(($degree-$d-$m/60)*3600);
        $u = floor(($degree-$d-$m/60-$s/3600)*360000);
        $s =  $s * 100 + $u ;
        $dms = "$d.$m.$s";
        $gps[$key] = $dms;
    }
    return( $gps );
}
//度分秒表記を角度に変換
function dms2degree( $pos_n , $pos_e ){
    $posN_a = explode( "." , $pos_n );
    $posE_a = explode( "." , $pos_e );
    $posN_a[2] = $posN_a[2] . "." .$posN_a[3];
    $posE_a[2] = $posE_a[2] . "." .$posE_a[3];
    $posN = $posN_a[0] + $posN_a[1]/60 + $posN_a[2]/3600;//北緯
    $posE = $posE_a[0] + $posE_a[1]/60 + $posE_a[2]/3600;//東経
    $gps['lat'] = $posN;
    $gps['lon'] = $posE;
    return $gps;
}
require_once('pel/PelJpeg.php');
if( $_GET['mode'] == "" ){
    //Exif情報を配列へ
    $xml = <<<EOD
        <result>
EOD;
    $dir = dir( "./image" );
    while( $item = $dir->read() ){
        if( $item != ".." && $item != "." ){
            $fname = "image/{$item}";
            if (($cls = exif_read_data( $fname, 'EXIF')) == FALSE)    return (-3);
            foreach ($cls as $key=>$sect) {
                if (is_array($sect) == FALSE) {
                    $exif[$key] = $sect;
                } else {
                    foreach($sect as $name=>$val){
                        $exif[$key . '.' . $name] = $val;
                    }
                }
            }
            $date = $exif["DateTimeOriginal"];
            $lat_a0 = explode( "/" , $exif['GPSLatitude.0'] );
            $lat_a1 = explode( "/" , $exif['GPSLatitude.1'] );
            $lat_a2 = explode( "/" , $exif['GPSLatitude.2'] );
            $lat_a2[0] = $lat_a2[0]/100;
            $pos_n = "{$lat_a0[0]}.{$lat_a1[0]}.{$lat_a2[0]}";
            $lng_a0 = explode( "/" , $exif['GPSLongitude.0'] );
            $lng_a1 = explode( "/" , $exif['GPSLongitude.1'] );
            $lng_a2 = explode( "/" , $exif['GPSLongitude.2'] );
            $lng_a2[0] = $lng_a2[0]/100;
            $pos_e = "{$lng_a0[0]}.{$lng_a1[0]}.{$lng_a2[0]}";
            $gps = dms2degree($pos_n,$pos_e);
            $xml .= <<<EOD
  <item>
    <id>{$cnt}</id>
    <image>http://www.moeten.info/flex/20081205_exifPHP/image/{$item}</image>
    <fname>{$item}</fname>
    <lat>{$gps['lat']}</lat>
    <lng>{$gps['lon']}</lng>
    <date>{$date}</date>
    <timestamp>{$exif['FileDateTime']}</timestamp>
  </item>
EOD;
            $cnt ++;
        }
    }
    $xml .= <<<EOD
</result>
EOD;
    header ("Content-Type: text/xml; charset=UTF-8");
    echo $xml;
    exit;
}elseif( $_GET['mode'] == "update" && $_GET['fname'] != "" && $_GET['lat'] != "" && $_GET['lon'] != "" ){
    //Exif情報の更新
    $safe_a = array( ".." , "/" );
    $safe_b = array( ""   , ""  );
    $fname = "image/" . str_replace( $safe_a , $safe_b , $_GET['fname'] );
    if( ! file_exists( $fname ) )exit;
    $lat = substr( $_GET['lat'] , 0 , 20 );
    $lon = substr( $_GET['lon'] , 0 , 20 );
    $gps = degree2dms( $lat , $lon );
    $lat_a = explode( "." , $gps["lat"] );
    $lon_a = explode( "." , $gps["lon"] );
    //GPS情報の書き込み
    $jpeg = new PelJpeg( $fname );
    $ifd0 = $jpeg->getExif()->getTiff()->getIfd();
    $ifd0->getSubIfd(PelIfd::GPS);
    $gps = new PelIfd(PelIfd::GPS);
    $ifd0->addSubIfd($gps);
    $gps->addEntry(new PelEntryRational(PelTag::GPS_LATITUDE, array($lat_a[0], 1), array($lat_a[1], 1), array($lat_a[2], 100)));
    $gps->addEntry(new PelEntryAscii(   PelTag::GPS_LATITUDE_REF, 'N'));
    $gps->addEntry(new PelEntryRational(PelTag::GPS_LONGITUDE, array($lon_a[0], 1), array($lon_a[1], 1), array($lon_a[2], 100)));
    $gps->addEntry(new PelEntryAscii(   PelTag::GPS_LONGITUDE_REF, 'E'));
    file_put_contents( $fname , $jpeg->getBytes() );
}
?>