Introduction
This tutorial will guide you through creating your own application to display geo-tagged Flickr images on a Bing Maps backdrop in Silverlight 4.
The components
Flickr Web Services
Flickr exposes a variety of services to access their database of public images, in REST (see this article), SOAP (read here) and RPC (here) formats. For more details, go to their API documentation. A detailed explanation of accessing RESTful services is outside the scope of this article, but, in essence is involves generating a url, downloading the result as a string (the Flickr REST service returns an XML file), and then parsing it to get the info we want.
Bing Maps
Bing released their Silverlight control SDK in November. We’ll be using version 1.0.1, which can be downloaded here. Download and install it.
Preparing
Both Flickr’s web services and Bing Maps web services (which the Bing Maps Control accesses for maps) require you to register an API key to identify who is accessing their services.
To apply for a Flickr API key, go here. For Bing Maps, go here.
Starting out
To start out, we’ll first setup a Silverlight page to display a map.
Create a new project in Blend4/VS2010. I’ll call mine BingFlickrSample. In the Silverlight project, add a reference to the Bing Maps DLL’s, located where you installed the Bing Maps SDK (default location is \Program Files\Bing Maps Silverlight Control).
To add a map to your page, add a namespace in your XAML file :
1: xmlns:map="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"
Add a map to your page, and set the API key using the CredentialsProvider property :
1: <maps:Map CredentialsProvider="your key here" />
This should display a map of the world on your page :
The Bing Maps control provides a lot of functionality out of the box. You can zoom in/out using the left hand slider, pan using the top left pan controller, and switch between Road and Aerial views. To collapse the controls, click the << button.
Accessing the Flickr web services
We’ll be using two of the Flickr API’s methods :
- photos.search (searches through the database with the arguments we specify), and
- photos.geo.getlocation (gets the location of the specified photo)
Our application will allow the user to search for photos with a search string, and only return the results in the current map view. The current map views extents are exposed via the map’s BoundingRectangle property. This is what our url looks like :
1: http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=YourAPIKeyHere&text=SearchStringHere&
2: bbox=MinLatitude,MinLongitude,MaxLatitude,MaxLongitude&per_page=10
An example of the returned XML can be found here.
In the url I’ve also included a per_page property, to limit the amount of results returned (10). In the sample application, the arguments are filled using the string.Format method. The MinLatitude, MinLongitude, MaxLatitude and MaxLongitude values are set to the map’s BoundingRectangle.West, BoundingRectangle.South, BoundingRectangle.East and BoundingRectangle.North properties respectively (refer to Wikipedia for a refresher on Latitudes and Longitudes).
To call the web service, we pass the url to a WebClient, set a handler for its DownloadStringCompleted event and call DownloadStringAsync :
1: WebClient flickRService = new WebClient();
2: flickRService.DownloadStringCompleted +=
3: new DownloadStringCompletedEventHandler(flickRService_DownloadStringCompleted);
4: flickRService.DownloadStringAsync(new Uri(url));
In the DownloadStringCompleted, we need to parse the returned XML. By using the System.Xml.LINQ namespace, we can use LINQ to parse it into a packaged object, which I’ll call FlickrPhoto (originally from Brad Abrams) :
1: public class FlickRPhoto
2: {
3: public string Id { get; set; }
4: public string Owner { get; set; }
5: public string Secret { get; set; }
6: public string Server { get; set; }
7: public string Farm { get; set; }
8: public string Title { get; set; }
9: public string ImageUrl
10: {
11: get
12: {
13: return string.Format("http://farm{0}.static.flickr.com/{1}/{2}_{3}.jpg",
14: Farm, Server, Id, Secret);
15: }
16: }
17:
18: public GeoLocation Location { get; set; }
19: }
The GeoLocation property is a simple container for two double properties :
1: public class GeoLocation
2: {
3: public double Latitude { get; set; }
4: public double Longitude { get; set; }
5:
6: /// <summary>
7: /// Initializes a new instance of the GeoLocation class.
8: /// </summary>
9: public GeoLocation(double latitude, double longitude)
10: {
11: Latitude = latitude;
12: Longitude = longitude;
13: }
14:
15: public GeoLocation()
16: {
17:
18: }
19: }
The XML parsing is done with this code :
1: IEnumerable<FlickRPhoto> Photos;
2:
3: Photos = from photo in xmlPhotos.Element("rsp").Element("photos").Descendants().ToList()
4: select new FlickRPhoto
5: {
6: Id = (string)photo.Attribute("id"),
7: Owner = (string)photo.Attribute("owner"),
8: Secret = (string)photo.Attribute("secret"),
9: Server = (string)photo.Attribute("server"),
10: Farm = (string)photo.Attribute("farm"),
11: Title = (string)photo.Attribute("title"),
12: };
Photos from Flickr are not returned with their geo-location by default, therefore we have to enumerate over all the returned photos, and request the location by calling photos.geo.getLocation on the web service :
1: http://api.flickr.com/services/rest/?method=flickr.photos.geo.getLocation&api_key=YourAPIKeyHere&
2: photo_id=PhotoIDHere
An example of the returned XML can be found here.
Handling the parsing of the returned XML is a bit different than the previous example. This call only returns one photo (if it is found), with its Location information. To parse it, we use :
1: string photoID = (string)(from x in xmlGeoLoc.Element("rsp").Descendants().ToList() select x).
2: First().Attribute("id");
to get the Photo ID (the photo’s identifier on Flickr), and
1: double Lat = Convert.ToDouble(xmlGeoLoc.Element("rsp").Descendants().
2: FirstOrDefault().Descendants().FirstOrDefault().Attribute("latitude").Value);
3: double Lon = Convert.ToDouble(xmlGeoLoc.Element("rsp").Descendants().
4: FirstOrDefault().Descendants().FirstOrDefault().Attribute("longitude").Value);
to get the Latitude and Longitude values, and then add them as GeoLocation properties to the specific photo.
Showing it on the map
Now we have a list of photos, with their locations, and we can display them on the map. Add a Pushpin (a native class included in Microsoft.Maps.MapControl) to the map to visually indicate where the photo was taken :
1: Pushpin pin = new Pushpin();
2: pin.Tag = currentPhoto.Id;
3: pin.Location = new Location(currentPhoto.Location.Latitude, currentPhoto.Location.Longitude);
4: pin.MouseLeftButtonDown += new MouseButtonEventHandler(pin_MouseLeftButtonDown);
5: mapMain.Children.Add(pin);
I add the photo’s ID as a Tag to the Pushpin, to have quick access to it in the Pushpin’s MouseLeftButtonDown event.
When over South Africa, and searching for the term “Fifa”, this is the result :
(Remember, only 10 results are being returned. Also, the zoom level may cause some Pushpins that are close to each other, look like only one)
I’ve added the MouseLeftButtonDown event handler on the Pushpin, to be able to view the actual photo at that location. In the event handler, I just get the photo’s ID from the Pushpin’s Taq property, pass the FlickrPhoto to a Silverlight Child Window (source code is attached), that displays the photo with an Image control :
1: <Image x:Name="imgPhoto" />
This sets the Image control’s Source :
1: imgPhoto.Source = new BitmapImage(new Uri(photo.ImageUrl));
The ImageUrl property is a calculated property on the FlickrPhoto class that combines arguments (farm, server, ID and secret) and builds a url to the photo.
And this, the result when click on one of the Pushpins :
Conclusion
This application demonstrates how to display geo-coded images on a map. In the next article, I will modify the interface to also allow you to upload photos to Flickr, and geo-tag them.
Ciao!
Marcel du Preez
marcel@inivit.com