Ludovic ROLAND

Blog technique sur mes expériences de développeur.

Windows Phone 7 : Des itinéraires dans vos applications !

19 février 2013

Dans ce nouvel article sur Windows Phone 7, je vous propose de voir assemble comment afficher des itinéraires dans vos applications grâce aux services Microsoft.

Ce que nous allons produire

L’application que nous allons produire sera constituée d’un pivot et de trois items :

  • un écran permettant de saisir les lieux de départ et d’arrivée ;
  • un écran affichant l’itinéraire sur la carte ;
  • un écran affichant le détail de l’itinéraire.

L’écran de saisie des lieux de départ de d’arrivée

L’écran de saisie est assez simple puisqu’il consiste à simplement afficher deux champs de saisie et un bouton :

<controls:PivotItem Header="Ecran de saisie">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="70"/>
            <RowDefinition Height="70"/>
            <RowDefinition Height="70"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <TextBlock Grid.Row="0" Grid.Column="0" Margin="10,0,0,0" Text="Départ : " VerticalAlignment="Center" />
        <TextBox Grid.Row="0" Grid.Column="1" Width="360" Name="txtDepart" />

        <TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" Margin="10,0,0,0" Text="Arrivée :" VerticalAlignment="Center" />
        <TextBox Grid.Row="1" Grid.Column="1" Width="360" Name="txtArrivee" />

        <Button Grid.Row="2" Grid.ColumnSpan="2" Name="btnRecherche" Content="Rechercher" Click="btnRecherche_Click" />
    </Grid>
</controls:PivotItem>

Le code behind quant à lui consiste simplement à vérifier que les champs ne sont pas vide avant de lancer la recherche :

private void btnRecherche_Click(object sender, RoutedEventArgs e)
{
    if (txtDepart.Text == "")
    {
        MessageBox.Show("Merci de saisir un lieu de départ");
    }
    else if (txtArrivee.Text == "")
    {
        MessageBox.Show("Merci de saisir un lieu d'arrivée");
    }
    else
    {
        //On lance la recherche
    }
}

L’écran d’affichage de la carte

Avant de faire la recherche, je vous propose de mettre en place l’écran qui accueillera la carte sur laquelle l’itinéraire sera tracée. Il s’agit d’une simple carte Bing. Pour plus d’information sur la carte et comment récupérer une clef, vous pouvez lire cet article.

<controls:PivotItem Header="Carte">
    <Grid>
        <my:Map HorizontalAlignment="Left" Name="mapItineraire" VerticalAlignment="Top" Width="455" Height="600" CopyrightVisibility="Collapsed" LogoVisibility="Collapsed" ZoomLevel="10" ZoomBarVisibility="Visible">
            <my:Map.CredentialsProvider>
                <my:ApplicationIdCredentialsProvider ApplicationId="Au..." />
            </my:Map.CredentialsProvider>
            <my:Map.Center>
                <my1:GeoCoordinate Altitude="NaN" Course="NaN" HorizontalAccuracy="NaN" Latitude="47.64483" Longitude="-122.141197" Speed="NaN" VerticalAccuracy="NaN" />
            </my:Map.Center>
        </my:Map>
    </Grid>
</controls:PivotItem>

Calcul de l’itinéraire

Le calcul de l’itinéraire va se faire en trois étapes :

  • recherche des coordonnées GPS du point de départ ;
  • recherche des coordonnées GPS du point d’arrivée ;
  • recherche de l’itinéraire.

L’ajout des services

Pour récupérer les coordonnées GPS des points de départ et d’arrivée, nous allons utiliser le service Microsoft Geocode tandis que le service Route sera utilisé pour le calcul de l’itinéraire à proprement parlé.

Pour pouvoir les utiliser, nous devons ajouter des références à ces services dans notre projet. Pour ce faire, rendez-vous dans le menu Projet puis Ajouter une référence de service. La fenêtre suivante doit alors s’afficher à l’écran :

Le service Geocode

Pour le service Geocode voici les informations à saisir :

  • Adresse : http://dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc
  • Espace de nom : GeocodeService

A noter que c’est vous qui décidez de l’espace de nom !

Le service Route

Pour le service Routevoici les informations à saisir :

  • Adresse : http//dev.virtualearth.net/webservices/v1/routeservice/routeservice.svc
  • Espace de nom : RouteService

Récupérer les coordonnées GPS du point de départ

Nous allons avoir besoin de quatre attributs faisant référence au service Geocode, à la requête au service, ainsi que deux attributs qui stockeront les coordonnées GPS des points de départ et d’arrivée :

private GeocodeServiceClient geocodeService;
private GeocodeRequest geocodeReq;
private GeocodeResult geocodeResDepart;
private GeocodeResult geocodeResArrivee;

Il convient maintenant de les** instancier** dans la méthode OnNavigatedTo :

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    //On instancie le service
    geocodeService = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService");
    geocodeService.GeocodeCompleted += geocodeService_GeocodeCompleted;

    //On instancie la requête
    geocodeReq = new GeocodeRequest();
    geocodeReq.Credentials = new Credentials() { ApplicationId = "As..." };
}

A noter que l’applicationId est la clef de votre carte Bing.

Il est maintenant temps de lancer la requête. Pour ça, nous allons mettre à jour la méthode btnRecherche_click :

private void btnRecherche_Click(object sender, RoutedEventArgs e)
{
    if (txtDepart.Text == "")
    {
        MessageBox.Show("Merci de saisir un lieu de départ");
    }
    else if (txtArrivee.Text == "")
    {
        MessageBox.Show("Merci de saisir un lieu d'arrivée");
    }
    else
    {
        //On indique que l'application tourne
        progressIndicator.IsVisible = true;

        //On lance la recherche
        geocodeReq.Query = txtDepart.Text;
        geocodeService.GeocodeAsync(geocodeReq);
    }
}

Une fois que la recherche est terminée, c’est la méthode geocodeService_GeocodeCompleted qui est appelée comme nous l’avons indiqué pendant l’instanciation de notre objet.

Voici le détail de la méthode :

private void geocodeService_GeocodeCompleted(object sender, GeocodeCompletedEventArgs e)
{
    if (e.Result != null && e.Result.Results.Any(x => x.Locations != null && x.Locations.Any()))
    {
        geocodeResDepart = e.Result.Results.FirstOrDefault();
    }
    else
    {
        MessageBox.Show("Impossible de localiser le départ");
    }
}

Récupérer les coordonnées GPS du point d’arrivée

Nous allons maintenant procéder à la récupération des coordonnées GPS du point d’arrivée. Nous allons lancer cette recherche uniquement en cas de réussite de la récupération des coordonnées GPS du point de départ.

Cette recherche va donc se faire dans la méthode geocodeService_GeocodeCompleted :

private void geocodeService_GeocodeCompleted(object sender, GeocodeCompletedEventArgs e)
{
    if (e.Result != null && e.Result.Results.Any(x => x.Locations != null && x.Locations.Any()))
    {
        //On récupère les coordonnées du point de départ
        geocodeResDepart = e.Result.Results.FirstOrDefault();

        //On lance la recherche pour le point d'arrivée
        geocodeReq.Query = txtArrivee.Text;
        geocodeService.GeocodeAsync(geocodeReq);
    }
    else
    {
        MessageBox.Show("Impossible de localiser le départ");
    }
}

Puisque cette méthode est encore appelée une fois le résultat trouvé, il convient de la modifier encore pour différencier son appel pour les coordonnées du point de départ ou d’arrivée. Pour se faire, nous allons créer un attribut qui nous permettra de savoir où nous en sommes.

Déclaration de l’attribut :

private string query;

Modification de la méthode btnRecherche_click :

private void btnRecherche_Click(object sender, RoutedEventArgs e)
{
    if (txtDepart.Text == "")
    {
        MessageBox.Show("Merci de saisir un lieu de départ");
    }
    else if (txtArrivee.Text == "")
    {
        MessageBox.Show("Merci de saisir un lieu d'arrivée");
    }
    else
    {
        //On indique que l'application tourne
        progressIndicator.IsVisible = true;

        //On indique le type de la requête
        query = "depart";

        //On lance la recherche
        geocodeReq.Query = txtDepart.Text;
        geocodeService.GeocodeAsync(geocodeReq);
    }
}

On modifie maintenant la méthode geocodeService_GeocodeCompleted :

private void geocodeService_GeocodeCompleted(object sender, GeocodeCompletedEventArgs e)
{
    if (e.Result != null && e.Result.Results.Any(x => x.Locations != null && x.Locations.Any()))
    {
        if (query == "depart")
        {
            //On récupère les coordonnées du point de départ
            geocodeResDepart = e.Result.Results.FirstOrDefault();

            //On indique le type de la nouvelle requête
            query = "arrivee";

            //On lance la recherche pour le point d'arrivée
            geocodeReq.Query = txtArrivee.Text;
            geocodeService.GeocodeAsync(geocodeReq);
        }
        else
        {
            //On récupère les coordonnées du point d'arrivée
            geocodeResArrivee = e.Result.Results.FirstOrDefault();
        } 
    }
    else
    {
        if (query == "depart")
        {
            MessageBox.Show("Impossible de localiser le départ");
        }
        else
        {
            MessageBox.Show("Impossible de localiser l'arrivée");
        }
    }
}

Le calcul de l’itinéraire

Maintenant que nous connaissons les coordonnées de nos points de départ et d’arrivée, nous allons procéder au calcul de l’itinéraire. Pour se faire, commencez par ajouter l’attribut suivant :

private RouteServiceClient routeService;

Mettez ensuite à jour la méthode OnNavigatedTo pour instancier notre attribut RouteServiceClient :

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    //On instancie les services
    geocodeService = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService");
    geocodeService.GeocodeCompleted += geocodeService_GeocodeCompleted;

    routeService = new RouteServiceClient("BasicHttpBinding_IRouteService");

    //On instancie la requête
    geocodeReq = new GeocodeRequest();
    geocodeReq.Credentials = new Credentials() { ApplicationId = "Au..." };
}

Nous allons maintenant créer une méthode CalculItineraire à appeler lorsque les coordonnées du point d’arrivée ont été trouvées. Voici le contenu commenté de notre méthode :

private void calculItineraire()
{
    //On instancie la requête
    RouteRequest routeReq = new RouteRequest();
    routeReq.Credentials = new Credentials() { ApplicationId = "Au..." };

    //Points de départ et d'arrivée de l'itinéraire
    Waypoint wayPointDepart = new Waypoint() { Location = new Location() { Longitude = geocodeResDepart.Locations.FirstOrDefault().Longitude, Latitude = geocodeResDepart.Locations.FirstOrDefault().Latitude }, Description = geocodeResDepart.DisplayName };
    Waypoint wayPointArrivee = new Waypoint() { Location = new Location() { Longitude = geocodeResArrivee.Locations.FirstOrDefault().Longitude, Latitude = geocodeResArrivee.Locations.FirstOrDefault().Latitude }, Description = geocodeResArrivee.DisplayName };

    //On précise les points les lequels on veut passer dans l'itinéraire
    routeReq.Waypoints = new ObservableCollection<Waypoint>();
    routeReq.Waypoints.Add(wayPointDepart);
    routeReq.Waypoints.Add(wayPointArrivee);

    //Quelques options...
    routeReq.Options = new RouteOptions() { Mode = TravelMode.Driving, RoutePathType = RoutePathType.Points };
    routeReq.Culture = "FR-fr";

    //On détermine la méthode à appeler quand cla recherche est terminée
    routeService.CalculateRouteCompleted += routeService_CalculateRouteCompleted;

    //On lance la recherche
    routeService.CalculateRouteAsync(routeReq);
}

Une fois le calcul de l’itinéraire lancé, il convient d’implémenter la méthode à appeler une fois les résultats trouvés :

private void routeService_CalculateRouteCompleted(object sender, CalculateRouteCompletedEventArgs e)
{
    if (e.Result != null && e.Result.Result != null && e.Result.Result.Legs != null && e.Result.Result.Legs.Any())
    {
        //Pushpin pour marquer le début et la fin du trajet
        Pushpin pinDepart = new Pushpin() { Location = new GeoCoordinate(geocodeResDepart.Locations.FirstOrDefault().Latitude, geocodeResDepart.Locations.FirstOrDefault().Longitude), Content = "Début" };
        Pushpin pinArrivee = new Pushpin() { Location = new GeoCoordinate(geocodeResArrivee.Locations.FirstOrDefault().Latitude, geocodeResArrivee.Locations.FirstOrDefault().Longitude), Content = "Fin" };

        //On fait le tracé
        var locationLst = new LocationCollection();
        foreach (Location loc in e.Result.Result.RoutePath.Points)
        {
            locationLst.Add(new GeoCoordinate(loc.Latitude, loc.Longitude));
        }

        MapPolyline polyline = new MapPolyline() { Stroke = new SolidColorBrush(Colors.Blue), StrokeThickness = 5, Locations = locationLst };

        //On ajouter les pushpin et le tracé sur la carte
        mapItineraire.Children.Add(pinArrivee);
        mapItineraire.Children.Add(pinDepart);
        mapItineraire.Children.Add(polyline);

        //On centre la carte sur le départ
        mapItineraire.SetView(pinDepart.Location, 10);
    }
    else
    {
        MessageBox.Show("Impossible de calculer l'itinéraire");
    }
}

L’écran d’affichage du détail de l’itinéraire

Cet écran peut-être découpé en deux :

  • un bref résumé de l’itinéraire comprenant les villes de départ et d’arrivée, la distance les séparant et le temps nécessaire pour les relier :
  • une liste des différentes étapes.

Avant toute chose, nous allons donc créer une classe nous permettant de modéliser une étape de l’itinéraire :

namespace Blog_Itineraire
{
    public class ItineraireEtape
    {
        public string temps { get; set; }
        public string distance { get; set; }
        public string instruction { get; set; }
    }
}

Le XAML quant à lui ressemble donc à ça :

<controls:PivotItem Header="Détails">
    <StackPanel>
        <TextBlock Name="lblDepart" />
        <TextBlock Name="lblArrivee" />
        <TextBlock Name="lblDistance" />
        <TextBlock Name="lblTemps" />

        <ListBox Name="lstBoxItineraire" Height="480" Margin="0,20,0,0">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <Grid Width="440">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>

                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>

                            <TextBlock Text="{Binding temps}" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" />
                            <TextBlock Text="{Binding distance}" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Right" />
                            <TextBlock Text="{Binding instruction}" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" TextWrapping="Wrap" Margin="0,10,0,0" />
                        </Grid>
                        <Rectangle Fill="White" Height="1" Margin="0,10" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</controls:PivotItem>

Dans le code behind, nous allons créer une méthode afficherItineraire qui prend en paramètre e.Result.Result.Legs.FirstOrDefault()e est le CalculateRouteCompletedEventArgs de la méthode routeService_CalculateRouteCompleted.

Comme vous allez  pouvoir le remarquer, j’ai également créé deux fonctions me permettant de supprimer les morceaux de XML du détail des itinéraires et de convertir les secondes en heure et minutes.

private void afficherItineraire(RouteLeg leg)
{
    //Infos générales
    lblDepart.Text = "Départ : " + geocodeResDepart.DisplayName;
    lblArrivee.Text = "Arrivée : " + geocodeResArrivee.DisplayName;
    lblDistance.Text = "Distance : " + leg.Summary.Distance + " km";
    lblTemps.Text = "Temps : " + ConvertirTemps(leg.Summary.TimeInSeconds);

    //Les étapes
    List<ItineraireEtape> lstSteps = new List<ItineraireEtape>();
    foreach (ItineraryItem item in leg.Itinerary)
    {
        lstSteps.Add(new ItineraireEtape() { temps = ConvertirTemps(item.Summary.TimeInSeconds), distance = item.Summary.Distance.ToString() + " km", instruction = ParseItineraire(item.Text) });
    }

    lstBoxItineraire.ItemsSource = lstSteps;

    //On stoppe l'indicateur
    progressIndicator.IsVisible = false;
}

private string ParseItineraire(string itemText)
{
    //On enlève les morceaux de XML
    itemText = itemText.Replace("<VirtualEarth:Action>", "");
    itemText = itemText.Replace("</VirtualEarth:Action>", "");

    itemText = itemText.Replace("<VirtualEarth:TurnDir>", "");
    itemText = itemText.Replace("</VirtualEarth:TurnDir>", "");

    itemText = itemText.Replace("<VirtualEarth:Toward>", "");
    itemText = itemText.Replace("</VirtualEarth:Toward>", "");

    itemText = itemText.Replace("<VirtualEarth:RoadName>", "");
    itemText = itemText.Replace("</VirtualEarth:RoadName>", "");

    itemText = itemText.Replace("<VirtualEarth:ExitNumber>", "");
    itemText = itemText.Replace("</VirtualEarth:ExitNumber>", "");

    itemText = itemText.Replace("<VirtualEarth:Sign>", "");
    itemText = itemText.Replace("</VirtualEarth:Sign>", "");

    itemText = itemText.Replace("<VirtualEarth:WaypointName>", "");
    itemText = itemText.Replace("</VirtualEarth:WaypointName>", "");

    return itemText;
}

private string ConvertirTemps(long timeInSeconds)
{
    TimeSpan timeSpan = TimeSpan.FromSeconds(timeInSeconds);

    if (timeSpan.Hours != 0)
    {
        return string.Format("{0:D2}h{1:D2}min", timeSpan.Hours, timeSpan.Minutes);
    }
    else if (timeSpan.Minutes != 0)
    {
        return string.Format("{0:D2}min", timeSpan.Minutes);
    }
    else
    {
        return string.Format("{0:D2}s", timeSpan.Seconds);
    }
}

Exemple d’exécution

Le code complet

Le fichier MainPage.xaml

<phone:PhoneApplicationPage 
    x:Class="Blog_Itineraire.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True" xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls" xmlns:my="clr-namespace:Microsoft.Phone.Controls.Maps;assembly=Microsoft.Phone.Controls.Maps" xmlns:my1="clr-namespace:System.Device.Location;assembly=System.Device">

    <!--LayoutRoot est la grille racine où tout le contenu de la page est placé-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <controls:Pivot Height="768" HorizontalAlignment="Left" Name="MainPivot" Title="Rolandl Itinéraire" VerticalAlignment="Top" Width="480">
            <controls:PivotItem Header="Ecran de saisie">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="70"/>
                        <RowDefinition Height="70"/>
                        <RowDefinition Height="70"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>

                    <TextBlock Grid.Row="0" Grid.Column="0" Margin="10,0,0,0" Text="Départ : " VerticalAlignment="Center" />
                    <TextBox Grid.Row="0" Grid.Column="1" Width="360" Name="txtDepart" />

                    <TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" Margin="10,0,0,0" Text="Arrivée :" VerticalAlignment="Center" />
                    <TextBox Grid.Row="1" Grid.Column="1" Width="360" Name="txtArrivee" />

                    <Button Grid.Row="2" Grid.ColumnSpan="2" Name="btnRecherche" Content="Rechercher" Click="btnRecherche_Click" />
                </Grid>
            </controls:PivotItem>

            <controls:PivotItem Header="Carte">
                <Grid>
                    <my:Map HorizontalAlignment="Left" Name="mapItineraire" VerticalAlignment="Top" Width="455" Height="600" CopyrightVisibility="Collapsed" LogoVisibility="Collapsed" ZoomLevel="10" ZoomBarVisibility="Visible">
                        <my:Map.CredentialsProvider>
                            <my:ApplicationIdCredentialsProvider ApplicationId="Au..." />
                        </my:Map.CredentialsProvider>
                        <my:Map.Center>
                            <my1:GeoCoordinate Altitude="NaN" Course="NaN" HorizontalAccuracy="NaN" Latitude="47.64483" Longitude="-122.141197" Speed="NaN" VerticalAccuracy="NaN" />
                        </my:Map.Center>
                    </my:Map>
                </Grid>
            </controls:PivotItem>

            <controls:PivotItem Header="Détails">
                <StackPanel>
                    <TextBlock Name="lblDepart" />
                    <TextBlock Name="lblArrivee" />
                    <TextBlock Name="lblDistance" />
                    <TextBlock Name="lblTemps" />

                    <ListBox Name="lstBoxItineraire" Height="480" Margin="0,20,0,0">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <StackPanel>
                                    <Grid Width="440">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="*" />
                                            <ColumnDefinition Width="*" />
                                        </Grid.ColumnDefinitions>

                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="Auto"/>
                                            <RowDefinition Height="Auto"/>
                                        </Grid.RowDefinitions>

                                        <TextBlock Text="{Binding temps}" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" />
                                        <TextBlock Text="{Binding distance}" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Right" />
                                        <TextBlock Text="{Binding instruction}" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" TextWrapping="Wrap" Margin="0,10,0,0" />
                                    </Grid>
                                    <Rectangle Fill="White" Height="1" Margin="0,10" />
                                </StackPanel>
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </StackPanel>
            </controls:PivotItem>
        </controls:Pivot>
    </Grid>

    <shell:SystemTray.ProgressIndicator>
        <shell:ProgressIndicator IsIndeterminate="True" IsVisible="False" x:Name="progressIndicator" />
    </shell:SystemTray.ProgressIndicator>
</phone:PhoneApplicationPage>

Le fichier MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using Blog_Itineraire.GeocodeService;
using Microsoft.Phone.Controls.Maps;
using Blog_Itineraire.RouteService;
using System.Collections.ObjectModel;
using Microsoft.Phone.Controls.Maps.Platform;
using System.Device.Location;

namespace Blog_Itineraire
{
    public partial class MainPage : PhoneApplicationPage
    {
        private GeocodeServiceClient geocodeService;
        private GeocodeRequest geocodeReq;
        private GeocodeResult geocodeResDepart;
        private GeocodeResult geocodeResArrivee;
        private RouteServiceClient routeService;
        private string query;

        // Constructeur
        public MainPage()
        {
            InitializeComponent();
        }

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            //On instancie les services
            geocodeService = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService");
            geocodeService.GeocodeCompleted += geocodeService_GeocodeCompleted;

            routeService = new RouteServiceClient("BasicHttpBinding_IRouteService");

            //On instancie la requête
            geocodeReq = new GeocodeRequest();
            geocodeReq.Credentials = new Credentials() { ApplicationId = "Au..." };
        }

        private void btnRecherche_Click(object sender, RoutedEventArgs e)
        {
            if (txtDepart.Text == "")
            {
                MessageBox.Show("Merci de saisir un lieu de départ");
            }
            else if (txtArrivee.Text == "")
            {
                MessageBox.Show("Merci de saisir un lieu d'arrivée");
            }
            else
            {
                //On indique que l'application tourne
                progressIndicator.IsVisible = true;

                //On indique le type de la requête
                query = "depart";

                //On lance la recherche
                geocodeReq.Query = txtDepart.Text;
                geocodeService.GeocodeAsync(geocodeReq);
            }
        }

        private void geocodeService_GeocodeCompleted(object sender, GeocodeCompletedEventArgs e)
        {
            if (e.Result != null && e.Result.Results.Any(x => x.Locations != null && x.Locations.Any()))
            {
                if (query == "depart")
                {
                    //On récupère les coordonnées du point de départ
                    geocodeResDepart = e.Result.Results.FirstOrDefault();

                    //On indique le type de la nouvelle requête
                    query = "arrivee";

                    //On lance la recherche pour le point d'arrivée
                    geocodeReq.Query = txtArrivee.Text;
                    geocodeService.GeocodeAsync(geocodeReq);
                }
                else
                {
                    //On récupère les coordonnées du point d'arrivée
                    geocodeResArrivee = e.Result.Results.FirstOrDefault();

                    //On lance l'itinéraire
                    calculItineraire();
                } 
            }
            else
            {
                if (query == "depart")
                {
                    MessageBox.Show("Impossible de localiser le départ");
                }
                else
                {
                    MessageBox.Show("Impossible de localiser l'arrivée");
                }
            }
        }

        private void calculItineraire()
        {
            //On instancie la requête
            RouteRequest routeReq = new RouteRequest();
            routeReq.Credentials = new Credentials() { ApplicationId = "Au..." };

            //Points de départ et d'arrivée de l'itinéraire
            Waypoint wayPointDepart = new Waypoint() { Location = new Location() { Longitude = geocodeResDepart.Locations.FirstOrDefault().Longitude, Latitude = geocodeResDepart.Locations.FirstOrDefault().Latitude }, Description = geocodeResDepart.DisplayName };
            Waypoint wayPointArrivee = new Waypoint() { Location = new Location() { Longitude = geocodeResArrivee.Locations.FirstOrDefault().Longitude, Latitude = geocodeResArrivee.Locations.FirstOrDefault().Latitude }, Description = geocodeResArrivee.DisplayName };

            //On précise les points les lequels on veut passer dans l'itinéraire
            routeReq.Waypoints = new ObservableCollection<Waypoint>();
            routeReq.Waypoints.Add(wayPointDepart);
            routeReq.Waypoints.Add(wayPointArrivee);

            //Quelques options...
            routeReq.Options = new RouteOptions() { Mode = TravelMode.Driving, RoutePathType = RoutePathType.Points };
            routeReq.Culture = "FR-fr";

            //On détermine la méthode à appeler quand cla recherche est terminée
            routeService.CalculateRouteCompleted += routeService_CalculateRouteCompleted;

            //On lance la recherche
            routeService.CalculateRouteAsync(routeReq);            
        }

        private void routeService_CalculateRouteCompleted(object sender, CalculateRouteCompletedEventArgs e)
        {
            if (e.Result != null && e.Result.Result != null && e.Result.Result.Legs != null && e.Result.Result.Legs.Any())
            {
                //Pushpin pour marquer le début et la fin du trajet
                Pushpin pinDepart = new Pushpin() { Location = new GeoCoordinate(geocodeResDepart.Locations.FirstOrDefault().Latitude, geocodeResDepart.Locations.FirstOrDefault().Longitude), Content = "Début" };
                Pushpin pinArrivee = new Pushpin() { Location = new GeoCoordinate(geocodeResArrivee.Locations.FirstOrDefault().Latitude, geocodeResArrivee.Locations.FirstOrDefault().Longitude), Content = "Fin" };

                //On fait le tracé
                var locationLst = new LocationCollection();
                foreach (Location loc in e.Result.Result.RoutePath.Points)
                {
                    locationLst.Add(new GeoCoordinate(loc.Latitude, loc.Longitude));
                }

                MapPolyline polyline = new MapPolyline() { Stroke = new SolidColorBrush(Colors.Blue), StrokeThickness = 5, Locations = locationLst };

                //On ajouter les pushpin et le tracé sur la carte
                mapItineraire.Children.Add(pinArrivee);
                mapItineraire.Children.Add(pinDepart);
                mapItineraire.Children.Add(polyline);

                //On centre la carte sur le départ
                mapItineraire.SetView(pinDepart.Location, 10);

                //On affiche le détail de l'itineraire
                afficherItineraire(e.Result.Result.Legs.FirstOrDefault()); 
            }
            else
            {
                MessageBox.Show("Impossible de calculer l'itinéraire");
            }
        }

        private void afficherItineraire(RouteLeg leg)
        {
            //Infos générales
            lblDepart.Text = "Départ : " + geocodeResDepart.DisplayName;
            lblArrivee.Text = "Arrivée : " + geocodeResArrivee.DisplayName;
            lblDistance.Text = "Distance : " + leg.Summary.Distance + " km";
            lblTemps.Text = "Temps : " + ConvertirTemps(leg.Summary.TimeInSeconds);

            //Les étapes
            List<ItineraireEtape> lstSteps = new List<ItineraireEtape>();
            foreach (ItineraryItem item in leg.Itinerary)
            {
                lstSteps.Add(new ItineraireEtape() { temps = ConvertirTemps(item.Summary.TimeInSeconds), distance = item.Summary.Distance.ToString() + " km", instruction = ParseItineraire(item.Text) });
            }

            lstBoxItineraire.ItemsSource = lstSteps;

            //On stoppe l'indicateur
            progressIndicator.IsVisible = false;
        }

        private string ParseItineraire(string itemText)
        {
            //On enlève les morceaux de XML
            itemText = itemText.Replace("<VirtualEarth:Action>", "");
            itemText = itemText.Replace("</VirtualEarth:Action>", "");

            itemText = itemText.Replace("<VirtualEarth:TurnDir>", "");
            itemText = itemText.Replace("</VirtualEarth:TurnDir>", "");

            itemText = itemText.Replace("<VirtualEarth:Toward>", "");
            itemText = itemText.Replace("</VirtualEarth:Toward>", "");

            itemText = itemText.Replace("<VirtualEarth:RoadName>", "");
            itemText = itemText.Replace("</VirtualEarth:RoadName>", "");

            itemText = itemText.Replace("<VirtualEarth:ExitNumber>", "");
            itemText = itemText.Replace("</VirtualEarth:ExitNumber>", "");

            itemText = itemText.Replace("<VirtualEarth:Sign>", "");
            itemText = itemText.Replace("</VirtualEarth:Sign>", "");

            itemText = itemText.Replace("<VirtualEarth:WaypointName>", "");
            itemText = itemText.Replace("</VirtualEarth:WaypointName>", "");

            return itemText;
        }

        private string ConvertirTemps(long timeInSeconds)
        {
            TimeSpan timeSpan = TimeSpan.FromSeconds(timeInSeconds);

            if (timeSpan.Hours != 0)
            {
                return string.Format("{0:D2}h{1:D2}min", timeSpan.Hours, timeSpan.Minutes);
            }
            else if (timeSpan.Minutes != 0)
            {
                return string.Format("{0:D2}min", timeSpan.Minutes);
            }
            else
            {
                return string.Format("{0:D2}s", timeSpan.Seconds);
            }
        }
    }
}

Le fichier ItineraireEtape.cs

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Blog_Itineraire
{
    public class ItineraireEtape
    {
        public string temps { get; set; }
        public string distance { get; set; }
        public string instruction { get; set; }
    }
}

Commentaires