アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

WPF で GeoTag 読み込み

October 26, 2009開発.NET, BitmapMetadata, GoeTag, WPF

WPF の BitmapMetadata クラスによる GeoTag 読み込みを試してみた。

BitmapMetadata は画像のメタデータを編集するためのクラス。EXIF、IPTC、XMP に対応している。GeoTag は EXIF に属しているので、このクラスから編集可能。ただし MSDN の BitmapMetadata 解説を読んでも使いかたが分かりにくい。読み込み処理には BitmapMetadata.GetQuery メソッドを使用するのだけど、引数の書式指定などは謎。

色々とググってみたら、このクラスを利用した GPS 情報を読み込みサンプルと、EXIF における GPS 情報の格納方法について解説したページを見つけたので、とりあえずはこれらを頼りに実装してみる。以下、参考資料。

今回の実験は単一クラスで十分なため、サンプル プロジェクトは作成しなかった。

実装

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; }
}

とりあえず今回はここまで。