BingMaps RouteService

25. Mai 2011

Einführung

In meinen ersten beiden Blog-Beiträgen Microsoft Bing Maps for Enterprise und BingMaps GeocodeService, habe ich über die Darstellung von Punkten auf einer BingMaps-Karte gesprochen. Im Gegensatz zu dem ersten Blog-Beitrag wurde im Zweiten der WebService “GeocodeService” mit eingebunden, der sich über den Webservice die Koordinaten des gesuchten Ortes/Punktes holt.

Microsoft bietet aber 3 verschiedene WebServices für die Darstellung von Punkten, Linien oder Bereichen auf Karten an. Diese wären

  1. GeocodeService (Punkte)
  2. RouteService (Punkte, Linien)
  3. SearchService (Punkte)

In diesem Blog Beitrag möchte ich die Verwendung des RouteService in BingMaps erläutern.

Wozu verwende ich den RouteService?

Mit Hilfe des Route Service wird eine Abfrage an den Webservice gestellt, der die Koordinaten (Längen- und Breitengrad im Format WGS84) der Eckpunkte für die zu berechnende Route zurückliefert. Bei diesem RouteService kann die Unterscheidung erfolgen, ob die Route/Strecke mit einem Fahrzeug (z.B. Auto) oder zu Fuß (Wanderstrecke) zurückgelegt werden soll. Des Weiteren kann noch unterschieden werden, ob Autobahnen genutzt oder vermieden werden sollen. Diesbezüglich verändert sich auch der Verlauf der Route/Strecke.

Eine Verwendung des RouteService bietet sich insbesondere für die Berechnung von Routenplanungen für den Güterverkehr an.

Voraussetzungen

  • BingMaps-Key
  • Visual Studio
  • Silverlight

Falls Sie noch keinen BingMaps-Key haben, können Sie sich hier einen erstellen.

Silverlight-Applikation

Als erster Schritt wird eine neue Silverlight-Applikation erstellt und der GeocodeService als Service Reference hinzugefügt. Hierzu klicken Sie mit der rechten Maustaste auf das Projekt und wählen Add Service Reference. Geben Sie als Adresse http://dev.virtualearth.net/webservices/v1/GeocodeService/GeocodeService.svc ein und klicken auf Go. Nennen Sie den Namespace GeocodeService. Wiederholen Sie diesen Schritt mit der Adresse http://dev.virtualearth.net/webservices/v1/RouteService/RouteService.svc. Nennen Sie den Namespace RouteService.

image

Im nächsten Schritt werden zwei Textboxen zum Eingeben der Start- und Zieladresse, zwei RadioButton für die Auswahl des Routenmodus (Auto, Fußgänger) und ein Button zum Starten der Routenberechnung erstellt. Des Weiteren werden noch ein Textblock zum Ausgeben der Entfernung/Distanz und eine Karte zum Darstellen der Suchergebnisse erstellt.

   1: <Grid x:Name="LayoutRoot" Background="White" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">

   2:  <Grid.RowDefinitions>

   3:   <RowDefinition Height="auto" />

   4:   <RowDefinition Height="*" />

   5:  </Grid.RowDefinitions>

   6:  <StackPanel Grid.Row="0">

   7:   <TextBlock Text="Geben Sie den Start- und den Zielpunkt ein!" FontSize="13" TextWrapping="Wrap" />

   8:   <Grid>

   9:    <Grid.RowDefinitions>

  10:     <RowDefinition />

  11:     <RowDefinition />

  12:     <RowDefinition />

  13:     <RowDefinition />

  14:    </Grid.RowDefinitions>

  15:    <Grid.ColumnDefinitions>

  16:     <ColumnDefinition Width="auto" />

  17:     <ColumnDefinition Width="500" />

  18:     <ColumnDefinition Width="auto" />

  19:     <ColumnDefinition Width="auto" />

  20:     <ColumnDefinition Width="auto" />

  21:    </Grid.ColumnDefinitions>

  22:    <TextBlock Text="Start:" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" FontSize="10" />

  23:    <TextBox x:Name="inputStart" Grid.Row="0" Grid.Column="1" KeyDown="input_KeyDown" GotFocus="inputStart_GotFocus" Width="500" Text="Borsigallee 19, Frankfurt am Main" FontSize="13" />

  24:    <TextBlock x:Name="outputStart" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" FontSize="10" Foreground="Black" Margin="14,0,0,0" VerticalAlignment="Center" />

  25:    <TextBlock Text="Ziel:" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" FontSize="10" />

  26:    <TextBox x:Name="inputZiel" Grid.Row="1" Grid.Column="1" KeyDown="input_KeyDown" GotFocus="inputZiel_GotFocus" Width="500" Text="Landwehrstraße 61, München" FontSize="13" />

  27:    <Button Content="Route berechnen" Grid.Row="1" Grid.Column="2" Click="Button_Click" />

  28:    <RadioButton x:Name="rdbDriving" Grid.Row="0" Grid.Column="2" Content="Auto" />

  29:    <RadioButton x:Name="rdbWalking" Grid.Row="0" Grid.Column="3" Content="Fußgänger" />

  30:    <TextBlock x:Name="outputZiel" Grid.Row="1" Grid.Column="3" FontSize="10" Foreground="Black" Margin="14,0,0,0" VerticalAlignment="Center" />

  31:    <TextBlock Text="Entfernung" Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" FontSize="10"/>

  32:    <TextBlock x:Name="outputDistance" Grid.Row="3" Grid.Column="1" FontSize="13" Foreground="Black" FontWeight="Bold" Margin="14,0,0,0" VerticalAlignment="Center" />

  33:   </Grid>

  34:  </StackPanel>

  35:  <StackPanel Grid.Row="1">

  36:   <Grid>

  37:    <Grid.ColumnDefinitions>

  38:     <ColumnDefinition Width="*" />

  39:     <ColumnDefinition Width="auto" />

  40:    </Grid.ColumnDefinitions>

  41:    <map:Map x:Name="routedMap" CredentialsProvider="<Your Key>" Culture="de-de" Grid.Column="0" Margin="0,0,10,0" CopyrightVisibility="Collapsed" ScaleVisibility="Collapsed" NavigationVisibility="Collapsed">

  42:     <map:MapLayer x:Name="RouteLayer" />

  43:     <map:MapLayer x:Name="ContentPopupLayer">

  44:      <Canvas x:Name="ContentPopup" Visibility="Collapsed" Opacity="0.85" MouseEnter="ContentPopup_MouseEnter" MouseLeave="ContentPopup_MouseLeave">

  45:       <Rectangle x:Name="ContentPopupRectange" Fill="White" Canvas.Left="0" Canvas.Top="0" Height="100" Width="300" RadiusX="20" RadiusY="20" />

  46:       <StackPanel Canvas.Left="10" Canvas.Top="10">

  47:        <TextBlock x:Name="ContentPopupText" FontSize="12" FontWeight="Bold" TextWrapping="Wrap" Width="250" />

  48:       </StackPanel>

  49:      </Canvas>

  50:     </map:MapLayer>

  51:    </map:Map>

  52:    <TextBlock x:Name="outputRoute" Grid.Column="1" />

  53:   </Grid>

  54:  </StackPanel>

  55: </Grid>

In den beiden nachfolgenden Funktionen werden die Services GeocodeService und RouteService aufgerufen.

   1: private GeocodeService.GeocodeServiceClient geocodeClient;

   2: private GeocodeService.GeocodeServiceClient GeocodeClient

   3: {

   4:   get

   5:   {

   6:     if (null == geocodeClient)

   7:     {

   8:       //Handle http/https; OutOfBrowser is currently supported on the MapControl only for http pages

   9:       bool httpsUriScheme = !Application.Current.IsRunningOutOfBrowser && HtmlPage.Document.DocumentUri.Scheme.Equals(Uri.UriSchemeHttps);

  10:       BasicHttpBinding binding = new BasicHttpBinding(httpsUriScheme ? BasicHttpSecurityMode.Transport : BasicHttpSecurityMode.None);

  11:       UriBuilder serviceUri = new UriBuilder("http://dev.virtualearth.net/webservices/v1/GeocodeService/GeocodeService.svc");

  12:       if (httpsUriScheme)

  13:       {

  14:         //For https, change the UriSceheme to https and change it to use the default https port.

  15:         serviceUri.Scheme = Uri.UriSchemeHttps;

  16:         serviceUri.Port = -1; 

  17:       }

  18:  

  19:       //Create the Service Client

  20:       geocodeClient = new GeocodeService.GeocodeServiceClient(binding, new EndpointAddress(serviceUri.Uri));

  21:       geocodeClient.GeocodeCompleted += new EventHandler<GeocodeService.GeocodeCompletedEventArgs>(client_GeocodeCompleted);

  22:     }

  23:     return geocodeClient;

  24:   }

  25: }

  26:  

  27: private RouteService.RouteServiceClient routeClient;

  28: private RouteService.RouteServiceClient RouteClient

  29: {

  30:   get

  31:   {

  32:     if (null == routeClient)

  33:     {

  34:       //Handle http/https; OutOfBrowser is currently supported on the MapControl only for http pages

  35:       bool httpsUriScheme = !Application.Current.IsRunningOutOfBrowser && HtmlPage.Document.DocumentUri.Scheme.Equals(Uri.UriSchemeHttps);

  36:       BasicHttpBinding binding = new BasicHttpBinding(httpsUriScheme ? BasicHttpSecurityMode.Transport : BasicHttpSecurityMode.None);

  37:       binding.MaxReceivedMessageSize = int.MaxValue;

  38:       binding.MaxBufferSize = int.MaxValue;

  39:       UriBuilder serviceUri = new UriBuilder("http://dev.virtualearth.net/webservices/v1/RouteService/RouteService.svc");

  40:       if (httpsUriScheme)

  41:       {

  42:         //For https, change the UriSceheme to https and change it to use the default https port.

  43:         serviceUri.Scheme = Uri.UriSchemeHttps;

  44:         serviceUri.Port = -1; 

  45:       }

  46:  

  47:       //Create the Service Client

  48:       routeClient = new RouteService.RouteServiceClient(binding, new EndpointAddress(serviceUri.Uri));

  49:       routeClient.CalculateRouteCompleted += new EventHandler<RouteService.CalculateRouteCompletedEventArgs>(client_RouteCompleted);

  50:     }

  51:     return routeClient;

  52:   }

  53: }

Mit der Funktion GeocodeAddress werden die Koordinaten für die beiden Punkte (Start- und Zieladresse) vom Webservice abgerufen.

   1: private void GeocodeAddress(string address, RoutingState state)

   2: {

   3:   var request = new GeocodeService.GeocodeRequest

   4:   {

   5:     ExecutionOptions = new GeocodeService.ExecutionOptions { SuppressFaults = true },

   6:     Culture = routedMap.Culture,

   7:     Query = address

   8:   };

   9:  

  10:   // Only accept results with high confidence.

  11:   request.Options = new GeocodeService.GeocodeOptions();

  12:   // Using ObservableCollection since this is the default for Silverlight proxy generation.

  13:   request.Options.Filters = new ObservableCollection<GeocodeService.FilterBase>();

  14:   GeocodeService.ConfidenceFilter filter = new GeocodeService.ConfidenceFilter();

  15:   filter.MinimumConfidence = GeocodeService.Confidence.High;

  16:   request.Options.Filters.Add(filter);

  17:  

  18:   if (null != state.output)

  19:   {

  20:     state.output.Text = "<Berechnung wird durchgeführt>";

  21:     state.output.Foreground = new SolidColorBrush(Colors.Black);

  22:   }

  23:  

  24:   routedMap.CredentialsProvider.GetCredentials(

  25:     (Credentials credentials) =>

  26:   {

  27:     // Pass in credentials for web services call. Replace with your own Credentials.

  28:     request.Credentials = credentials;

  29:  

  30:     // Make asynchronous call to fetch the data ... pass state object.

  31:     GeocodeClient.GeocodeAsync(request, state);

  32:   });

  33: }

   1: private object lockObject = new object();

   2: private void client_GeocodeCompleted(object sender, GeocodeService.GeocodeCompletedEventArgs e)

   3: {

   4:   RoutingState state = e.UserState as RoutingState;

   5:   GeocodeService.GeocodeResult result = null;

   6:   string outString;

   7:  

   8:   try

   9:   {

  10:     if (e.Result.ResponseSummary.StatusCode != GeocodeService.ResponseStatusCode.Success)

  11:     {

  12:       outString = "Fehler bei der Berechnung ... Status <" + e.Result.ResponseSummary.StatusCode.ToString() + ">";

  13:     }

  14:     else if (0 == e.Result.Results.Count)

  15:     {

  16:       outString = "Kein Ergebnis";

  17:     }

  18:     else

  19:     {

  20:       // Only report on first result.

  21:       result = e.Result.Results[0];

  22:       outString = result.DisplayName;

  23:     }

  24:   }

  25:   catch (Exception)

  26:   {

  27:     outString = "Exception raised";

  28:   }

  29:           

  30:   // Update UI with geocode result.

  31:   if (null != state.output)

  32:   {

  33:     state.output.Text = outString;

  34:   }

  35:  

  36:   if (null == result)

  37:   {

  38:     result = new GeocodeService.GeocodeResult();

  39:   }

  40:  

  41:   // Update state object ... when all the results are set, call route.

  42:   bool doneGeocoding;

  43:   lock (lockObject)

  44:   {

  45:     state.results[state.locationNumber] = result;

  46:     doneGeocoding = state.GeocodesComplete;

  47:   }

  48:  

  49:   if (doneGeocoding && state.GeocodesSuccessful)

  50:   {

  51:     //Clear any existing routes

  52:     ClearRoute();

  53:  

  54:     //Calculate the route

  55:     CalculateRoute(state.results);

  56:   }

  57: }

   1: private RouteService.Waypoint GeocodeResultToWaypoint(GeocodeService.GeocodeResult result)

   2: {

   3:   var waypoint = new RouteService.Waypoint

   4:   {

   5:     Description = result.DisplayName,

   6:     Location = new Location { Latitude = result.Locations[0].Latitude, Longitude = result.Locations[0].Longitude }

   7:   };

   8:   return waypoint;

   9: }

Die beiden nachfolgenden Funktionen CalculateRoute und client_RouteCompleted, die wir nun erstellen, berechnen die Fahr- bzw. Laufstrecke der Route und geben entscheidende Eckpunkte (z.B. hier abbiegen, geradeaus fahren, usw…) zurück. Diese beinhalten die Koordinaten sowie Anweisungen.

   1: private void CalculateRoute(GeocodeService.GeocodeResult[] locations)

   2: {

   3:   var request = new RouteService.RouteRequest

   4:   {

   5:     Culture = routedMap.Culture,

   6:     Waypoints = new ObservableCollection<RouteService.Waypoint>(),

   7:     ExecutionOptions = new RouteService.ExecutionOptions { SuppressFaults = true },

   8:     Options = new RouteService.RouteOptions { RoutePathType = RouteService.RoutePathType.Points }

   9:   };

  10:  

  11:   foreach (GeocodeService.GeocodeResult result in locations)

  12:   {

  13:     request.Waypoints.Add(GeocodeResultToWaypoint(result));

  14:   }

  15:  

  16:   //Wenn “Auto” ausgewählt ist

  17:   if (rdbDriving.IsChecked == true)

  18:   {

  19:     request.Options.Mode = RouteService.TravelMode.Driving;

  20:   }

  21:   //Wenn “Fußgänger” ausgewählt ist

  22:   else if (rdbWalking.IsChecked == true)

  23:   {

  24:     request.Options.Mode = RouteService.TravelMode.Walking;

  25:   }

  26:   //Wenn weder “Auto” noch “Fußgänger” ausgewählt ist, dann gib eine Infobox aus

  27:   else

  28:   {

  29:     MessageBox.Show("Bitte wählen Sie Auto oder Fußgänger aus!");

  30:   }

  31:  

  32:   // Now that both locations were found ... use "to" for routing update.

  33:   outputStart.Text += " --> " + outputZiel.Text;

  34:   outputZiel.Text += " ... <routing>";

  35:  

  36:   routedMap.CredentialsProvider.GetCredentials(

  37:     (Credentials credentials) =>

  38:   {

  39:     //Pass in credentials for web services call. Replace with your own Credentials.

  40:     request.Credentials = credentials;

  41:                 

  42:     // Make asynchronous call to fetch the data ... pass state object.

  43:     RouteClient.CalculateRouteAsync(request);

  44:   });

  45: }

   1: private void client_RouteCompleted(object sender, RouteService.CalculateRouteCompletedEventArgs e)

   2: {

   3:   string outString;

   4:   try

   5:   {

   6:     if (e.Result.ResponseSummary.StatusCode != RouteService.ResponseStatusCode.Success)

   7:     {

   8:       outString = "Fehler bei der Berechnung ... Status <" + e.Result.ResponseSummary.StatusCode.ToString() + ">";

   9:     }

  10:     else if (0 == e.Result.Result.Legs.Count)

  11:     {

  12:       outString = "Kann keine Route berechnen";

  13:     }

  14:     else

  15:     {

  16:       double distance = e.Result.Result.Summary.Distance;

  17:       outputDistance.Text = distance.ToString() + " km";

  18:                     

  19:       Color routeColor = Colors.Blue;

  20:       SolidColorBrush routeBrush = new SolidColorBrush(routeColor);

  21:       outString = "Route berechnet";

  22:       outputZiel.Foreground = routeBrush;

  23:  

  24:       var routeLine = new MapPolyline

  25:       {

  26:         Stroke = routeBrush,

  27:         Opacity = 0.65,

  28:         StrokeThickness = 5.0

  29:       };

  30:  

  31:       routeLine.Locations = new LocationCollection();

  32:       foreach (Location p in e.Result.Result.RoutePath.Points)

  33:       {

  34:         routeLine.Locations.Add(new Location(p.Latitude, p.Longitude));

  35:       }

  36:       RouteLayer.Children.Add(routeLine);

  37:       LocationRect rect = new LocationRect(routeLine.Locations[0], routeLine.Locations[routeLine.Locations.Count - 1]);

  38:                                   

  39:       int i = 1;

  40:       int anz = e.Result.Result.Legs[0].Itinerary.Count;

  41:       foreach (RouteService.ItineraryItem itineraryItem in e.Result.Result.Legs[0].Itinerary)

  42:       {

  43:         var point = new Ellipse

  44:         {

  45:           Width = 10,

  46:           Height = 10,

  47:           DataContext = i,

  48:           Opacity = 0.65,

  49:           Tag = itineraryItem

  50:         };

  51:  

  52:         if (i == 1) 

  53:         { 

  54:           point.Fill = new SolidColorBrush(Colors.Green);

  55:           point.Width = 20;

  56:           point.Height = 20;

  57:         }else if(i == anz)

  58:         {

  59:           point.Fill = new SolidColorBrush(Colors.Red);

  60:           point.Width = 20;

  61:           point.Height = 20;

  62:         }

  63:         else

  64:         {

  65:           point.Fill = new SolidColorBrush(Colors.Yellow);

  66:           point.Width = 15;

  67:           point.Height = 15;

  68:         }

  69:         Location location = new Location(itineraryItem.Location.Latitude, itineraryItem.Location.Longitude);

  70:         MapLayer.SetPosition(point, location);

  71:         MapLayer.SetPositionOrigin(point, PositionOrigin.Center);

  72:  

  73:         RouteLayer.Children.Add(point);

  74:         i++;

  75:       }

  76:       routedMap.SetView(rect);

  77:     }

  78:   }

  79:   catch (Exception)

  80:   {

  81:     outString = "Exception raised routine";

  82:   }

  83:   outputZiel.Text = outString;

  84: }

   1: private void GetRoute()

   2: {

   3:   bool errorFound = false;

   4:  

   5:   if (string.IsNullOrEmpty(inputZiel.Text))

   6:   {

   7:     outputZiel.Text = "Bitte eine Zieladresse eingeben!";

   8:     errorFound = true;

   9:   }

  10:   if (string.IsNullOrEmpty(inputStart.Text))

  11:   {

  12:     outputStart.Text = "Bitte eine Startadresse eingeben!";

  13:     errorFound = true;

  14:   }

  15:   if (errorFound)

  16:   {

  17:     return;

  18:   }

  19:  

  20:   // Geocode locations in parallel.

  21:   GeocodeService.GeocodeResult[] results = new GeocodeService.GeocodeResult[2];

  22:   // From location.

  23:   RoutingState state0 = new RoutingState(results, 0, outputStart);

  24:   GeocodeAddress(inputStart.Text, state0);

  25:   // To location.

  26:   RoutingState state1 = new RoutingState(results, 1, outputZiel);

  27:   GeocodeAddress(inputZiel.Text, state1);

  28: }

   1: internal class RoutingState

   2: {

   3:   internal RoutingState(GeocodeService.GeocodeResult[] resultArray, int index, TextBlock tb)

   4:   {

   5:     results = resultArray;

   6:     locationNumber = index;

   7:     output = tb;

   8:   }

   9:  

  10:   internal bool GeocodesComplete

  11:   {

  12:     get

  13:     {

  14:       for (int idx = 0; idx < results.Length; idx++)

  15:       {

  16:         if (null == results[idx])

  17:         return false;

  18:       }

  19:       return true;

  20:     }

  21:   }

  22:  

  23:   internal bool GeocodesSuccessful

  24:   {

  25:     get

  26:     {

  27:       for (int idx = 0; idx < results.Length; idx++)

  28:       {

  29:         if (null == results[idx] || null == results[idx].Locations || 0 == results[idx].Locations.Count)

  30:         {

  31:           return false;

  32:         }

  33:       }

  34:       return true;

  35:     }

  36:   }

  37:  

  38:   internal GeocodeService.GeocodeResult[] results;

  39:   internal int locationNumber;

  40:   internal TextBlock output;

  41: }

Abschließend muss noch eine Methode für den Button Route berechnen geschrieben werden, der die Berechnung der Route startet.

   1: private void Button_Click(object sender, RoutedEventArgs e)

   2: {

   3:   GetRoute();

   4: }

   5:  

   6: private void inputZiel_GotFocus(object sender, RoutedEventArgs e)

   7: {

   8:   inputZiel.SelectAll();

   9: }

  10:  

  11: private void inputStart_GotFocus(object sender, RoutedEventArgs e)

  12: {

  13:   inputStart.SelectAll();

  14: }

  15:  

  16: private void input_KeyDown(object sender, KeyEventArgs e)

  17: {

  18:   if (e.Key == Key.Enter)

  19:   {

  20:     GetRoute();

  21:   }

  22: }

Ergebnis

Starten Sie nun die Solution und geben Sie eine Start- und eine Zieladresse ein. Klicken Sie anschließend auf Route berechnen um den RouteService anzusprechen.

In dem folgenden Screenshot kann man erkennen, dass nachdem ich Start- und Zieladresse eingeben, Streckenmodus (hier: Auto) ausgewählt und auf den Button Route berechnen gedrückt habe, oberhalb der Karte die Entfernung und in der Karte mehrere verschieden farbige Punkte erstellt wurden. Hierbei wird der Startpunkt grün, der Endpunkt rot und die Zwischenpunkte gelb dargestellt. Wenn man mit dem Mauszeiger auf einen Punkt geht, werden der Name und die Beschreibung des Punktes angezeigt.

Rechts neben der Karte kann man den Verlauf der Strecke ablesen.

image

In meinem nächsten Blog-Beitrag werde ich die Verwendung des SearchService in BingMaps erläutern.