WPF で GeoTag 読み込み
WPF の BitmapMetadata クラスによる GeoTag 読み込みを試してみた。
BitmapMetadata は画像のメタデータを編集するためのクラス。EXIF、IPTC、XMP に対応している。GeoTag は EXIF に属しているので、このクラスから編集可能。ただし MSDN の BitmapMetadata 解説を読んでも使いかたが分かりにくい。読み込み処理には BitmapMetadata.GetQuery メソッドを使用するのだけど、引数の書式指定などは謎。
色々とググってみたら、このクラスを利用した GPS 情報を読み込みサンプルと、EXIF における GPS 情報の格納方法について解説したページを見つけたので、とりあえずはこれらを頼りに実装してみる。以下、参考資料。
- GPS情報付画像のexif情報をc#のプログラムで配列に取得しました。
- Exif 2.1 GPS IFD tags
- How to read GPS metadata from image | Tamir Khason - Just code
今回の実験は単一クラスで十分なため、サンプル プロジェクトは作成しなかった。
実装
GeoTag 読み込みクラスを以下のように実装してみた。いずれ Google Maps API と絡めて GeoTag 編集を行ってみたいので、クラス名は Marker としておく。
/// <summary>
/// Google Map 内のマーカーを表すクラスです。
/// </summary>
class Marker
{
/// <summary>
/// 画像ファイルから Marker インスタンスを生成します。
/// </summary>
/// <param name="fileName">画像ファイルのパス。</param>
/// <returns>成功時は Marker インスタンス。失敗時は null 参照。</returns>
public static Marker FromImage( string fileName )
{
Marker marker = null;
using( var stream = new FileStream( fileName, FileMode.Open, FileAccess.Read, FileShare.Read ) )
{
var decoder = new JpegBitmapDecoder( stream, BitmapCreateOptions.None, BitmapCacheOption.None );
var metadata = ( BitmapMetadata )decoder.Frames[ 0 ].Metadata;
var latitude = metadata.GetQuery( "/app1/ifd/gps/subifd:{ulong=2}" ) as ulong[];
if( latitude == null ) { return null; }
var longitude = metadata.GetQuery( "/app1/ifd/gps/subifd:{ulong=4}" ) as ulong[];
if( longitude == null ) { return null; }
marker = new Marker()
{
Latitude = Marker.ConvertGeoTagPosition( latitude ),
Longitude = Marker.ConvertGeoTagPosition( longitude )
};
}
return marker;
}
/// <summary>
/// EXIF から得られた GPS 情報を表す { 度, 分, 秒 } の配列を Geo タグ用の位置情報へ変換します。
/// </summary>
/// <param name="values">GPS 情報の配列。</param>
/// <returns>変換された値。</returns>
private static double ConvertGeoTagPosition( ulong[] values )
{
double degrees = Marker.NormalizeGpsValue( values[ 0 ] );
double minutes = Marker.NormalizeGpsValue( values[ 1 ] );
double seconds = Marker.NormalizeGpsValue( values[ 2 ] );
// Geo タグ用の位置へ変換する
return degrees + ( minutes / 60.0 ) + ( seconds / 3600 );
}
/// <summary>
/// GPS 情報の一つのデータ ( GPSLatitude または GPSLongitude ) を正規化します。
/// EXIF のタグ情報については、以下のページを参照して下さい。
/// <span>
/// http://homepage1.nifty.com/gigo/DC/GPS/gpsifd.html
/// </span>
/// </summary>
/// <param name="value">GPS 情報。</param>
/// <returns>正規化されたデータ。</returns>
private static double NormalizeGpsValue( ulong value )
{
byte[] bytes = BitConverter.GetBytes( value );
int upper = BitConverter.ToInt32( bytes, 0 );
int lower = BitConverter.ToInt32( bytes, 4 );
return ( ( double )upper / ( double )lower );
}
/// <summary>
/// 緯度を取得または設定します。
/// </summary>
public double Latitude { get; set; }
/// <summary>
/// 経度を取得または設定します。
/// </summary>
public double Longitude { get; set; }
}
とりあえず今回はここまで。