Share via


Exercise 3: Calculating a Route

In this exercise we will:

  • Create a map route layer
  • Use Bing services to calculate to and from routes
  • Display calculated route itineraries

We will use the Microsoft Visual Studio 2010 Express for Windows Phone development environment, and will deploy to the Windows Phone Emulator for debugging. The solution we will be working with is based upon the previous exercise.

Task 1 – Adding a Route to the Map

In this task you’ll work with Bing Maps SOAP services to calculate a route from point A to point B, and then you’ll create a route layer for rendering the calculated route. As part of the route rendering, you’ll also render route itineraries provided by the Bing Maps services.

In this task you’ll use the following services:

  • Geocode Service – to match addresses, places, and geographic entities to latitude and longitude coordinates on the map, as well as return location information for a specified latitude and longitude coordinate
  • Route Service – to generate routes and driving directions based on locations or waypoints, for example, directions that include traffic warnings and route hints between multiple locations, and directions from all major roads to a destination (1-click directions, also referred to as a "party map")
  • Search Service – to parse a search query that contains a location or keyword (or both) and return search result
  1. Open the starter solution located in the Source\Ex3-RouteCalculation\Begin folder.

    Note:
    You should set the Bing Maps application Id in the App class with your private key created in the previous exercise.

  2. Add a reference to the Bing Maps Geocode service, right click on the project references, and select “Add Service Reference…”. Paste the following service address into the opened dialog, and click Go: https://dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc
  3. In the “Namespace” field, make sure that you enter Bing.Geocode and then click OK.

    Note:
    If you have problems generating the data from the service, please make sure that you are currently connected to the Internet. Try to paste the service address into the web browser; you should get a service test page. If you have a connection, try to run Visual Studio 2010 again as Administrator (right click on Visual Studio 2010 icon on start menu, select “Run as administrator”), then try again.

  4. Add reference to the Bing Maps Route service, right-click the project references, and select “Add Service Reference…”. Paste the following service address into the opened dialog, and click Go: https://dev.virtualearth.net/webservices/v1/routeservice/routeservice.svc. In the “Namespace” field, make sure that you enter Bing.Route and then click OK.
  5. Add a reference to the Bing Maps Search service, right-click the project references, and select “Add Service Reference…”. Paste the following service address into the opened dialog, and click Go: https://dev.virtualearth.net/webservices/v1/searchservice/searchservice.svc. In the “Namespace” field, make sure that you enter Bing.Search and then click OK.
  6. Open the ServiceReferences.ClientConfig configuration file that was automatically created as a project item and delete all CustomBinding related entries. In this lab we will use only the basic http bindings. You should have a file that looks like this:

    XAML

    <configuration> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IGeocodeService" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647"> <security mode="None" /> </binding> <binding name="BasicHttpBinding_IRouteService" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647"> <security mode="None" /> </binding> <binding name="BasicHttpBinding_ISearchService" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647"> <security mode="None" /> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="https://dev.virtualearth.net/webservices/v1/geocodeservice/GeocodeService.svc" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IGeocodeService" contract="Bing.Geocode.IGeocodeService" name="BasicHttpBinding_IGeocodeService" /> <endpoint address="https://dev.virtualearth.net/webservices/v1/routeservice/routeservice.svc" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IRouteService" contract="Bing.Route.IRouteService" name="BasicHttpBinding_IRouteService" /> <endpoint address="https://dev.virtualearth.net/webservices/v1/searchservice/searchservice.svc" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ISearchService" contract="Bing.Search.ISearchService" name="BasicHttpBinding_ISearchService" /> </client> </system.serviceModel> </configuration>
    Note:
    You’ve now added three Bing Maps services. Next you’ll add a helper class that uses these services to wrap part of the asynchronous calls for calculating a route.

  7. Pay attention to the files under Helpers project: RouteCalculator.cs, RoutingState.cs, RouteCalculationError.cs. These files will help you calculate a route using the Bing Maps Services you’ve added. The RouteCalculator class exposes one public method called CalculateAsync and one public event called Error. To calculate a route all you have to do is to initialize a new instance of the RouteCalculator type with the following parameter: Map credentials, To string, From string, UI dispatcher, and a delegate that will be called on the UI thread when calculation is done. If an error occurs, the Error event is raised on the UI thread. This is why you must pass the UI dispatcher.
  8. In the RouteModel.cs file under Models folder, create a constructor with one collection parameter of type Microsoft.Phone.Controls.Maps.Platform.Location. Use it to initialize the internal collection. Location has an implicit cast to the GeoCoordinate type.

    C#

    public class RouteModel { private readonly LocationCollection _locations; public ICollection<GeoCoordinate> Locations { get { return _locations; } } public RouteModel(ICollection<Location> locations) { _locations = new LocationCollection(); foreach (Location location in locations) { _locations.Add(location); } } }

  9. In the MainPage.xaml.cs, add a new ObservableCollection<RouteModel> and expose it with a public property called Routes. This collection holds the calculated route and will be bound with the route layer.

    C#

    private readonly ObservableCollection<RouteModel> _routes = new ObservableCollection<RouteModel>(); public ObservableCollection<RouteModel> Routes { get { return _routes; } }

  10. To calculate a route asynchronously, create an instance of RouteCalculator inside CalculateRoute method. So far you should have all the required parameters as properties of the MainPage class, except for the last one which will be an empty lambda expression with a single ‘result’ parameter.

    C#

    try { var routeCalculator = new RouteCalculator( CredentialsProvider, To, From, Dispatcher, result => { }); } …

  11. Register the RouteCalculator.Error event by displaying an error message to the user and then call the RouteCalculator.CalculateAsync method to start the route calculation.

    C#

    try { var routeCalculator = new RouteCalculator( CredentialsProvider, To, From, Dispatcher, result => { }); // Display an error message in case of fault. routeCalculator.Error += r => MessageBox.Show(r.Reason); // Start the route calculation asynchronously. routeCalculator.CalculateAsync(); } …

  12. When the route calculation is finished, the lambda expression should be invoked. Inside the lambda expression, clear the Route collection. For the simplicity, you’ll have only one route at a time.

    C#

    result => { Routes.Clear(); };

  13. Below, create a new RouteModel instance based on route calculator parameter (result.Result.RoutePath.Points), and add the new route to the route collection.

    C#

    var routeModel = new RouteModel(result.Result.RoutePath.Points); Routes.Add(routeModel);

  14. Then, center the map on the new route, by calling Map.SetView, and passing it a LocationRect instance which can be calculated using the LocationRect.CreateLocationRect static helper method.

    C#

    var viewRect = LocationRect.CreateLocationRect(routeModel.Locations); Map.SetView(viewRect);

  15. To create a route layer, add another MapItemsControl to the map control just before the pushpins layer, and bind it to the Routes property.

    XAML

    <my:MapItemsControl ItemsSource="{Binding Routes}"> <my:MapItemsControl.ItemTemplate> <DataTemplate> </DataTemplate> </my:MapItemsControl.ItemTemplate> </my:MapItemsControl>

  16. To draw the route as a single line on the map, set the MapItemsControl.ItemTemplate with a data template that contains a single MapPolyline instance.

    XAML

    <my:MapItemsControl.ItemTemplate> <DataTemplate> <my:MapPolyline Stroke="#FF2C76B7" Opacity="0.85" StrokeThickness="6" /> </DataTemplate> </my:MapItemsControl.ItemTemplate>

  17. Bind the MapPolyline instance with the RouteModel.Locations property.

    XAML

    <my:MapPolyline Stroke="#FF2C76B7" Opacity="0.85" StrokeThickness="6" Locations="{Binding Locations}" />

  18. To test the application:

    • Press F5 and then click on route icon on the application bar (the upward-facing arrow).The route view should slide down from the top.
    • Write a valid address in the From and To fields. For example: “Microsoft Building 1, WA,” “Microsoft Building 9, WA.”
    • Click the search icon. This should calculate the route and you should see it on the map. If an error occurs, you should see an error message.

    Figure 12

    Calculating a route

Task 2 – Displaying Route Itineraries

The Bing route service supplies additional data with the route data, namely, the route itineraries data. By using the itineraries data you can display the exact directions of how to walk or drive from point A to point B on the route. In this task you’ll create a directions list based on these itineraries.

  1. In the MainPage.xaml.cs, add a new collection of type ItineraryItem, and expose it as public property called Itineraries. This type was auto generated as part of the Bing Route service data contracts. You’ll use this class to encapsulate the itineraries collection provided with the route.

    C#

    private readonly ObservableCollection<ItineraryItem> _itineraries = new ObservableCollection<ItineraryItem>(); public ObservableCollection<ItineraryItem> Itineraries { get { return _itineraries; } }

  2. Find the CalculateRoute method and clear both the Routes collection and the Itineraries collection. Since you’re displaying only one route at a time you’ll provide the itineraries of that active route.

    C#

    result => { Routes.Clear(); Itineraries.Clear(); ... }

  3. After adding the new route to the collection of routes, add all the itineraries provided by result.Result.Legs[0] right.

    C#

    var routeModel = new RouteModel(result.Result.RoutePath.Points);
    FakePre-4a97b721660b4f3092691ef74e9dcde7-e9434bfcc5424959b54b3a3bdadf7bd5FakePre-207d60abcc6d4b4e9b4f6184cdcabb30-bf192425694f4015a2e139608978d14dFakePre-119f41e854344eada63657c9913f79cc-ae06e3d389ab4caca93ac0930eae8f61FakePre-ca03db8754314b9a835c5afbc0af1c35-129633401fd849ef8cc6a404f88baf12FakePre-f13d97a3f9044700801b8e1183aa8f81-ce3e00a61dc248e4b6d0d66fb8529b1bFakePre-f569277615b24b339b294460a84bbc03-daa3356b271c486cbd53e773be9a5275

  4. In the MainPage.xaml, use the MapItemsControl to add a new layer for the itineraries, right below the routed layer, and bind it with the Itineraries collection.

    XAML

    <my:MapItemsControl ItemsSource="{Binding Itineraries}"> </my:MapItemsControl>

  5. To display each Itinerary as a custom pushpin, set the MapItemsControl.ItemTemplate with a new data template that contains a single pushpin.

    XAML

    <my:MapItemsControl ItemsSource="{Binding Itineraries}"> <my:MapItemsControl.ItemTemplate> <DataTemplate> <my:Pushpin /> </DataTemplate> </my:MapItemsControl.ItemTemplate> </my:MapItemsControl>

  6. Bind the pushpin with the ItineraryItem.Location property.

    XAML

    <my:Pushpin Location="{Binding Location}" />

  7. Create an instance of the value converter inside the DefaultStyles.xaml resource dictionary located in the Resources\Styles folder.

    XAML

    <ResourceDictionary ... xmlns:converters="clr-namespace:UsingBingMaps.Converters" mc:Ignorable="d"> <converters:LocationGeoCoordinateConverter x:Key="LocationGeoCoordinateConverter" /> ... </ResourceDictionary>

  8. Use the converter inside the binding extension.

    XAML

    <my:Pushpin Location="{Binding Location, Converter={StaticResource LocationGeoCoordinateConverter}}" />

  9. Create a style for displaying the itinerary pushpin as a rounded shape and place it inside the DefaultStyle.xaml resource dictionary located in the Resources\Styles folder.

    XAML

    <Style x:Key="MapPoint" TargetType="Ellipse"> <Setter Property="Width" Value="18"/> <Setter Property="Height" Value="18"/> <Setter Property="Fill" Value="#FF003664"/> <Setter Property="Stroke" Value="AliceBlue"/> </Style> <Style x:Key="ItineraryPushpinStyle" TargetType="m:Pushpin"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="m:Pushpin"> <Grid Height="20" Width="20"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="VisualStateGroup"> <VisualStateGroup.Transitions> <VisualTransition GeneratedDuration="0:0:0.1"> <VisualTransition.GeneratedEasingFunction> <PowerEase EasingMode="EaseIn"/> </VisualTransition.GeneratedEasingFunction> </VisualTransition> <VisualTransition GeneratedDuration="0:0:0.1" To="Selected"> <VisualTransition.GeneratedEasingFunction> <PowerEase EasingMode="EaseIn"/> </VisualTransition.GeneratedEasingFunction> </VisualTransition> </VisualStateGroup.Transitions> <VisualState x:Name="UnSelected"/> <VisualState x:Name="Selected"> <Storyboard> <ColorAnimation Duration="0" To="White" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="ellipse" d:IsOptimized="True"/> <DoubleAnimation Duration="0" To="1.3" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="ellipse" d:IsOptimized="True"/> <DoubleAnimation Duration="0" To="1.3" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)" Storyboard.TargetName="ellipse" d:IsOptimized="True"/> <ColorAnimation Duration="0" To="#FFF08609" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="ellipse_Center" d:IsOptimized="True"/> <DoubleAnimation Duration="0" To="1.5" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="ellipse_Center" d:IsOptimized="True"/> <DoubleAnimation Duration="0" To="1.5" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)" Storyboard.TargetName="ellipse_Center" d:IsOptimized="True"/> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Ellipse x:Name="ellipse" Style="{StaticResource MapPoint}" Width="20" Height="20" RenderTransformOrigin="0.5,0.5" Fill="White" Stroke="#FF2C76B7" StrokeThickness="3" > <Ellipse.RenderTransform> <CompositeTransform/> </Ellipse.RenderTransform> </Ellipse> <Ellipse x:Name="ellipse_Center" Style="{StaticResource MapPoint}" Width="8" Height="8" RenderTransformOrigin="0.5,0.5" Fill="Black" Stroke="{x:Null}" StrokeThickness="2" > <Ellipse.RenderTransform> <CompositeTransform/> </Ellipse.RenderTransform> </Ellipse> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>

  10. Change the pushpin’s style to the new style you’ve just created.

    XAML

    <my:Pushpin Location="{Binding Location, Converter={StaticResource LocationGeoCoordinateConverter}}" Style="{StaticResource ItineraryPushpinStyle}" />

  11. In the DefaultStyles.xaml, add an instance of the converter you’ve created.

    XAML

    <ResourceDictionary ... xmlns:converters="clr-namespace:UsingBingMaps.Converters" ...> <converters:ItineraryTextConverter x:Key="ItineraryTextConverter" /> ... </ResourceDictionary>

  12. To test the results, press F5 and calculate a valid route.

    Figure 13

    Route with Itineraries

  13. To display the itinerary view right after a route was found, in the MainPage.xaml.cs search the HasDirections property and return true if the Itinerary collection is not empty.

    C#

    public bool HasDirections { get { return Itineraries.Count > 0; } }

  14. In the CalculateRoute method, call the ShowDirectionsView inside the lambda body, right after setting the map view.

    C#

    // Set the map to center on the new route. var viewRect = LocationRect.CreateLocationRect(routeModel.Locations); Map.SetView(viewRect); ShowDirectionsView();

  15. To simplify testing, search for the InitializeDefaults, and set the following defaults:

    C#

    private void InitializeDefaults() { Zoom = DefaultZoomLevel; Center = DefaultLocation; From = "Microsoft Building 1, WA"; To = "Microsoft Building 9, WA"; }

  16. To test the final application, press F5 and search for a valid route. The directions view should pop up automatically.

    Figure 14

    Bing Maps Final

    This concludes the exercise and the lab.

    Note:
    The complete solution for this exercise is provided at the following location: Source\Ex3-RouteCalculation\End.