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 情報が更新されている事が確認できる。