Geocoding and Routing in Bing Maps Windows Store Apps (Native)

In this blog post we are going to look at how implement Geocoding and Routing using the Native Bing Maps Windows Store App SDK. If you are new to developing with the Native Bing Maps Windows Store App SDK I recommend reading the Getting started with Bing Maps Windows Store Apps blog post first. We will also be making use of the Bing Maps REST Services, if you are unfamiliar with using this in native code take a look at this MSDN documentation on Using the REST services with .NET. The methods covered in this blog post can be used as an example of how to access other REST services from Windows Store applications.

Geocoding is one of the most common tasks by users of online maps. Geocoding is the process of taking an address or query and returning its equivalent coordinate on the map. Routing is the task of calculating the directions between two or more locations. In Bing Maps, there are a lot of different options around routing such as routing by different modes of transportation; driving, walking, or transit.

Setting up the project

To set up the project, open Visual Studios 2012 and create a new project. In the window that opens, select Visual C# -> Windows Store. Select the Blank App template and call the application BingMapsSearch_WinRT_CS and press OK.

Next, we will add in the libraries needed for serializing the response from the Bing Maps REST services. To do this, right-click on the project and select Add -> New Item. Create a class file called BingMapsRESTServices.cs. Open up this class file and delete the contents. Go to the MSDN documentation on Using the REST services with .NET and copy and paste the C# Data Contracts into this file. At this point, your Solution should look something like this:

image

Adding Bing Maps to the App

To get started we will need to add a reference to the Bing Maps SDK. To do this, right click on the References folder and press Add Reference. Select Windows -> Extensions select Bing Maps for C#, C++ and Visual Basic. If you do not see this option, take a moment to verify that you have installed the Bing Maps SDK for Windows 8 style apps. While you are here, you should add a reference to the Microsoft Visual C++ Runtime Package as it is required by the Bing Maps SDK when developing using C# or Visual Basic.

image

You may notice that there is a little yellow indicator on the references that you just added. This is because the Bing Maps SDK requires that you set the Active solution platform in Visual Studio to one of the following options.

  • > C#, Visual Basic: ARM, x86 or x64

  • > C++: ARM, Win32 or x64

To do this, right click on the Solution folder and select Properties. Then go to Configuration Properties -> Configuration. Find your project and under the Platform column set the target platform. For this blog post I’m going to select x86. Press Ok and the yellow indicator should disappear from our references.

image

Now we can add a map to our application. To do this, open the MainPage.xaml file. We will first need to add the Bing Maps SDK as a namespace at the top of the file. After we do this we can add a Map object to the Grid control and add our Bing Maps key to the credentials properties of the map. We will also give the map a name of MyMap. While we are at it, we will create a side panel that has input controls for geocoding and routing and a button for clearing the map. In the side panel we will also add two ListBox objects. We will use these to display the geocode results and route itinerary to the user. The XAML for file will look like this:

<Page

   x:Class="BingMapsSearch_WinRT_CS.MainPage"

   xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:local="using:BingMapsSearch_WinRT_CS"

   xmlns:d="https://schemas.microsoft.com/expression/blend/2008"

   xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"

   xmlns:m="using:Bing.Maps">

 

    <Page.Resources>

        <Style TargetType="Button">

            <Setter Property="Background" Value="Green"/>

        </Style>

    </Page.Resources>

   

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="350"/>

            <ColumnDefinition/>

        </Grid.ColumnDefinitions>

 

        <!-- Left Side Panel -->

        <ScrollViewer Background="Gray">

            <StackPanel Margin="10,10,20,10">

                <!-- Clear Map Button -->

                <Button Content="Clear Map" Click="ClearMapBtn_Click"

                       HorizontalAlignment="Right"/>

 

                <!-- Geocode Input -->

                <TextBlock Text="Geocode" FontSize="24"/>

                <Grid>

                    <TextBox Name="GeocodeTbx" HorizontalAlignment="Left"

                            Width="220" Height="25"/>

                    <Button Content="Geocode" HorizontalAlignment="Right"

                           Click="GeocodeBtn_Click"/>

                </Grid>

 

                <!-- Route Input -->

                <TextBlock Text="Route" FontSize="24" Margin="0,10,0,0"/>

                <StackPanel Orientation="Horizontal">

                    <TextBlock Text="From:" FontSize="18"/>

                    <TextBox Name="FromTbx" Width="220" Margin="10,0,0,0"/>

                </StackPanel>

                <StackPanel Orientation="Horizontal">

                    <TextBlock Text="To:" FontSize="18"/>

                    <TextBox Name="ToTbx" Width="220" Margin="33,10,0,10"/>

                </StackPanel>

                <Button Content="Get Directions" Click="RouteBtn_Click"

                       HorizontalAlignment="Right"/>

 

                <!-- Geocode Results Panel -->

                <ListBox Name="GeocodeResults"

                        SelectionChanged="GeocodeResultSelected" Margin="0,10,0,0">

                    <ListBox.ItemTemplate>

                        <DataTemplate>

                            <TextBlock Text="{Binding Name}"/>

                        </DataTemplate>

                    </ListBox.ItemTemplate>

                </ListBox>

 

                <!-- Route Itinerary Panel -->

                <StackPanel Name="RouteResults">

                    <ListBox ItemsSource="{Binding RouteLegs}">

                        <ListBox.ItemTemplate>

                            <DataTemplate>

                                <ListBox ItemsSource="{Binding ItineraryItems}">

                                    <ListBox.ItemTemplate>

                                        <DataTemplate>

                                            <StackPanel Orientation="Horizontal">

                                                <TextBlock Text="{Binding Instruction.Text}"

                                                          TextWrapping="Wrap" Width="200"/>

                                                <TextBlock Text="{Binding TravelDistance}"

                                                          Margin="10,0,0,0" />

                                                <TextBlock Text="km"/>

                                            </StackPanel>

                                        </DataTemplate>

                                    </ListBox.ItemTemplate>

                                </ListBox>

                            </DataTemplate>

                        </ListBox.ItemTemplate>

                    </ListBox>

                </StackPanel>

            </StackPanel>

        </ScrollViewer>

 

        <!-- Map Area -->

        <m:Map Name="MyMap" Grid.Column="1" Credentials="YOUR_BING_MAPS_KEY"/>

    </Grid>

</Page>

If you create the event handlers for the buttons and the GeocodeResults ListBox you can then run the application to see what it looks like. You should end up with something like this:

wsLayout

Adding the Application Logic

Open up the MainPage.xaml.cs file. In here we will create a global MapShapeLayer variable called routeLayer inside the MainPage class. We will use this shape layer to display the route line on the map. In the constructor of the MainPage class we will initialize this variable and add it to the map. At this point, the MainPage.xaml.cs file will look like this:

using Bing.Maps;

using BingMapsRESTService.Common.JSON;

using System;

using System.Runtime.Serialization.Json;

using System.Threading.Tasks;

using Windows.UI;

using Windows.UI.Popups;

using Windows.UI.Xaml;

using Windows.UI.Xaml.Controls;

using Windows.UI.Xaml.Media;

using Windows.UI.Xaml.Navigation;

 

namespace BingMapsSearch_WinRT_CS

{

    public sealed partial class MainPage : Page

    {

        private MapShapeLayer routeLayer;

 

        public MainPage()

        {

            this.InitializeComponent();

 

            routeLayer = new MapShapeLayer();

            MyMap.ShapeLayers.Add(routeLayer);

        }

 

        protected override void OnNavigatedTo(NavigationEventArgs e)

        {

        }

    }

}

There are a few other common methods that we will want to add to this file to make things a bit easier and keep the code clean. The first common task we will want to do is notify the user if there is an error with their request. In .NET we would normally use the MessageBox class, but this is not available to Windows Store applications. Instead, we have to use the MessageDialog class. This class has a lot more options than we need, so to keep things simple, we will create a method that takes in a string message and displays it to the user. We will call this method ShowMessage and use the following code:

private async void ShowMessage(string message)

{

    MessageDialog dialog = new MessageDialog(message);

    await dialog.ShowAsync();

}

The second common task is to call the Bing Maps REST service and then serialize the response into a BingMapsRESTService.Common.JSON.Response object. This class comes from the data contracts we copied in from MSDN documentation on Using the REST services with .NET. In that blog post we made use of the WebClient class to make our request to the service. In Windows Store applications we do not have access to this class and instead make use of the System.Net.Http.HttpClient class. We will call this common method GetResponse and use the following code:

private async Task<Response> GetResponse(Uri uri)

{

    System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();

    var response = await client.GetAsync(uri);

 

    using (var stream = await response.Content.ReadAsStreamAsync())

    {

        DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Response));

        return ser.ReadObject(stream) as Response;

    }

}

In this last common method, we will clear the map and the results panels of our application. The map has to be cleared before we process any new requests. We will also need this functionality for the clear map button. We will call this method ClearMap and use the following code:

private void ClearMap()

{

    MyMap.Children.Clear();

    routeLayer.Shapes.Clear();

 

    //Clear the geocode results ItemSource

    GeocodeResults.ItemsSource = null;

 

    //Clear the route instructions

    RouteResults.DataContext = null;

}

We can now add the button handler for the Clear Map button. All this button handler will do is call our ClearMap method. The handler will look like this:

private void ClearMapBtn_Click(object sender, RoutedEventArgs e)

{

    ClearMap();

}

Adding the Geocoding Logic

We can now add the geocoding logic to the MainPage.xaml.cs file. When the user presses the geocode button we will get their input and check that it is a valid string. If it is, we will then create the URL to geocode the query against the Bing Maps REST Location service and then pass this URL to our GetResponse method. Next, we will display all the locations results on the map as pushpins and add a tap event that will trigger the name of the location to be displayed to the user. We will also pass the results to the GeocodeResults ListBox which will list the location results in the side panel. The GeocodeBtn_Click event handler will look like this:

private async void GeocodeBtn_Click(object sender, RoutedEventArgs e)

{

    ClearMap();

 

    string query = GeocodeTbx.Text;

 

    if (!string.IsNullOrWhiteSpace(query))

    {

        //Create the request URL for the Geocoding service

        Uri geocodeRequest = new Uri(

            string.Format("https://dev.virtualearth.net/REST/v1/Locations?q={0}&key={1}",

            query, MyMap.Credentials));

 

        //Make a request and get the response

        Response r = await GetResponse(geocodeRequest);

 

        if (r != null &&

            r.ResourceSets != null &&

            r.ResourceSets.Length > 0 &&

            r.ResourceSets[0].Resources != null &&

            r.ResourceSets[0].Resources.Length > 0)

        {

            LocationCollection locations = new LocationCollection();

 

            int i = 1;

 

            foreach (BingMapsRESTService.Common.JSON.Location l

                     in r.ResourceSets[0].Resources)

            {

                //Get the location of each result

                Bing.Maps.Location location =

                      new Bing.Maps.Location(l.Point.Coordinates[0], l.Point.Coordinates[1]);

 

                //Create a pushpin each location

                Pushpin pin = new Pushpin(){

                    Tag = l.Name,

                    Text = i.ToString()

                };

 

                i++;

 

                //Add a tapped event that will display the name of the location

                pin.Tapped += (s, a) =>

                {

                    var p = s as Pushpin;

                    ShowMessage(p.Tag as string);

                };

 

                //Set the location of the pushpin

                MapLayer.SetPosition(pin, location);

 

                //Add the pushpin to the map

                MyMap.Children.Add(pin);                     

 

                //Add the coordinates of the location to a location collection

                locations.Add(location);

            }

 

            //Set the map view based on the location collection

            MyMap.SetView(new LocationRect(locations));

 

            //Pass the results to the item source of the GeocodeResult ListBox

            GeocodeResults.ItemsSource = r.ResourceSets[0].Resources;

        }

        else

        {

            ShowMessage("No Results found.");

        }

    }

    else

   {

        ShowMessage("Invalid Geocode Input.");

    }

}

Next, we will add a SelectionChanged event to the ListBox. When triggered, it will call a method named GeocodeResultSelected. This method will zoom in to the selected location. The following code will be used for this method:

private void GeocodeResultSelected(object sender, SelectionChangedEventArgs e)

{

    var listBox = sender as ListBox;

 

    if (listBox.SelectedItems.Count > 0)

    {

        //Get the Selected Item

        var item = listBox.Items[listBox.SelectedIndex]

                   as BingMapsRESTService.Common.JSON.Location;

 

        //Get the items location

        Bing.Maps.Location location =

               new Bing.Maps.Location(item.Point.Coordinates[0], item.Point.Coordinates[1]);

 

        //Zoom into the location

        MyMap.SetView(location, 18);

    }

}

If you run the application and do a search for “London” your application should look like this:

wsGeo

Adding the Routing Logic

We can now add the routing logic to the MainPage.xaml.cs file. When the user presses the “Get Directions” button we will first get their input locations and check that they are valid strings. If they are valid, we will create the URL to get the driving route between the two locations and pass this URL to our GetResponse method. Next, we will display the route line on the map along with a pushpin for the start and end points. We will also display the route itinerary items in a ListBox in the side panel. The RouteBtn_Click event handler will look like this:

private async void RouteBtn_Click(object sender, RoutedEventArgs e)

{

    ClearMap();

 

    string from = FromTbx.Text;

    string to = ToTbx.Text;           

 

    if (!string.IsNullOrWhiteSpace(from))

    {

        if (!string.IsNullOrWhiteSpace(to))

        {

            //Create the Request URL for the routing service

            Uri routeRequest = new Uri( string.Format("https://dev.virtualearth.net/REST/V1/Routes/Driving?wp.0={0}&wp.1={1}&rpo=Points&key={2}", from, to, MyMap.Credentials));

                   

            //Make a request and get the response

            Response r = await GetResponse(routeRequest);

 

            if (r != null &&

                r.ResourceSets != null &&

                r.ResourceSets.Length > 0 &&

                r.ResourceSets[0].Resources != null &&

                r.ResourceSets[0].Resources.Length > 0)

            {

                Route route = r.ResourceSets[0].Resources[0] as Route;

 

                //Get the route line data

                double[][] routePath = route.RoutePath.Line.Coordinates;

                LocationCollection locations = new LocationCollection();

 

                for (int i = 0; i < routePath.Length; i++)

                {

                    if (routePath[i].Length >= 2)

                    {

                        locations.Add(new Bing.Maps.Location(routePath[i][0],

                                      routePath[i][1]));

                    }

                }

 

                //Create a MapPolyline of the route and add it to the map

                MapPolyline routeLine = new MapPolyline()

                {

                    Color = Colors.Blue,

                    Locations = locations,

                    Width = 5

                };

 

                routeLayer.Shapes.Add(routeLine);

 

                //Add start and end pushpins

                Pushpin start = new Pushpin()

                {

                    Text = "S",

                    Background = new SolidColorBrush(Colors.Green)

                };

 

                MyMap.Children.Add(start);

                MapLayer.SetPosition(start,

                    new Bing.Maps.Location(route.RouteLegs[0].ActualStart.Coordinates[0],

                        route.RouteLegs[0].ActualStart.Coordinates[1]));

 

                Pushpin end = new Pushpin()

                {

                    Text = "E",

                    Background = new SolidColorBrush(Colors.Red)

                };

 

                MyMap.Children.Add(end);

                MapLayer.SetPosition(end,

                    new Bing.Maps.Location(route.RouteLegs[0].ActualEnd.Coordinates[0],

                    route.RouteLegs[0].ActualEnd.Coordinates[1]));

 

                //Set the map view for the locations

                MyMap.SetView(new LocationRect(locations));

 

                //Pass the route to the Data context of the Route Results panel

                RouteResults.DataContext = route;

            }

            else

            {

                ShowMessage("No Results found.");

            }

        }

        else

        {

            ShowMessage("Invalid 'To' location.");

        }

    }

    else

    {

        ShowMessage("Invalid 'From' location.");

    }

}

At this point, we have accomplished all that we set out to do in this blog post. If you run the application now and calculate a route from “New York” to “Miami” you should end up with something like this:

wsRoute

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional