January 2015

Volume 30 Number 1


Windows Phone 8.1 - Adding Application Data to the Windows Phone 8.1 Map Control

By Keith Pijanowski

In my November article on the mapping capabilities of Windows Phone 8.1, I introduced the Windows Phone 8.1 Map control and the Mapping Services API. I showed how to use the Map control to add mapping functionality to a phone application, for example, adding images to mark a specified location, calculating the geo-coordinates of a specified address (geocoding), determining the address of a specified geo-coordinate (reverse geocoding), and providing driving and walking directions.

This article will show how to get application data into the Map control by adding controls to the Map control using XAML, and binding application data to those controls. If your application will be used offline, as well, the underlying data that’s used by the Map control can also be downloaded for offline use. This allows the map control to fit in with the offline functionality of your app. I’ll also show how to take the Map control offline and keep the offline data up-to-date.

Adding Controls to the Map Control Using XAML

In my previous article, I used code to add an ellipse control to a map in order to circle a specific location on the map. That’s a good approach if you need complete control. However, if you wish to get creative and annotate a map with many different types of controls, using code can be clunky. Adding controls to the map using XAML is more efficient. Also, the XAML approach is much easier when you need to map a collection of locations within a map using data binding.

First I’ll show how to add controls using XAML and how to bind application data to these controls. Then I’ll delve further into data binding and show how to data bind a collection of locations to the Map control.

Adding Basic XAML Controls There are two ways to add controls to the Map control in order to add application data to a map: You can add the controls as children of the Map control or you can use the MapItemsControl to contain the controls.

Figure 1 shows how to add controls as children of the Map control. Because Children is the default content property of the Map control, it doesn’t need to be explicitly specified in the XAML markup. Figure 2 shows an alternate approach to adding controls to the Map control, which makes use of the MapItemControl to contain added controls.

Figure 1 Adding Controls as Children of the Map Control

<Maps:MapControl
  x:Name="myMapControl" Grid.Row="1"
  MapServiceToken="{StaticResource MapServiceTokenString}" >
  <!-- Progress bar which is used while the page is loading. -->
  <ProgressBar Name="pbProgressBar" IsIndeterminate="True" Height="560"
    Width="350" />
  <TextBlock Name="tbMessage" Text="{Binding Message}"
    Maps:MapControl.Location="{Binding Location}"  
    Maps:MapControl.NormalizedAnchorPoint="1.0,1.0"
    FontSize="15" Foreground="Black" FontWeight="SemiBold"
    Padding="4,4,4,4"
    Visibility="Collapsed"
    />
  <Image Name="imgMyLocation" Source="Assets/PinkPushPin.png"
    Maps:MapControl.Location="{Binding Location}"
    Maps:MapControl.NormalizedAnchorPoint="0.25, 0.9"
    Height="30" Width="30"
    Visibility="Collapsed" />
</Maps:MapControl>

Figure 2 Containing Controls Within a MapItemControl

<Maps:MapControl
  x:Name="myMapControl"
  MapServiceToken="{StaticResource MapServiceTokenString}">
  <Maps:MapItemsControl>
    <TextBlock Name="tbAddress" Text="{Binding Message}"
      Maps:MapControl.Location="{Binding Location}"
      Maps:MapControl.NormalizedAnchorPoint="1.0,1.0"
      Visibility="Collapsed" />
    <Image Name="imgMyLocation" Source="Assets/PinkPushPin.png"
      Maps:MapControl.Location="{Binding Location}"
      Maps:MapControl.NormalizedAnchorPoint="0.25, 0.9"
      Height="30" Width="30"
      Visibility="Collapsed"/>
  </Maps:MapItemsControl>
</Maps:MapControl>

If you use the MapItemControl technique shown in Figure 2, be aware you won’t be able to access the controls in your codebehind. IntelliSense will acknowledge your control names as valid variables, but at run time these variables will always be null and if you reference them you’ll get a NullReferenceException. The MapItemControl is typically used to contain data templates that are used for binding a collection of objects to the Map control. (This will be discussed later in this article.)  Therefore, if you’re not binding to a collection of objects, it’s better to add your controls as children of the Map control, as shown in Figure 1.

Data Binding The code in Figure 1 makes use of data binding to set the Location property of both the image control and the text block. The Text property of the TextBlock also utilizes data binding. To quickly review, the Location property is an attached property of type Geopoint. It’s used to specify where on the map the control will be placed. The NormalizedAnchorPoint attached property allows the location of the control to be fine-tuned. For example, you can use the NormalizedAnchorPoint property to center a control over a location, or place a control on the upper left of a location. Both of these properties were discussed in detail in my first article (msdn.microsoft.com/magazine/dn818495).

Data binding allows XAML controls to get their values from underlying objects. An object created from the following class will be used to hold the values needed for the XAML controls shown in Figure 1:

public class LocationEntity
{
  public Geopoint Location { get; set; }
  public string Message { get; set; }
}

Figure 3 shows the complete OnNavigatedTo event for a page containing the controls in Figure 1. (If a page or view within an application requires a lot of setup, consider placing this code in the creation of a view model and running it asynchronously.) This code sets the device’s current location into an instance of the LocationEntity class along with a short message. Notice that the LocationEntity object is set into the DataContext property of the Map control.

Figure 3 The OnNavigatedTo Event Creating Objects Needed for Data Binding

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
  // Call the navigation helper base function.
  this.navigationHelper.OnNavigatedTo(e);
  // Get the user's current location so that it can be
  // used as the center point of the map control.
  Geolocator geolocator = new Geolocator();
  // Set the desired accuracy.
  geolocator.DesiredAccuracyInMeters = 200;
  // The maximum acceptable age of cached location data.
  TimeSpan maxAge = new TimeSpan(0, 2, 0);
  // Timeout used for the call to GetGeopositionAsync.
  TimeSpan timeout = new TimeSpan(0, 0, 5);
  Geoposition geoposition = null;
  try
  {
    geoposition = await geolocator.GetGeopositionAsync(maxAge, timeout);
  }
  catch (Exception)
  {
    // Add exception logic.
  }
  // Set up the LocationEntity object.
  LocationEntity locationEntity = new LocationEntity();
  locationEntity.Location = geoposition.Coordinate.Point;
  locationEntity.Message = "You are here";
  // Specify the location that will be at the center of the map.
  myMapControl.Center = locationEntity.Location;
  // Set the map controls data context so data binding will work.
  myMapControl.DataContext = locationEntity;
  // Zoom level.
  myMapControl.ZoomLevel = 15;
  // The map control has done most of its work. Make the controls visible.
  imgMyLocation.Visibility = Windows.UI.Xaml.Visibility.Visible;
  tbMessage.Visibility = Windows.UI.Xaml.Visibility.Visible;
  // Collapse the progress bar.
  pbProgressBar.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}

A Few Tips

In the code shown in Figure 3, the OnNavigatedTo event performs time-consuming work. Specifically, this event calls the GetGeopositionAsync function of a Geolocator object. This function gets the device’s current location, which is needed before the image control and the TextBlock control can be properly positioned on the map. In situations like this, any controls added to the map as children via XAML are initially displayed in the upper-left corner of the map until they can be positioned on the map. This creates a poor UX. To correct this, simply set up any added controls as Collapsed within your XAML. The added controls can be made visible once the control locations are calculated and the object in which the controls are bound is set up. (Refer to Figure 1 and the last few lines of code in Figure 3.)

Consider using the DesiredAccuracyInMeters property of the Geolocator object. Setting this property to 100 meters or less will get the most accurate data available. If the device has GPS capabilities, the GPS will then be used to determine the device’s current location. If this value is greater than 100 meters, the Geolocator object will optimize for power and use non-GPS data, such as Wi-Fi signals. The 100 meter threshold could change as devices evolve, so set this property based on the needs of the application and not the underlying threshold, which triggers different behavior. Finally, the DesiredAccuracyInMeters property overrides anything set in the Geolocator’s DesiredAccuracy property.

Also, consider using the overload of GetGeopositionAsync, which accepts a maximumAge parameter and a timeout parameter. The maximum age parameter is a TimeSpan that specifies the maximum acceptable age of cached location data. The timeout parameter is also a TimeSpan and will cause the GetGeopositionAsync function to throw an exception if determining the current location takes longer than specified.

The previous two tips should substantially improve performance. However, in areas with poor Internet connectivity and on devices with low memory and slower CPUs, the Map control and the Mapping Services APIs may still need time, resulting in the user experiencing a delay. In such cases, the Map control will display a map of the globe at the current zoom level until a location is set into the Map control’s center point property. This is also a poor UX because the user should be given some visual indication that work is occurring. In the XAML in Figure 1, a progress bar is added as a child of the Map control and is made visible. The progress bar is set up as indeterminate because the exact amount of time needed isn’t known. Once the center property of the map is calculated and set into the Map control, the progress bar can be collapsed. Best practices for the use of indeterminate progress bars indicate they should always be placed at the very top of the page, and for most scenarios, I agree. However, when using the Map control, I prefer to see the progress bar displayed right across the center of the map. The Map control tends to grab the user’s focus and for this reason a progress bar at the very top of the page might be overlooked. Also, a progress bar shown across the Map control tells the user that it’s the Map control doing work.

Data Binding a Collection

Binding a collection to the Map control is similar to binding a collection to a ListBox, ListView, GridView or any other control that displays collections. To illustrate this with the Map control, consider the following class:

public class Team
{
  public string TeamName { get; set; }
  public Uri ImageSource { get; set; }
  public string Division { get; set; }
  public string StadiumName { get; set; }
  public Geopoint StadiumLocation { get; set; }
  public Point NAP { get; set; }
}

An instance of this class could be used to hold basic information about a professional sports team. Notice that one of the properties is a Geopoint property that contains the location of the stadium used as the team’s home field.

The XAML in Figure 4 sets up the Map control such that a collection of Team objects can be bound to it and the location of each team’s home field stadium will be marked with a small icon.

Figure 4 Setting Up the Map Control for Collection Binding

<Maps:MapControl
  x:Name="myMapControl" Grid.Row="1"
  MapServiceToken="{StaticResource MapServiceTokenString}" >
  <Maps:MapItemsControl x:Name="MapItems" >
    <Maps:MapItemsControl.ItemTemplate>
      <DataTemplate>
        <Image Source="{Binding ImageSource}"
          Maps:MapControl.Location="{Binding StadiumLocation}"
          Maps:MapControl.NormalizedAnchorPoint="{Binding NAP}"
          Height="15" Width="15"
          />
      </DataTemplate>
    </Maps:MapItemsControl.ItemTemplate>
  </Maps:MapItemsControl>
</Maps:MapControl>

If you’re familiar with collection binding in other controls, nothing in Figure 4 should be unfamiliar. An Image control is contained within a DataTemplate. The Image control’s attached Location property is bound to the StadiumLocation property of the Team object. The Image control also has an attached NormalizedAnchorPoint property, which is bound to a property that centers the image over the stadium location. 

Note that when binding the Map control to a collection, the NormalizedAnchorPoint property can’t be hardcoded. It must be bound to a value in the underlying object. If you try to set this property like this:

Maps:MapControl.NormalizedAnchorPoint="0.5,0.5"

the XAML editor will not like this syntax and the value will never get set. Microsoft is aware of this problem.

Figure 5 shows the Map control displaying the NFL stadium locations. (This screenshot is from the code sample that accompanies this article. The code sample contains all the ancillary code not shown here for the sake of brevity, such as instantiating the collection and properly formatting the control to fit nicely on a page.)

The Stadiums of the National Football League
Figure 5 The Stadiums of the National Football League

Scaling Controls

It’s possible to size controls so they represent a specified distance. For example, you may want to show a scale on your map. A scale gives users a visual clue, allowing them to guess distances between objects on the map. Also, if you’re using the Map control to depict a geofence, a good way to do this is to add an ellipse to the Map control, shape it into a circle, and size the radius of the circle such that the circle covers the alert distance of the geofence.

Controls in Windows Phone 8.1 are sized by specifying width and height in pixels. Therefore, scaling controls to represent a certain distance involves determining how much distance is represented by a single pixel on the Map control. Here’s the equation for determining the distance represented by a single pixel on the Map control: 

const double BING_MAP_CONSTANT = 156543.04;
double MetersPerPixel =
  BING_MAP_CONSTANT * Math.Cos(latitude) / Math.Pow(2, zoomLevel);

The latitude used should come from the Map control’s center property. It must be in radians, not degrees. Because latitude is typically expressed in degrees, you’ll have to convert it to radians by multiplying the degree value by Pi/180 (Math.PI/180). The zoom level comes from the zoom level of the Map control and is a value from 1 to 20. On Windows Phone 8.1, it’s expressed as a double.

A complete mathematical derivation of the equation shown here is beyond the scope of this article; however, a few comments are in order.

At first glance the equation may seem incorrect. Why does the latitude matter? Shouldn’t the scale of a map simply be a function of the zoom level? The truth is that latitude does matter and the zoom level alone is insufficient when determining the scale of a map. Bing Maps, which is the back end for the Map control, makes use of the Mercator projection. The Mercator projection is a technique for projecting the details of the surface of a sphere (in this case the Earth) onto a square surface. This projection introduces inconsistencies in scale because the farther you go from the equator, the more stretching there is to maintain a square fit. A simple experiment can help you better understand the Mercator projection. Peel an orange and save all the pieces. When the orange is completely peeled, put the pieces back together on a flat surface and try to get them to exactly fit into a square shape. It’s impossible. The only way to get these pieces into a square shape is to stretch the pieces that are not on the “equator” of your orange. The closer you get to the “poles,” the more stretching you need to do to get a square fit. This stretching creates a difference in scale as compared to locations close to the equator. The amount of stretching needed is a function of the distance from the equator. Hence, the dependency on latitude. As an example of this stretching in action on a Mercator projection of the Earth, consider the two cities of Quebec, Canada, and Key West, Fla. A completely zoomed-in view (Zoom Level of 20) of Quebec using the Map control will result in 100 pixels representing 33.5 feet. The same zoom level of Key West will result in 100 pixels representing 44.5 feet. The residents of Quebec enjoy a scale that’s 11 feet better than the residents of Key West—a small consolation for the colder climate. For more information on the Mercator projection, check out the MSDN Library article, “Understanding Scale and Resolution,” at bit.ly/1xIqEwC.

The final fact to note on the equation is the Bing Map Constant. The Bing Map Constant is based on the Earth’s radius and on the equations Microsoft uses to determine the map view for a specified zoom level. Its units are meters per pixel.

Figure 6 shows a TextBlock and a Rectangle added to a Map control to be used as a distance scale. Figure 7 shows the ZoomLevelChanged event and the logic needed to create a distance scale. (The code sample checks the RegionInfo.CurrentRegion.IsMetric property and shows metric values for users who are more familiar with the metric system.) Finally, Figure 8 show a screenshot from the code sample—a distance scale added to a Map control.

Figure 6 Xaml TextBlock and Rectangle Used As a Distance Scale

<Maps:MapControl
  x:Name="myMapControl" Grid.Row="1"
  MapServiceToken="{StaticResource MapServiceTokenString}"
  ZoomLevelChanged="myMapControl_ZoomLevelChanged">
  <!-- Distance scale, which is located at the lower left of the Map control. -->
  <TextBlock Name="tbScale" Text="Scale Text" FontSize="15" Foreground="Black"
    Margin="24,530,24,6" Opacity="0.6" />
  <Rectangle Name="recScale" Fill="Purple" Width="100" Height="6"
    Margin="24,548,24,24" Opacity="0.6" />
</Maps:MapControl>

Figure 7 ZoomLevelChanged Event

private void myMapControl_ZoomLevelChanged(MapControl sender, object args)
{
  // Get the Map control's current zoom level.
  double zoomLevel = sender.ZoomLevel;
  // Use the latitude from the center point of the Map control.
  double latitude = sender.Center.Position.Latitude;
  // The following formula for map resolution needs latitude in radians.
  latitude = latitude * (Math.PI / 180);
  // This constant is based on the diameter of the Earth and the
  // equations Microsoft uses to determine the map shown for the
  // Map control's zoom level.
  const double BING_MAP_CONSTANT = 156543.04;
  // Calculate the number of meters represented by a single pixel.
  double MetersPerPixel =
    BING_MAP_CONSTANT * Math.Cos(latitude) / Math.Pow(2, zoomLevel);
  // Aditional units.
  double KilometersPerPixel = MetersPerPixel / 1000;
  double FeetPerPixel = MetersPerPixel * 3.28;
  double MilesPerPixel = FeetPerPixel / 5280;
  // Determine the distance represented by the rectangle control.
  double scaleDistance = recScale.Width * MilesPerPixel;
  tbScale.Text = scaleDistance.ToString() + " miles";
}

Distance Scale Added to a Map Control Showing Sunset Pier in Key West, Fla.
Figure 8 Distance Scale Added to a Map Control Showing Sunset Pier in Key West, Fla.

Downloading Maps for Offline Use

Maps can be downloaded for offline use. This is handy for applications that need to provide mapping functionality when the device is without any form of Internet connectivity, such as while a user is driving on a long highway between urban areas or hiking deep in the woods.

Maps can’t be downloaded programmatically without user consent because they take up a significant amount of storage. The user must opt in to each download by using a system-­provided UX that allows needed maps to be selected and downloaded. This is accomplished using the static class MapManager, which belongs to the Windows.Services.Maps namespace. This class provides functions for both downloading maps and updating maps. The ShowDownloadedMapsUI function shown here will launch the built-in Maps application and display the page shown in Figure 9:

MapManager.ShowDownloadedMapsUI();

The UI for Downloading Maps
Figure 9 The UI for Downloading Maps

The built-in maps application is named Maps and the page shown in Figure 9 can also be accessed by tapping the Download maps button from the Settings menu option. Because this function takes the user to another application (the built-in maps application), he will have to return to the original application manually.

The page shown in Figure 9 shows all the maps that have been previously downloaded. It also contains a menu option that allows you to delete any of the previously downloaded maps in case storage on the device needs to be freed or a map is no longer needed.

Tapping the add button (+) starts the map selection wizard, displaying a page that lets you specify the continent containing the map you wish to download. Once a continent is selected, the page displaying all countries within the selected continent will be displayed. You can select all the countries within a continent and download maps for the entire continent, but this would require a large amount of storage. For example, all the maps for the United States require 3.4GB of storage. The entire continent of North and Central America would require much more.

If you select a large country that contains multiple regions or states, you’ll see a page that lets you choose specific regions or states. You can select more than one region by using the Select button. When a region is specified, you’ll be taken to the Download page shown in Figure 10. This same page will also be shown if you select a small country without any regions or states.

Downloading a Map
Figure 10 Downloading a Map

Before downloading any maps, it’s a good idea to encourage users to check the available space on the device by going to the Settings application and selecting Storage sense. This will show the available space on the device, as well as the storage used by each application on the device in case space needs to be freed up. At the time of this writing, there are no WinRT APIs that allow you to programmatically determine the amount of storage used on a device or the amount of storage available.

It’s also a good idea to download maps while connected to an unrestricted network, such as Wi-Fi. The code sample for this article shows how to check the current network connection and warn the user if they’re on a metered connection.

Updating Downloaded Maps

Maps are occasionally changed or modified. New roads may be added to a city, street names may change, and regional borders are sometimes altered. Therefore, downloaded maps should be periodically updated. The MapManager static class contains a function named ShowMapsUpdateUI for updating downloaded maps:

MapManager.ShowMapsUpdateUI();

The ShowMapsUpdateUI function is similar to the ShowDownloadedMapsUI function in that it takes the user to the built-in maps application. When this function is called, all downloaded maps are checked to see if they need to be updated. If any previously downloaded maps require updating, the user will be told the size of the update and given the option to either cancel or proceed with the download. This same functionality is also available from the Settings menu option of the Maps application.

If your application encourages the downloading of maps by utilizing ShowDownloadedMapsUI, it’s a best practice to also use ShowMapsUpdateUI so that downloaded maps can stay current.

Wrapping Up

In this article, I showed you how to add application data to the Map control for Windows Phone 8.1. This included adding XAML controls to a map, data binding and drawing controls to scale. I also showed how to take the Map control’s underlying data offline so the Map control can be set up to work seamlessly within applications designed for offline use.


Keith Pijanowski is an engineer, entrepreneur, and business guy. He has more than 20 years of experience in the software industry, working for startups and large companies in roles that have ranged from writing code to business development. Reach him at keithpij@msn.com or twitter.com/keithpij.

Thanks to the following Microsoft technical expert for reviewing this article: Mike O’Malley
Mike O’Malley is Senior Program Manager, Operating Systems at Microsoft. He has been working on Mmap- related developer experiences for Microsoft for three years and has presented on location- aware application development at Build each year.