Windows Phone - 取得圖像中的GeoLocation資訊

Windows Phone - 取得圖像中的GeoLocation資訊

之前在參加受訓課程的時有一個作業,主要是用於取得圖像中的描述資訊(metadata),如下圖:

未命名

上圖中這些資訊是什麼呢?即是所謂的EXIF資料,根據<Exchangeable image file format>的說明,我擷錄一些部分來加以說明:

 

Exchangeable image file format (EXIF)

    用於描述Image、Sound與ancillary tags的一種標準。通常被用於像智慧手機、數位相機、掃瞄器所建立產出物時增加的描述。

常見的檔案:JPEG、TTF、RIFF WAV、PNG、GIF…等均可見面這些描述的資料。上述提及的Geolocation資料在其中,更多詳

細的內容可以參考<Geotagging>上的說明,如果主要識別Geolocation的話,可以參考下圖:

   image

可得知儲存在圖像中的資料,有一段放置的就是Geolocation,它主要分成五大部分:Version、LatitudeRef/Latitude、

LongitudeRef/Longitude,這些均可透過byte[]位移轉換成16進字,再進一步換成ASCII的內容。

 

上述說明的轉換方式,其實在.NET Framework 4.5開始也有支持對應的<PropertyItem 類別>類別來取得。

非常可惜的在Windows Phone的SDK裡目前是沒有System.Drawing.Imaging的,所以也就不支持<PropertyItem 類別>。

 

那麼在WP裡如果要取得圖像的GeoLocation該怎麼辦呢?

根據參考<Windows Phone 7 - Browsing your Photos via Bing Maps>與<Understanding and Reading Exif Data>的內容,

加上對於EXIF Tag後,我透過下方說明來協助大家了解如何去取得EXIT Tag中Geolocation資訊。

 

〉利用<Understanding and Reading Exif Data>取得JPEG中的EXIF Tag

    擷錄一段「

Note: Unfortunately for Silverlight developers, the sandboxed .

Net framework available to them does not expose the GDI+

wrappers implemented in the System.Drawing.Imaging namespace - at least not at the time of writing this article.

These wrappers include all the necessary tools to retrieve various informations from image metadata,

so if they're available don't waste your time re-inventing the wheel.

」可以了解為何Windows Phone沒有支持Imaing.PropertyItem。

 

 

    為了取得EXIF Tag中的資料,先來簡單了解一下JPEG File Format。

a) JPEG File Format

    一張JPEG圖像是由二個Bytes為分隔所組合起來的。

    ‧第一個標記byte:永遠是0xFF;

    ‧第二個標記byte:跟在第一個標記byte之後;舉例來說,常見的就是 0xFF 0xD8;

    ‧那Exif放置的標記byte:為 0xE1;

 

b) Exif Format

     Exif資料被儲存於一個目錄結構或稱IFD結構,前4個bytes儲存目錄的長度;接著2個bytes做為已在目錄中找到的entries

的數量或tags數;每一個Exif entry被定義於前2個bytes做為tag的識別。它的格式(2 bytes)和最後的元件數量(4 bytes.)。

Exif圖像格式定義如下:

內容 說明
Unsigned Byte (1 byte per component)
ASCII String (1 bpc)
Unsigned Short (2 bpc)
Unsigned Long (4 bpc)
Unsigned Rational (8 bpc, 4 for the numerator and 4 for the denominator)
Signed Byte  
Undefined (1 bpc)
Signed Short  
Signed Long  
Signed Rational  
Single (4 bpc)
Double (8 bpc)

 

上述說明的內容,如果不是非常清楚也沒有關係,因為透過該SDK已幫我們處理好這些位元問題,

透過下圖的結構圖,可看出上述介紹的Format已被包裝於類別之中,接下來就看我們怎麼使用它了。

image

 

//"Understanding and Reading Exif Data"的source加入專案參考
//直接利用ExifReader取得JPEG中的Exif資料
JpegInfo info = ExifReader.ReadJpeg(picture.GetImage(), picture.Name);

該JpegInfo類別裡包裝了所有Exif的資料,即可以做用於應用程式之中。

 

大致上了解了使用的SDK之後,下方藉由二個範例來介紹如何使用該SDK。

 

[範例說明]

範例分成二個部分來說明:(1) 讀取手機相本的圖像,並且取得它們的座標;(2) 將這些座標標示於地圖中;

開發前提示:記得先確認打勾開啟定位資料,"設定->應用程式->照片+相機->在我拍攝的照片中包含位置資訊"。

 

範例1:讀取手機相本的圖像,並且取得它們的座標。

(a) 先至<Understanding and Reading Exif Data>下載必要的SDK

     由於它具有Silverlight的版本,可以透過VS2012將它開啟後,重建出ExifLib.dll

     image

     =>如果您不是CodeProject可以申請一下,因為這裡有很多值得收藏或參考的資料,可以看到其他人撰寫程式的風格與新寫法。

 

(b) 將ExifLib.dll加入至自行建立的專案裡

      image

 

(c) 建立一個新類別:PictureItem.cs,儲存圖像物件與相關資訊

public class PictureItem
{
    public string FileName { get; set; }
    public string Location
    {
        get { return string.Format("{0},{1}", Latitude, Longitude); }
    }
    public double Latitude { get; set; }
    public double Longitude { get; set; }
    public Picture Picture { get; set; }
}

 

(d) 定義一個Picture的Binding Convert,搭配XAML使用

public class PictureThumbnailConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
                          System.Globalization.CultureInfo culture)
    {
        Picture picture = value as Picture;
        BitmapImage src = new BitmapImage();
        src.SetSource(picture.GetThumbnail());
        return src;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, 
                              System.Globalization.CultureInfo culture)
    {
        return null;
    }
}

     記得在要使用的Page裡,先宣告其xmlns與<phone:PhoneApplicationPage.Resources />如下:

xmlns:myapp="clr-namespace:ImageGeoLocation"
<phone:PhoneApplicationPage.Resources>
    <myapp:PictureThumbnailConverter x:Key="PictureThumbnailConverter" />
</phone:PhoneApplicationPage.Resources>

 

(e) 定義函式以轉換Latitude / Longitude的值為Double

#region 轉換座標值為Dobule函式
/// <summary>
/// 解譯Latitude為Double值。
/// </summary>
private static double DecodeLatitude(JpegInfo info)
{
    double degrees = ToDegrees(info.GpsLatitude);
    return info.GpsLatitudeRef == ExifGpsLatitudeRef.North ? degrees : -degrees;
}
 
/// <summary>
/// 解譯Longitude為Double值。
/// </summary>
private static double DecodeLongitude(JpegInfo info)
{
    double degrees = ToDegrees(info.GpsLongitude);
    return info.GpsLongitudeRef == ExifGpsLongitudeRef.East ? degrees : -degrees;
}
 
public static double ToDegrees(double[] data)
{
    return data[0] + data[1] / 60.0 + data[2] / (60.0 * 60.0);
}
#endregion

 

(f) 撰寫取得MediaLibrary中的「Camera Roll」中的圖示,並且加它們顯示於畫面

private void LoadImages()
{
    MediaLibrary tLibrary = new MediaLibrary();
    //取得所有的Pictures分類目錄
    var tCollection = (from Collection in tLibrary.Pictures
                       select Collection.Album.Name).Distinct();
    //取得Camera Roll中的圖示
    string tFolder = tCollection.ElementAt(2);
    
    var tPictures = from Pictures in tLibrary.Pictures
                    where Pictures.Album.Name.Equals(tFolder )
                    select Pictures;
    //轉換成新的類別
    List<PictureItem> tDataSource = new List<PictureItem>();
    foreach (Picture tPItem in tPictures)
    {
        JpegInfo info = ExifReader.ReadJpeg(tPItem.GetImage(), tPItem.Name);
        tDataSource.Add(new PictureItem
        {
            FileName = tPItem.Name,
            Latitude = DecodeLatitude(info),
            Longitude = DecodeLongitude(info),
            Picture = tPItem
        });
    }
    //加入至畫面的ListBox
    lstImages.ItemsSource = tDataSource;
}
 
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    //載入圖像
    LoadImages();
}

 

(g) 畫面顯示的XAML

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <ListBox x:Name="lstImages" Width="450">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid Margin="5,0,5,10" 
                      Width="450"
                      Background="{StaticResource PhoneChromeBrush}">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="30*" />
                        <ColumnDefinition Width="70*" />
                    </Grid.ColumnDefinitions>
                    <StackPanel Grid.Column="0">
                        <Image Source="{Binding Picture, Converter={StaticResource PictureThumbnailConverter}}" 
                              
                               Stretch="Fill" />
                    </StackPanel>
                    <StackPanel Grid.Column="1">
                        <TextBlock Text="{Binding FileName}" 
                                   TextWrapping="Wrap"
                                   Style="{StaticResource PhoneTextTitle2Style}" />
                        <TextBlock Text="{Binding Location}" 
                                   TextWrapping="Wrap"
                                   Style="{StaticResource PhoneTextTitle3Style}" />
                    </StackPanel>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>                
    </ListBox>
</Grid>

 

(h) 執行結果

       wp_ss_20131015_0001

 

 

 

範例二:將圖示加入至地圖;

(a) 獨立一個新的Page,並且加入Map Control,記得開啟對應的Capabilities:ID_CAP_MAP與ID_CAP_LOCATION

<Grid x:Name="LayoutRoot" Background="Transparent">
    <maps:Map x:Name="mapControl" />
</Grid>

 

 

(b) 建立一個GeoCoordinateWatcher來找到自己目前所在的位置

#region 控制目前所在位置的邏輯
 
private void FocusMe()
{
    GeoCoordinateWatcher tWatcher = new GeoCoordinateWatcher(GeoPositionAccuracy.Default);
    tWatcher.PositionChanged += tWatcher_PositionChanged;
    tWatcher.StatusChanged += tWatcher_StatusChanged;
    tWatcher.Start();
}
 
void tWatcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
    if (e.Position != null)
    {
        //指定地圖顯示目前所在位置
        mapControl.Center = e.Position.Location;
        mapControl.ZoomLevel = 10;
        
        //加入ToolKit中的Pushpin
        Pushpin tPush = new Pushpin();
        tPush.GeoCoordinate = mapControl.Center;
        tPush.Content = "Me";
        
        //將Pushpin加入圖層中
        MapOverlay tOverLay = new MapOverlay
        {
            GeoCoordinate = mapControl.Center,
            Content = tPush
        };
        MapLayer tLayer = new MapLayer();
        tLayer.Add(tOverLay);
        mapControl.Layers.Add(tLayer);
        
        //關閉Watcher避免太花費電力
        ((GeoCoordinateWatcher)sender).Stop();
    }
}
 
void tWatcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
{
 
}
#endregion
 
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
 
    FocusMe();
 
    FeedPictureToPushPin();
}

 

 

(c) 將範例1所捉出的圖示,加入至地圖中

private void FeedPictureToPushPin()
{
    MapLayer tLayer = new MapLayer();
    foreach (PictureItem tItem in Utility.gPictureSets)
    {
        //代表沒有資料
        if (tItem.Location == "0,0") continue;
        //取得圖示
        Image tImage = new Image();
        BitmapImage tBitmap = new BitmapImage();
        tBitmap.SetSource(tItem.Picture.GetThumbnail());
        tImage.Source = tBitmap;
        //加入Pushpin中
        Pushpin tPushPin = new Pushpin
        {
            GeoCoordinate = new GeoCoordinate(tItem.Latitude, tItem.Longitude),
            Content = tImage
        };
        MapOverlay tOverLay = new MapOverlay
        {
            GeoCoordinate= new GeoCoordinate(tItem.Latitude, tItem.Longitude),
            Content = tPushPin
            
        };                
        tLayer.Add(tOverLay);
    }
    mapControl.Layers.Add(tLayer);
}

 

 

(d) 執行結果

     wp_ss_20131015_0002

 

[範例程式]

  

[補充]

1. 手動調整Exif Tag的做法:

   ‧How to edit EXIF data in .NET

   ‧How to Edit Meta Data Inside JPG Files with C# 

 

======

現在透過手機或相機拍照,其實照片裡面夾帶了很多新的資訊,包括:拍攝的設備、鏡頭資訊、GPS資訊…等,

這些資訊也支持很多Idea的實現,常見的例子就同範例二把圖像標示於地圖做為旅行的記憶或是做完山區審查人員

記錄所檢查過的地點等。但其實也需要特別注意,當你上傳的圖像至Social Network時,相對地就把你的座標告知大家,

即個人隱私問題則需要注意了。簡單分享其作法,希望對大家有所幫助,謝謝。

 

References

Windows Phone 7 - Browsing your Photos via Bing Maps (重點)

Understanding and Reading Exif Data (重要)

How to find geo-location from image header in asp.net (.net framework 4.5)

PropertyItem 類別

How to: Read Image Metadata

How to: Load and Display Metafiles

Automatically extracting GPS data from photos and populating Geolocation fields in SharePoint 2013

Exchangeable image file format

How to edit EXIF data in .NET

How to Edit Meta Data Inside JPG Files with C# (重要)

Photo Properties

Changing EXIF Data using C#

ExifLib - A Fast Exif Data Extractor for .NET 2.0+ (重要)

the Windows Phone 7 Bing Map control

Binding.Converter 屬性

http://msdn.microsoft.com/en-us/library/windowsphone/design/hh202896(v=vs.105).aspx

Windows Phone Toolkit

Maps in Windows Phone 8 and Phone toolkit: a winning team – Part 1

 

Dotblogs 的標籤: