November 2014

Volume 29 Number 11


Windows Phone 8.1 : Mapping in Windows Phone 8.1

Keith Pijanowski

Each generation of Windows Phone brings improved mapping capabilities, and Windows Phone 8.1, with the new Map control introduced with Visual Studio 2013 Update 3, is no exception. If all you need is a simple map, centered on a specified location, you can accomplish this with the new Map control with just a few lines of code. But if you need to respond to user interaction, customize the look of the control, or calculate routes, you can also use the new Map control and the mapping services API for these more complex scenarios.

This article will cover the new Windows Phone 8.1 Map control and the mapping services APIs. I’ll start by showing basic map display features, then take a look at more advanced features that show how to place push pins on the map at a specified address. I’ll also show you how to place XAML controls on the Map control, and how to get the GPS location (and address) that corresponds to a specific point on the Map control. This is useful if you want to allow the user to touch a location of interest and then determine the GPS location and street address. Finally, I’ll show how to calculate driving and walking directions.

A good companion piece to the material I’ll cover is the September 2014 article, “Creating a Location-Aware App with Geofencing,” by Tony Champion. A geofence is a specified region around a GPS location that can be registered with Windows. Once the region is registered, an application can receive notifications when the device enters or leaves the region. Geofencing is also new to Windows Phone 8.1. If you’re going to build an application that uses geofencing, consider using the Map control to allow users to specify geofences and show their existing geofences.

As of this writing, there’s a different Map control for every flavor of mobile and tablet platform:

  • Windows Phone Silverlight 8.0/8.1 applications: Microsoft.Phone.Maps.Controls.Map
  • Windows Store 8.x applications: Bing.Maps.Map
  • Windows Phone 8.1 Runtime applications: Windows.UI.Xaml.Controls.Maps

This article focuses on the new Map control and mapping services that can be used within applications written for the Windows Phone 8.1 Runtime.

Getting Started

Authenticating Your Application Your Windows Phone application needs to be authenticated every time it uses the Map control or the mapping services found in the Windows.Services.Maps namespace. To authenticate your application, you need a Map service ApplicationID and a Map service Authentication Token, both of which can be retrieved from your Developer Dashboard in the Windows Phone Dev Center. You’ll find step-by-step instructions for getting an ApplicationID and AuthenticationToken from the MSDN Library article at bit.ly/1y78M2F.

Once you have them, you need to place the ApplicationID within your application’s Package.appxmanifest file. Instructions for doing this are also in the MSDN Library article. You’ll use the Map Service Authentication Token in your code, as you’ll see later in this article.

Capabilities Many of the samples in this article use the user’s current location, and the Map control may need the Internet. Therefore, the Internet and Location capabilities should be declared in the application manifest file as shown in Figure 1. However, mapping services also work without Internet connectivity when maps are downloaded for offline use. For more information on downloading maps, see “How to Download and Update Offline Maps (XAML)” at bit.ly/1nXBOrS.

Declaring Internet and Location Capabilities
Figure 1 Declaring Internet and Location Capabilities

Displaying Maps

Displaying a Simple Map Adding a Map control to a page is as easy as adding any other control. You can drag the control from the Visual Studio Toolbox onto your page or you can enter the XAML as follows: 

<Maps:MapControl
  x:Name="myMapControl"
  MapServiceToken="Place your token here."
  Height="200" Width="300"
  HorizontalAlignment="Center"
  Margin="0,0,0,12"
  />

Notice the MapServiceToken property. This property must be given the value of the Map Service Authentication Token that was assigned to your application in the Developer Dashboard of the Windows Phone Dev Center. Also, notice that you can position the Map control like any other control. The Map control doesn’t have to dominate the entire screen. A small map zoomed into a specific location can make an effective visual on a page that contains location-specific information. On the other hand, maps that are zoomed out (or may be zoomed out by the user) will need additional real estate. For such maps, consider using the full page.

The preceding XAML will produce uninteresting results if placed within an application and run. This control will show a map completely zoomed out. Therefore, the next step in using the Map control is to identify a specific location as the center point of the map, as well as the zoom level.

The following code first calculates the user’s current position and then sets that position into the Center property of the map. It also sets the zoom level to 15. The minimum value for the zoom level is 1, which shows the map fully zoomed out—and displays half the globe in your Map control. The maximum value for the zoom level is 20, which can zoom the Map control into a specific street address:

Geolocator geolocator = new Geolocator();
Geoposition geoposition = null;
try
{
  geoposition = await geolocator.GetGeopositionAsync();
}
catch (Exception ex)
{
  // Handle errors like unauthorized access to location
  // services or no Internet access.
}
myMapControl.Center = geoposition.Coordinate.Point;
myMapControl.ZoomLevel = 15;

Adding Images to the Map Control If you want to mark a specific location on your map, you can add images with a title to the Map control. The code in Figure 2 will point to the device’s current location by adding an image from the project’s Assets folder to the Map control.

Figure 2 Adding a Custom Image to the Map Control

Geolocator geolocator = new Geolocator();
Geoposition geoposition = null;
try
{
  geoposition = await geolocator.GetGeopositionAsync();
}
catch (Exception ex)
{
  // Handle errors like unauthorized access to location services or no Internet access.
}
MapIcon mapIcon = new MapIcon();
mapIcon.Image = RandomAccessStreamReference.CreateFromUri(
  new Uri("ms-appx:///Assets/PinkPushPin.png"));
mapIcon.NormalizedAnchorPoint = new Point(0.25, 0.9);
mapIcon.Location = geoposition.Coordinate.Point;
mapIcon.Title = "You are here";
myMapControl.MapElements.Add(mapIcon);

To add an image to your Map control, first create a MapIcon object and set its Location property to a Geopoint, which represents the location of interest on your map. The code in Figure 2 uses the user’s current location. The Title property is the text that will be displayed above the image that’s added to the Map control.

To specify the image, set the MapIcon’s Image property using the RandomAccessStreamReference.CreateFromUri method. This is a static method found in the Windows.Storage.Streams namespace. The image should be approximately 50 pixels by 50 pixels; if it’s larger, it will be scaled down.

When the purpose of your image is to point to a specific location, it’s a best practice to use the MapIcon’s NormalizedAnchorPoint property. This property allows you to indicate the point within your image that will be placed over the specified Geopoint. If your image is a push pin, this would be the tip of the pin. If you’re using an image of an arrow, this point would be the tip of the arrow. The default value for the NormalizedAnchorPoint property is a Point object created with a value of (0, 0), which represents the upper-left corner of the image. The bottom-right corner of the image is (1,1). This coordinate system is depicted in Figure 3 with a few sample points.

Coordinate System Used for the NormalizedAnchorPoint Property
Figure 3 Coordinate System Used for the NormalizedAnchorPoint Property

If the image used as the map icon contains a pointer and you don’t set the NormalizedAnchorPoint property correctly, the map icon will point to an incorrect position on your map. If your image is merely a dot or small circle, you’d use a value of (0.5,0.5), which is the center of the image.

If you don’t specify an image, the MapControl has a default image, shown in Figure 4, that will be used.

The Default Map Icon
Figure 4 The Default Map Icon

As you can see, the default MapIcon image has a pointer located at the bottom center of the image. Therefore, the Normalized­AnchorPoint property needs to be set to (0.5, 1.0).

The final detail to notice about the code is that the MapIcon object is added to the Map’s MapElements collection, so you can add more than one MapIcon to your map if you wish.

There’s no guarantee that the map icon and the title will be shown. It might be hidden if it will obscure other elements or labels on the map. This is especially true as the map is zoomed out (zoom level decreases) and more information has to fit within the Map control.

The code in Figure 2 displays the map in Figure 5, assuming you’re watching a baseball game in Fenway Park.

Map Control Showing Fenway Park in Boston, Mass.
Figure 5 Map Control Showing Fenway Park in Boston, Mass.

Adding Controls to the Map Control Adding a XAML control to the Map control is similar to adding an image. The following code creates an ellipse, sizes it to a circle, adds it to the Map control, and centers it on a geographic coordinate:

// Create a circle
Windows.UI.Xaml.Shapes.Ellipse fence = 
  new Windows.UI.Xaml.Shapes.Ellipse();
fence.Width = 30;
fence.Height = 30;
fence.Stroke = new SolidColorBrush(Colors.DarkOrange);
fence.StrokeThickness = 2;
MapControl.SetLocation(fence, geoposition.Coordinate.Point);
MapControl.SetNormalizedAnchorPoint(fence, new Point(0.5, 0.5));
myMapControl.Children.Add(fence);

Notice that the Map control’s static function SetLocation must be used to attach the geographic coordinate to the ellipse object. Similarly, the SetNormalizedAnchorPoint function is used to attach the Normalized Anchor Point property.

Any control that inherits from UIElement or MapItemsControl can be added to the Map control using these techniques.

Getting the Location

The heart of the Map control is the BasicGeoposition object, which can represent every location on Earth using just three numbers: latitude, longitude and altitude. Not only is this is an efficient way to divide the globe, it’s also precise. Every location has its own unique combination of latitude, longitude and altitude. No two locations will have the same combination.

Unfortunately, your application is going to interface with human beings, and human beings do not like to represent locations as pure numeric values. Consequently, the globe is divided into continents, countries within continents, regions (or states) within countries, towns within regions, and streets within towns. While continent names, country names, and postal codes are unique, regions can sometimes contain two or more towns with the same name. Additionally, a town may have two or more streets with the same name. The bottom line is that it’s possible for an address to be confused with two or more locations. This is especially true when dealing with an incomplete representation of an address (leaving off the state name, postal code or town).

Therefore, if your application is going to show more than the user’s current location, which can be retrieved directly from the device, you’ll need to convert between the numeric and named systems previously described. The process of converting an address to a geographic location is known as geocoding. The process of converting a geographic location to a human-readable address is known as reverse geocoding.

Converting an Address to a Geographic Location (Geocoding) Geocoding an address requires the use of the mapping services APIs—the collection of classes found in the Windows.Services.Maps namespace. To use these APIs, you need to set your Authentication Token in the static property ServiceToken of the MapService class. The following line of code shows how to do this (the remainder of this article will assume that this line of code is in the OnLaunched event of the application):

MapService.ServiceToken = "Place your token here";

Once the Authentication Token has been set, you can use the FindLocationsAsync static function, which is found in the MapLocationFinder class. Here’s how to use the FindLocationsAsync function:

Geolocator geolocator = new Geolocator();
Geoposition currentPosition = await geolocator.GetGeopositionAsync();

MapLocationFinderResult result = await MapLocationFinder.FindLocationsAsync(
  address, currentPosition.Coordinate.Point, 5);

The first parameter of the FindLocationsAsync function is a string representation of an address. Any string may be passed to this function. The back-end mapping services, which the FindLocationsAsync function communicates, will do the best it can to find a matching location. The second parameter is known as a reference point and represents the geographic location where the search should start. This parameter is sometimes referred to as a hint. If used properly it can greatly increase the relevance of the returned locations and the speed with which they’re calculated. For example, if you know the user is doing a local search, then pass in the user’s current location as the reference point. However, if you know the user is planning her vacation, pass in the location of her hotel. The final parameter is the maximum number of locations that will be returned.

The return value of the FindLocationsAsync function is an object of type MapLocationFinderResult. It will tell you if the search was successful or not. If the search was successful, the MapLocation­FinderResult object will contain a collection of MapLocation objects. The code in Figure 6 checks the success of the search, and if it’s successful, places the collection into a ListView control so that all matching locations can be shown to the user. The MapLocation object contains both human-readable address information and geographic information that can be used by the MapControl. The code in Figure 6 could easily be converted to show push pins on a Map control.

Figure 6 Retrieving the Results of a Location Search

if (result.Status == MapLocationFinderStatus.Success)
{
  List<string> locations = new List<string>();
  foreach (MapLocation mapLocation in result.Locations)
  {
    // create a display string of the map location
    string display = mapLocation.Address.StreetNumber + " " +
      mapLocation.Address.Street + Environment.NewLine +
      mapLocation.Address.Town + ", " +
      mapLocation.Address.RegionCode + "  " +
      mapLocation.Address.PostCode + Environment.NewLine +
      mapLocation.Address.CountryCode;
    // Add the display string to the location list.
    locations.Add(display);
  }
  // Bind the location list to the ListView control.
  lvLocations.ItemsSource = locations;
}
else
{
  // Tell the user to try again.
}

Converting a Geographic Position into an Address (Reverse Geocoding) Another useful feature a mapping application can provide is the ability to get a human-readable address from a touched area of the Map control. To code this feature, you first need a way to get the position of the control that was touched by the user. This can be accomplished using the MapTapped event of the Map control. The Map control also has a Tapped event, which is fired when a XAML child of the Map control is tapped. Any direct tap of the Map control goes through the MapTapped event. The code in Figure 7 shows an implementation of the MapTapped event that displays the address of the tapped location in a TextBlock control.

Figure 7 Finding the Address of a Tapped Location on the Map Control

private async void myMapControl_MapTapped(MapControl sender, 
  MapInputEventArgs args)
{
  // Find the address of the tapped location.
  MapLocationFinderResult result =
    await MapLocationFinder.FindLocationsAtAsync(args.Location);
  if (result.Status == MapLocationFinderStatus.Success)
  {
    if (result.Locations.Count > 0)
    {
      string display = result.Locations[0].Address.StreetNumber + " " +
        result.Locations[0].Address.Street;
      tbAddress.Text = display;
    }
  }
}

There are a few details worth calling out in this implementation of the MapTapped event. First, the geopoint that’s associated with the tapped location is passed to this event via args.Location. The FindLocationsAtAsync function can use this geopoint to get a human-readable address. This function is a static function in the MapLocationFinder class, which is part of the mapping services API (Windows.Services.Maps namespace). This function returns a MapLocationFinderResult object that contains a Status property and a collection of MapLocation objects. Even though the return value allows for a collection of MapLocation objects, usually there will be only one MapLocation object. However, there might be a high-rise building somewhere in the world that has two or more street addresses associated with it. If a user clicks in the middle of a big field or lake, a value will still be returned. If there’s no actual street address nearby, the MapLocation object will contain only the Region, Country and Postal Code.

Routes

The mapping services can calculate routes from a given starting point to a given ending point. The Map control itself is capable of displaying the route, which can be calculated as driving routes or walking routes.

Driving Routes As the name implies, driving routes are intended to be driven in some vehicle that won’t travel the wrong way down a one-way street and must obey all other rules of the road. The code in Figure 8 calculates the driving directions from New York City’s Grand Central Station to Central Park.

Figure 8 Calculating a Driving Route

// Start at Grand Central Station.
BasicGeoposition startLocation = new BasicGeoposition();
startLocation.Latitude = 40.7517;
startLocation.Longitude = -073.9766;
Geopoint startPoint = new Geopoint(startLocation);
// End at Central Park.
BasicGeoposition endLocation = new BasicGeoposition();
endLocation.Latitude = 40.7669;
endLocation.Longitude = -073.9790;
Geopoint endPoint = new Geopoint(endLocation);
// Get the route between the points.
MapRouteFinderResult routeResult =
await MapRouteFinder.GetDrivingRouteAsync(
  startPoint,
  endPoint,
  MapRouteOptimization.Time,
  MapRouteRestrictions.None,
  290);

The code is fairly straightforward. The static function MapRouteFinder.GetDrivingRouteAsync is passed a starting point, an ending point, an optimization parameter (described later) and a parameter that specifies restrictions (also described later). The fifth and final parameter is an optional heading that specifies the current direc­tion of the user in com­pass degrees (0 degrees represents North, 180 degrees represents South and so on). This is useful when routes are calculated while the user is driving. When the heading parameter is used, the route is calculated based on the user’s current direction of travel. The first few legs of the route will contain maneuvers that get the user to the best possible route, which is especially useful within cities where the user might be on a one-way road heading in the wrong direction. Changing the heading to 135 in the code in Figure 8 causes the route to be calculated such that it contains a legal way to change directions.

Once you have a result from the MapRouteFinder.GetDrivingRoute­Async function, you need to check it. If a route was successfully retrieved, it can then be displayed as turn-by-turn instructions. The route can also be visually displayed in a Map control. The code in Figure 9 displays the route from Grand Central Station to Central Park as turn-by-turn instructions.

Figure 9 Looping Through Route Legs and Route Maneuvers

if (routeResult.Status == MapRouteFinderStatus.Success)
{
  // Use the route to initialize a MapRouteView.
  MapRouteView viewOfRoute = new MapRouteView(routeResult.Route);
  viewOfRoute.RouteColor = Colors.Blue;
  viewOfRoute.OutlineColor = Colors.Blue;
  // Add the new MapRouteView to the Routes collection
  // of the MapControl.
  MapwithDrivingRoute.Routes.Add(viewOfRoute);
  // Fit the MapControl to the route.
  await MapwithDrivingRoute.TrySetViewBoundsAsync(
    routeResult.Route.BoundingBox,
    null,
    Windows.UI.Xaml.Controls.Maps.MapAnimationKind.Bow);
}

The MapRoute object contains summary information that indicates how long it should take to traverse the route and the length of the route in meters. The MapRoute object organizes route information by using MapRouteLeg objects and MapRouteManeuver objects. A MapRoute contains one or more MapRouteLeg objects and each MapRouteLeg contains one or more MapRouteManeuver objects. The information that will be shown to the user is in the MapRouteManeuver object. The MapRouteManeuver objects contain naviga­tional instructions, exit information (if applicable), length of the maneuver, and maneuver notices (if applicable). The Kind property is also very useful. Sample values include left turn, right turn, and so forth. It can be used to associate an image with each maneuver. A full list of possible values for the Kind property can be found at bit.ly/1nXOyi7. Figure 10 shows what these directions would look like in an application.

Turn-by-Turn Directions for a Driving Route
Figure 10 Turn-by-Turn Directions for a Driving Route

Routes can also be displayed graphically within a Map control (see Figure 11).

Figure 11 Showing a Route Visually in a Map Control

// Display summary info about the route.
tbTurnByTurn.Inlines.Add(new Run()
{
  Text = "Total estimated time (minutes) = "
    + routeResult.Route.EstimatedDuration.TotalMinutes.ToString("F1")
});
tbTurnByTurn.Inlines.Add(new LineBreak());
tbTurnByTurn.Inlines.Add(new Run()
{
  Text = "Total length (kilometers) = "
    + (routeResult.Route.LengthInMeters / 1000).ToString("F1")
});
tbTurnByTurn.Inlines.Add(new LineBreak());
// Display the directions.
tbTurnByTurn.Inlines.Add(new Run()
{
  Text = "DIRECTIONS"
});
tbTurnByTurn.Inlines.Add(new LineBreak());
// Loop through the legs and maneuvers.
int legCount = 0;
foreach (MapRouteLeg leg in routeResult.Route.Legs)
{
  foreach (MapRouteManeuver maneuver in leg.Maneuvers)
  {
    tbTurnByTurn.Inlines.Add(new Run()
    {
      Text = maneuver.InstructionText
    });
    tbTurnByTurn.Inlines.Add(new LineBreak());
  }
}

To show a route in a Map control you first need to create a view of the route using the MapRouteView class. Once you have a view you can add it to the Routes collection of the Map control. Finally, the Map control’s TrySetViewBoundsAsync function will size the Map control such that the entire route is visible. This function even comes with an animation parameter that provides a visual effect as the Map control is being redrawn to accommodate the route. Figure 12 shows what this would look like in an application.

A Driving Route in a Map Control
Figure 12 A Driving Route in a Map Control

Route Optimizations Driving routes can be optimized with respect to time, distance and traffic. The MapRouteOptimization enumeration provides the values in Figure 13 for optimizing the calculation of a route.

Figure 13 The MapRouteOptimization Enumeration

Time The route will be optimized such that it can be covered in the least amount of time given the distance and speed limit for each maneuver of the route.
Distance The route will optimized such that it can be covered in the shortest distance given the distance of each maneuver.
TimeWithTraffic The route will be optimized such that it can be covered in the least amount of time given the distance, speed limit and current traffic conditions for each maneuver of the route.

 

Route Restrictions Driving routes can also be calculated with restrictions, such as “no highways” or “no toll roads.” The MapRoute­Restrictions enumeration provides the values shown in Figure 14 for restricting the calculation of a driving route.

Figure 14 The MapRouteRestrictions Enumeration

None No restrictions are applied to the calculation of a route. The route can contain maneuvers that include highways, tunnels, ferries, toll roads and dirt roads.
Highways The route will not contain any maneuvers that travel over a highway.
TollRoads The route will not contain any maneuvers that travel over a toll road.
Ferries The route will not contain any maneuvers that require the use of a ferry.
Tunnels The route will not contain any maneuvers that travel through a tunnel.
DirtRoads The route will not contain any maneuvers that travel over a dirt road.

 

These values can be logically combined. For example, the following code is a restriction that prevents the use of highways and toll roads:

MapRouteFinderResult routeResult =
  await MapRouteFinder.GetDrivingRouteAsync(
  startPoint,
  endPoint,
  MapRouteOptimization.Time,
  MapRouteRestrictions.Highways || MapRouteRestrictions.TollRoads);

Walking Routes Calculating walking routes is very similar to calculating driving routes. The following code calculates a walking route based on the same end points as the driving route example: 

// Get the route between the points.
MapRouteFinderResult routeResult =
  await MapRouteFinder.GetWalkingRouteAsync(
  startPoint,
  endPoint);

The static function MapRouteFinder.GetWalkingRouteAsync is used to perform this calculation. Unlike the corresponding function for calculating driving routes, this function has no overloads that allow for optimizations, restrictions or current heading. These attributes don’t impact walking route calculations because speed limits and traffic conditions aren’t relevant. Also, the current heading can be easily changed. Walking routes are calculated for the shortest distance. The images in Figure 15 and in Figure 16 show the walking route from Grand Central Station to Central Park.

Turn-by-Turn Directions for a Walking Route
Figure 15 Turn-by-Turn Directions for a Walking Route

Walking Route Shown in a Map Control
Figure 16 Walking Route Shown in a Map Control

Wrapping Up

In this article I showed how to use the new Map control for Windows Phone 8.1, including basic map display, adding images and adding controls. I also introduced the underlying mapping services API and took a look at geocoding, reverse geocoding and route calculations.

A number of other topics related to mapping should be considered: overlaying tiled images, data binding and managing offline maps. You can find information about these topics on the Windows Dev Center (bit.ly/X7S7ei).


Keith Pijanowski has more than 20 years of experience in the software industry. He has worked for startups and large companies in roles that ranged from writing code to business development. Currently, he’s working for himself as an independent consultant. Pijanowski lives in the New York City area, but has never cheered for a New York sports team. 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.