您的位置:首页 > 移动开发 > Objective-C

Windows Phone 7 – Browsing your Photos via Bing Maps

2012-06-22 08:56 393 查看
A Technical Blog articleView original blog here.[^]

The Windows Phone 7 camera gives you the option to record the location where a picture was taken (under Settings => applications => pictures+camera). With this feature turned on, each application has their latitude, longitude and altitude
stored as part of the standard EXIF data. I thought it would be fun to combine the previous blog post I wrote on pushpin
clustering with the photos on my camera, to allow me to explore them via a Bing Maps control. With not much more than 100 lines of code I came up with an application which I think is a lot of fun to use.


Here are all the photos on my phone, note the way the pushpins are clustered.
Here are a few pictures I took in New York, of the One World Trade Centre and the Stock
Exchange.


Here are some pictures around Europe, including one ofGergely Orosz waiting for his turn in the Edinburgh Marathon Relay.
And finally, some pictures I took whilst running aroundKielder Water during Kielder marathon.

Accessing the EXIF data

You can access the photos on a WP7 device via the XNA MediaLibrary class. The interface
that this class provides gives you access to Picture instances which have properties that allow you to access the width
/ height and a few other basic attributes. They also have methods that return streams which can be used to read the thumbnail and image data, however, they do not expose the picture location. This is ‘hidden’ within the EXIF data.
Fortunately there is a C# implementation of an EXIF decoder available on codeproject, which, with
a few tweaks by Tim Heuer works just fine within Silverlight for Windows Phone 7.
With this library, accessing the EXIF data is a one-liner:


 Collapse | Copy
Code
JpegInfo info = ExifReader.ReadJpeg(picture.GetImage(), picture.Name);


The JpegInfo class exposes the raw EXIF geolocation data, which is detailed in the EXIF specification as being expressed as separate components
of degrees, minutes and seconds together with a reference direction (North / South, East / West). We can convert from the sexagesimal numeric system used in EXIF,
to the decimal system as follows:


 Collapse | Copy
Code
private static double DecodeLatitude(JpegInfo info)
{
double degrees = ToDegrees(info.GpsLatitude);
return info.GpsLatitudeRef == ExifGpsLatitudeRef.North ? degrees : -degrees;
}

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


Analysing the images

When the application starts a 
BackgroundWorker
 is used to read the EXIF data for all of the pictures in the phone’s media library,
with those that have geolocation data available being stored in a separate list:


 Collapse | Copy
Code
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;

// analyse the pictures that reside in the Media Library in a background thread
bw.DoWork += (s, e) =>
{
var ml = new MediaLibrary();

using (var pics = ml.Pictures)
{
int total = pics.Count;
int index = 0;
foreach (Picture picture in pics)
{
// read the EXIF data for this image
JpegInfo info = ExifReader.ReadJpeg(picture.GetImage(), picture.Name);

// check if we have co-ordinates
if (info.GpsLatitude.First() != 0.0)
{
_images.Add(new LocatedImage()
{
Picture = picture,
Lat = DecodeLatitude(info),
Long = DecodeLongitude(info)
});
}

// report progress back to the UI thread
string progress = string.Format("{0} / {1}", index, total);
bw.ReportProgress((index * 100 / total), progress);

index++;
}
}
};

// update progress on the UI thread
bw.ProgressChanged += (s, e) =>
{
string title = (string)e.UserState;
ApplicationTitle.Text = title;
};

bw.RunWorkerAsync();

// when analysis is complete, add the pushpins
bw.RunWorkerCompleted += (s, e) =>
{
ApplicationTitle.Text = "";
AddPushpins();
};


When the pictures have all been analysed, a pushpin is created for each image which is then added to the clusterer
described in my previous blog post.


 Collapse | Copy
Code
private void AddPushpins()
{
List<Pushpin> pushPins = new List<Pushpin>();

// create a pushpin for each picture
foreach (var image in _images)
{
Location location = new Location()
{
Latitude = image.Lat,
Longitude = image.Long
};

Pushpin myPushpin = new Pushpin()
{
Location = location,
DataContext = image,
Content = image,
ContentTemplate = this.Resources["MarkerTemplate"] as DataTemplate
};

pushPins.Add(myPushpin);
}

// add them to the map via a clusterer
var clusterer = new PushpinClusterer(map, pushPins, this.Resources["ClusterTemplate"] as DataTemplate);
}


The template used for the pushpins simply renders the image thumbnail:


 Collapse | Copy
Code
<DataTemplate x:Key="MarkerTemplate">
<Border BorderBrush="White" BorderThickness="1">
<Image Source="{Binding Picture, Converter={StaticResource PictureThumbnailConverter}}"
Width="80" Height="80"/>
</Border>
</DataTemplate>


This makes use of a simple value converter which takes a 
Picture
 instance and converts it into a 
BitmapImage
which
is used as the 
Source 
for the image:


 Collapse | Copy
Code
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;
}
}


The puhspin clusterer allows you to specify a separate template for clustered pushpins. The 
DataContext
 for this template is
a list of the 
DataContexts
 of the clustered pins that it represents. For this application I created a template which renders what looks like a ‘stack’
of images. The number of pictures in the cluster is rendered as a
TextBlock
 and the last image in the cluster rendered.


 Collapse | Copy
Code
<DataTemplate x:Key="ClusterTemplate">
<Grid Width="75" Height="75">
<Canvas>
<Border Style="{StaticResource FakePhoto}"
Canvas.Left="0" Canvas.Top="0"/>
<Border Style="{StaticResource FakePhoto}"
Canvas.Left="5" Canvas.Top="5"/>
<Border BorderBrush="White" BorderThickness="1"
Canvas.Left="10" Canvas.Top="10"
DataContext="{Binding Path=., Converter={StaticResource LastConverter}}">
<Image Source="{Binding Picture, Converter={StaticResource PictureThumbnailConverter}}"
Width="60" Height="60"/>
</Border>
<TextBlock Text="{Binding Count}"
Opacity="0.5"
Canvas.Left="25" Canvas.Top="15"
FontSize="35"/>
</Canvas>
</Grid>
</DataTemplate>

<Style TargetType="Border" x:Key="FakePhoto">
<Setter Property="Width" Value="60"/>
<Setter Property="Height" Value="60"/>
<Setter Property="BorderBrush" Value="White"/>
<Setter Property="Background" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
</Style>


The code that renders the last image is a bit cunning, it uses a value converter that performs a Linq style ‘last’ operations, extracting the last items from a collection of objects:


 Collapse | Copy
Code
public class LastConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
IList enumerable = value as IList;
return enumerable.Cast<object>().Last();
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}


This feels quite neat to me 


The clustered pins look like the following, which is a cluster of 5 images around Paris, with the stunning La Grande Arche de la Défense as
the image at the top of the cluster:



Despite its simplicity, I have had a lot of fun playing with this application. It has certainly encouraged me to take as many photos as possible whenever I go travelling.
You can download the full sourcecode here: PhotoBrowser.zip
Regards, Colin E.


License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐