アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

WPF で Google Map & GeoTag その 2

WPF で Google Maps API & GeoTag を扱うシリーズその 2。前回は画像の EXIF から GeoTag を生成して Google Maps 上にマーカー表示するところまで実装した。今回は GeoTag 編集と保存に対応してみる。

サンプル プログラム

前回からの変更点をまとめる。

  • 標高をサポート
  • GeoTag 保存をサポート

サンプル プロジェクト一式は以下。ビルドには Visual Studio 2008 SP1、プログラム実行は NET Framework 3.5 SP1 とネットワーク接続された環境が必要となる。

  • サンプルプロジェクト
  • ※緯度・経度の方角のパス指定が間違っていたので、2009/11/08 に zip を修正版に差し替えました。

このプログラムの GeoTag 書き換えは画像を直に更新するため破損する可能性があります。試される場合はテスト画像の用意を推奨します。

標高情報

緯度・経度を保存する場合、標高についても検討が必要。

カシミール 3D などでは緯度・経度とともに標高も設定可能。しかし緯度・経度だけを更新すると標高との矛盾が発生する。座標が変化したなら、その地点の標高を改めて取得反映するとか、標高そのものを削除するなどの対応が考えられる。

はじめは標高を消す方向で考えていたのだけど、調査したところアメリカ地質調査所の標高取得サービスから標高を取得できることが分かった。

この Web サービスの URL に緯度・経度を指定して実行すると標高データ XML を得られる。XML の内容は以下のようになる。

<?xml version="1.0" encoding="utf-8" ?>
<double>61</double>

Google Maps API V2 の場合、ファイルのダウンロードと XML パーサーをサポートしていたので、これらの処理を JavaScript で実装できたが、V3 では自前で用意する必要があって面倒。

また、前回までの記事でマップ上のマーカー移動をマネージ 側へ即時反映するように設計していたので、標高は C# 側で処理するようにした。

public double Altitude { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }

/// <summary>
/// 現在の緯度・経度を使用して、標高を更新します。
/// 更新に失敗した場合、標高は 0.0 に設定されます。
/// <para>
/// 標高は USGS ( アメリカ地質調査所 ) の getElevation サービスから取得しています。
/// USGS は全世界から参照される可能性があるので、サービスの負荷を軽減する為に、呼び出し頻度が少なくなるようにしてください。
/// </para>
/// </summary>
public void UpdateAltitude()
{
    this.Altitude = 0.0;

    try
    {
        // getElevation は X_Value に経度、Y_Value に緯度を指定する
        var url = String.Format( "http://gisdata.usgs.gov/xmlwebservices2/elevation_service.asmx/getElevation?X_Value={0}&Y_Value={1}&Elevation_Units=METERS&Source_Layer=-1&Elevation_Only=true", this.Longitude, this.Latitude );
        var xml = new XmlDocument();

        xml.Load( url );

        var element = xml.SelectSingleNode( "//double" );
        if( element != null )
        {
            // 不正な緯度・経度を指定した場合は "-1.79769313486231E+308" という文字列が入り、
            // Convert.ToDouble に失敗するので、必ず例外をハンドリングしておく事。
            //
            this.Altitude = Convert.ToDouble( element.InnerText );
        }
    }
    catch( Exception exp )
    {
        Debug.WriteLine( exp.Message );
    }
}

XmlDocument クラスは Web 上の XML も読み込める。よってサービスの URL を渡して戻り値を DOM 解析する。ただしコメントにもあるとおり、このサービスを頻繁に呼び出すと負荷などが心配なので、この処理は Map 上へマーカーを追加した時か、マーカーの移動が完了した時に限定して実行している。

マーカーの移動完了は JavaScript 上でマーカーの dragend をハンドリングしてそれをマネージ コードに通知するようにしている。JavaScript 上の実装は以下のようになる。

// 移動終了
google.maps.event.addListener( marker, 'dragend', function()
{
    window.external.OnMarkerMoveFinished( marker.id );
});

EXIF 情報の保存

今回のプログラムでは、以下の EXIF 情報を更新する。

  • 緯度
  • 経度
  • 標高 ( メートル )
  • 測地系 ( Google Map API なので WGS84 )
  • GPS バージョン ( 常に 2.0.0.0 )
  • 編集アプリ名 ( アセンブリのタイトル )

BitmapMetadata.GetQuery、SetQuery への書式指定では以下の資料が参考になった。

WPF で画像のメタデータを読み書きする場合、BitmapMetadata や InPlaceBitmapMetadataWriter クラスを使用するようだが正しく EXIF を書き換える為には工夫が必要らしい。今回は下記の実装を参考にした。

詳細はサンプルの GeoTag.cs における GeoTag.Save メソッドを参照の事。

プログラムの実行

プログラムを実行すると以下のようになる。

スクリーン ショット

画像のマーカーを移動すると編集済みの状態となる。この時に「編集内容を保存」ボタンを押すと、画像ファイルにマーカーの状態が保存される。保存後の画像を Picasa、ViewNX、カシミール 3D などで開くと、GPS 情報が更新されている事が確認できる。