Writing a Bing Maps location aware application for Windows Phone 7 Series

imageIf you have been keeping up with the news from Mix2010 and yesterdays’ keynote you will know a number of exciting things have been released for windows phone 7 series:

  • completely free developer tools
  • the windows 7 phone series beta sdk
  • lots of details about using silverlight and xna to develop phone applications

So what does that mean for Bing Map, will there be a new silverlight control for mobile? There may well be an update to better support mobile development but you can start right now using the current Bing Maps silverlight control and it works! To show you how here is a quick tutorial creating your first location aware Bing Maps phone application.

Prerequisites

Getting Started

Start by opening Visual Studio 2010 (or express as offered free in the sdk) and create a new project, selecting the “Windows Phone Application” project template.

image

Now add references to the Bing Maps silverlight control (usually found in C:\Program Files\Bing Maps Silverlight Control\V1\Libraries). You may find you need to also add a reference to System.Windows.Browser to get it to compile correctly (from c:\Program Files\Microsoft Silverlight\4.0.50303.0\).

Finally for the setup, add a reference to System.Device.Location.

Creating a Basic User Interface

Now we have the project created lets create a simple user interface in XAML that has a Bing Map Control, a status bar and a start button. First add the Bing Maps namespace to you MainPage.xaml file:

xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"

Then add the following block of XAML in the grid named “ContentGrid” which should already be in MainPage.xaml:

  1. <m:Map Name="mapMain" NavigationVisibility="Collapsed" Mode="AerialWithLabels" CredentialsProvider="ADD_YOUR_BINGMAPS_KEY_HERE">
  2. </m:Map>
  3. <Border Background="Black" Height="40" Opacity="0.7" VerticalAlignment="Top">
  4.     <TextBlock Name="tbStatus" Text="Click below to start" Margin="5 5 0 0" />
  5.    </Border>

Now change the name of the application and the title to be whatever you want. Hit F5 and you should get the emulator load up with you application showing a Bing Map!"

Making it actually do something

For the next part of the tutorial we are going to make the application actually do something with the map by making it location aware, that is to say use the devices location to change the map in some way.

First add the following to your XAML just under the map control, to add a button:

  1. <Button Name="btnStart" Click="btnStart_Click" Width="260" Height="40">
  2.     <Button.Content>
  3.         <StackPanel Orientation="Horizontal">
  4.             <Path Fill="White" Data="F1 M 2.339,6.489 C 1.193,5.343 1.193,3.485 2.339,2.339 C 3.485,1.192 5.344,1.193 6.489,2.339 C 7.635,3.485 7.635,5.343 6.489,6.489 C 5.344,7.636 3.485,7.636 2.339,6.489 Z M 11.711,10.209 L 8.185,6.684 C 9.207,4.986 9.000,2.757 7.535,1.293 C 5.812,-0.431 3.017,-0.431 1.293,1.293 C -0.431,3.017 -0.431,5.812 1.293,7.536 C 2.757,8.999 4.988,9.206 6.685,8.185 L 10.209,11.710 L 11.711,10.209 Z" Margin="0,0,5,0">
  5.                 <Path.RenderTransform>
  6.                     <ScaleTransform ScaleX="2.5" ScaleY="2.5" />
  7.                 </Path.RenderTransform>
  8.             </Path>
  9.             <TextBlock Foreground="White" Text="Find Me Now" Margin="20 -5 0 0" />
  10.         </StackPanel>
  11.     </Button.Content>
  12. </Button>

 

Then and add a map layer with a simple circle pushpin to the map control:

  1.           <m:Map Name="mapMain" NavigationVisibility="Collapsed" Mode="AerialWithLabels" CredentialsProvider="ApXw1_p4abRAyITXFZGy6IvPyUN05hwF08hfkkNMUfExuujgB-XpmebygnDRH1RA">
  2.               <m:MapLayer Name="lMain">
  3.                   <Ellipse Fill="Red" Width="20" Height="20" m:MapLayer.Position="0,0" Name="ppLocation" Visibility="Collapsed" />
  4.                           </m:MapLayer>
  5.             </m:Map>

Now we are going to wire up the click event of the button to get the users location and update the map. We will do this in a number of stages.

Firstly, add these using statements to the top of your code:

  1. using System.Device.Location;
  2. using Microsoft.Maps.MapControl;

 

Now add a private member of type GeoCoordinateWatcher above the MainPage constructor:

  1. GeoCoordinateWatcher watcher;
  2. public MainPage()
  3. {
  4.     InitializeComponent();
  5.  
  6.     SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;
  7. }

And add the following event handler for the button click:

  1. private void btnStart_Click(object sender, RoutedEventArgs e)
  2. {
  3.     // Reinitialize the GeoCoordinateWatcher
  4.     watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
  5.     watcher.MovementThreshold = 100;//distance in metres
  6.  
  7.     // Add event handlers for StatusChanged and PositionChanged events
  8.     watcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(watcher_StatusChanged);
  9.     watcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(watcher_PositionChanged);
  10.  
  11.     // Start data acquisition
  12.     watcher.Start();
  13.  
  14.     //hide button
  15.     btnStart.Visibility = Visibility.Collapsed;
  16. }

This creates a new GeoCoordinateWatcher when the button is clicked with the accuracy set to high, sets a movement threshold allowing movement by 100 metres before the application is alerted and attaches two events. The first event “StatusChanged” will fire when the status of the location data changes (like during initialisation and once we have a location) and the second event handles changes to the current location. We will add the handlers for both in a minute. Lastly we start the watcher and hide the button then wire it up in the XAML to our recently added button’s click event:

  1. <Button Name="btnStart" Click="btnStart_Click" Width="260" Height="40">
  2.     <Button.Content>
  3.         <StackPanel Orientation="Horizontal">
  4.             <Path Fill="White" Data="F1 M 2.339,6.489 C 1.193,5.343 1.193,3.485 2.339,2.339 C 3.485,1.192 5.344,1.193 6.489,2.339 C 7.635,3.485 7.635,5.343 6.489,6.489 C 5.344,7.636 3.485,7.636 2.339,6.489 Z M 11.711,10.209 L 8.185,6.684 C 9.207,4.986 9.000,2.757 7.535,1.293 C 5.812,-0.431 3.017,-0.431 1.293,1.293 C -0.431,3.017 -0.431,5.812 1.293,7.536 C 2.757,8.999 4.988,9.206 6.685,8.185 L 10.209,11.710 L 11.711,10.209 Z" Margin="0,0,5,0">
  5.                 <Path.RenderTransform>
  6.                     <ScaleTransform ScaleX="2.5" ScaleY="2.5" />
  7.                 </Path.RenderTransform>
  8.             </Path>
  9.             <TextBlock Foreground="White" Text="Find Me Now" Margin="20 -5 0 0" />
  10.         </StackPanel>
  11.     </Button.Content>
  12. </Button>

 

Ok, the next piece of the puzzle is to add the event handlers we attached in the button click handler above, watcher_StatusChanged and watcher_PositionChanged:

  1. #region Event Handlers
  2.  
  3. /// <summary>
  4. /// Handler for the StatusChanged event. This invokes MyStatusChanged on the UI thread and
  5. /// passes the GeoPositionStatusChangedEventArgs
  6. /// </summary>
  7. /// <param name="sender"></param>
  8. /// <param name="e"></param>
  9. void watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
  10. {
  11.     Deployment.Current.Dispatcher.BeginInvoke(() => MyStatusChanged(e));
  12.  
  13. }
  14.  
  15. /// <summary>
  16. /// Handler for the PositionChanged event. This invokes MyStatusChanged on the UI thread and
  17. /// passes the GeoPositionStatusChangedEventArgs
  18. /// </summary>
  19. /// <param name="sender"></param>
  20. /// <param name="e"></param>
  21. void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
  22. {
  23.     Deployment.Current.Dispatcher.BeginInvoke(() => MyPositionChanged(e));
  24. }
  25.  
  26. #endregion

You will notice these call two methods when they are triggered, but they ensure that these methods are called on the UI thread otherwise we will get some nasty cross thread errors.

Finally add the two UI thread methods for doing something when the events fire, and a simple ResetMap method that's called when the user initially clicks the button:

  1. /// <summary>
  2. /// Custom method called from the PositionChanged event handler
  3. /// </summary>
  4. /// <param name="e"></param>
  5. void MyPositionChanged(GeoPositionChangedEventArgs<GeoCoordinate> e)
  6. {
  7.     // Update the map to show the current location
  8.     Location ppLoc = new Location(e.Position.Location.Latitude, e.Position.Location.Longitude);
  9.     mapMain.SetView(ppLoc, 10);
  10.  
  11.     //update pushpin location and show
  12.     MapLayer.SetPosition(ppLocation, ppLoc);
  13.     ppLocation.Visibility = System.Windows.Visibility.Visible;
  14.  
  15.     
  16. }
  17.  
  18. /// <summary>
  19. /// Custom method called from the StatusChanged event handler
  20. /// </summary>
  21. /// <param name="e"></param>
  22. void MyStatusChanged(GeoPositionStatusChangedEventArgs e)
  23. {
  24.     switch (e.Status)
  25.     {
  26.         case GeoPositionStatus.Disabled:
  27.             // The location service is disabled or unsupported.
  28.             // Alert the user
  29.             tbStatus.Text = "sorry we can't find you on this device";
  30.             break;
  31.         case GeoPositionStatus.Initializing:
  32.             // The location service is initializing.
  33.             // Disable the Start Location button
  34.             tbStatus.Text = "looking for you...";
  35.             break;
  36.         case GeoPositionStatus.NoData:
  37.             // The location service is working, but it cannot get location data
  38.             // Alert the user and enable the Stop Location button
  39.             tbStatus.Text = "can't find you yet...";
  40.             ResetMap();
  41.  
  42.             break;
  43.         case GeoPositionStatus.Ready:
  44.             // The location service is working and is receiving location data
  45.             // Show the current position and enable the Stop Location button
  46.             tbStatus.Text = "found you!";
  47.             break;
  48.  
  49.     }
  50. }
  51.  
  52. void ResetMap()
  53. {
  54.     Location ppLoc = new Location(0, 0);
  55.     mapMain.SetView(ppLoc, 1);
  56.  
  57.     //update pushpin location and show
  58.     MapLayer.SetPosition(ppLocation, ppLoc);
  59.     ppLocation.Visibility = System.Windows.Visibility.Collapsed;
  60. }

 

These first method MyPositionChanged updates the map by setting the current map view to the new location and zooming in. It also makes the pushpin icon visible. The second method MyStatusChanged simply updates the status text in the UI with a message telling the user the current state of the location services.

Houston we have a problem

By now you should have a finished application the compiles, if not you can download the source code below to see the full code:

butdownloadcode[1]

You can run the application in the emulator by clicking F5 and the map and UI should appear, BUT here we find a problem.

The emulator currently does not support the location service, so all we ever get back is a status change of NoData. There is currently not even any way to add fake data to the emulator so we will have to wait for an update to allow us to actually get location aware applications working in the emulator :-(

So until we can get our hands on some real hardware “Nudge, Nudge”  there is no real way to test this application.