Astuces DotNet (Sébastien Courtois)

01/02/2010

[Silverlight 3/4] Comment faire des contrôles avec deux faces différentes ?

Classé dans : Intermediaire, Silverlight, Silverlight 3, Silverlight 4, XAML — Mots-clefs :, , , , — sebastiencourtois @ 21:23

Au commencement Microsoft créa Silverlight. Silverlight, la technologie RIA nouvelle génération pour créer des applications vectorielles entièrement en code managé. Microsoft vit que Silverlight était bon… mais qu’il lui manquait un petit truc pour rivaliser avec son rival. Alors Microsoft dit : “Que la 3D soit” et la 3D fut intégrée dans Silverlight … enfin pas tout à fait.

Tout d’abord, la « 3D » dans Silverlight n’est qu’un moteur de perspective qui ne permet que de faire des rotations/translations de contrôles. Point de moteur ou de monde 3D ici. On parle uniquement ici que de pouvoir faire des jolis effets en alliant animations et perspectives.

Ensuite cette perspective a une feature (« not a bug ») assez ennuyeuse à mon sens : Si on prend l’exemple d’un contrôle contenant une image, lorsque l’on retourne ce contrôle, on se retrouve avec … la même image inversée.

Cap3

Personnellement j’aurais aimé avoir accès à une deuxième face pour pouvoir ajouter des nouveaux contrôles et, comme on n’est jamais mieux servi que par soi-même, j’ai donc cherché un moyen de réaliser ce contrôle. J’ai appelé ce contrôle : FacesPanel.

Fonctionnement de base du contrôle FacesPanel

En parcourant les forums, la pratique général est d’avoir deux contrôles sur un même conteneur (un Grid par exemple) et de rendre visible l’un ou l’autre selon la propriété RotationX/RotationY d’un PlaneProjection. L’algorithme est le suivant :

double Roty = Math.Abs(((PlaneProjection)this.Projection).RotationY);
double Rotx = Math.Abs(((PlaneProjection)this.Projection).RotationX);
if ((Roty % 360 > 90 && Roty % 360 < 270) || (Rotx % 360 > 90 && Rotx % 360 < 270))
{
    //Back Face
}
else
{
    //Front Face
}

Remarque : Vous vous demander peut être pourquoi on ne trouve pas de RotationZ. La raison est qu’une Rotation sur l’axe Z se fera toujours “face” à l’utilisateur. Par conséquent, aucun cas de changement de face. Une autre interrogation pourrait être au sujet des Math.Abs et des %. Cela est simplement dû au fait que les rotations peuvent être négatives comme supérieure à 360°.

Cette méthode a deux inconvénients qu’il va falloir gérer :

1°) Lors du retournement du contrôle, le fait de changer la visibilité des contrôles ne résout pas le problème de l’inversion graphique car c’est le container qui tourne donc le contrôles interne suit le mouvement. Pour gérer ce problème, on utilise un ScaleTransform afin de remettre le contrôle dans le bon sens.

2°) Je n’ai pas réussi à récupérer les valeurs RotationX/RotationY au cours d’une animation lorsque je me binde sur ces propriétés. Cela est problématique pour avoir un comportement naturel au cours d’une animation (le changement de face se faisant uniquement à la fin de l’animation). Pour parer ce problème, j’ai utilisé une solution peu élégant/optimisé mais qui à le mérite de fonctionner (si vous avez mieux, n’hésitez pas à commenter). J’utilise un DispatcherTimer afin que, périodiquement, je regarde le valeurs de RotationX/RotationY pour voir si un changement de face est nécessaire.

Propriétés/Methodes de FacesPanel :

Voila pour la base du fonctionnement. Afin de rendre le contrôle exploitable, j’ai rajouté les propriétés suivantes :

  • FrontFace (type : UIElement) : Contrôle pour la face principale
  • BackFace (type : UIElement) : Contrôle pour la face caché
  • AnimationDirection (type: TurnDirection ) : Permet de définir le sens du retournement de la face. Se base sur l’énum TurnDirection défini dans le même fichier .cs. 5 valeurs (NONE,LEFT,RIGHT,UP,BOTTOM)
  • AnimationDuration (type : int) : Durée de l’animation de retournement
  • AnimationEasing (Type : IEasingFunction) : Animation Easing pour l’animation de  retournement.
  • GoToFrontFace() : Montre la face principale.
  • GoToBackFace() : Montre la face “caché”.
  • TurnFace() : Change de face

Cas d’utilisation : Photo Bucket

Afin de faire un cas d’utilisation, j’ai choisi de reprendre une démonstration Silverlight 4 sur glisser/déposer faite par Audrey Petit. Ce que je souhaitait faire était de pouvoir glisser déposer des images puis, par click de souris sur une image, la retourner pour marquer des commentaires au dos. J’ai donc utilisé l’exemple d’Audrey en rajoutant un contrôle spécifique pour gérer les images.

PictureControl.xaml :

<UserControl x:Class="PhotoBucket.PictureControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:PhotoBucket"
    mc:Ignorable="d" Width="150" Height="150">
    <local:FacesPanel x:Name="FacesPanelControl" AnimationDirection="LEFT" AnimationDuration="3" MouseLeftButtonDown="FacesPanelControl_MouseLeftButtonDown">
        <local:FacesPanel.AnimationEasing>
            <ElasticEase />
        </local:FacesPanel.AnimationEasing>
        <local:FacesPanel.FrontFace>
            <Border Background="White" BorderBrush="Black" BorderThickness="1">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="120" />
                        <RowDefinition Height="30" />
                    </Grid.RowDefinitions>
                    <Image x:Name="ImageZone"  Margin="10,5,10,0" />
                    <TextBlock Text="{Binding ElementName=txtTitle,Path=Text}" FontSize="10" FontStyle="Italic"
                               Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
                </Grid>
            </Border>
        </local:FacesPanel.FrontFace>
        <local:FacesPanel.BackFace>
            <Border Background="White" Margin="5" BorderBrush="Black" BorderThickness="1">
                <StackPanel>
                    <TextBlock Text="Titre de la photo" Margin="2" />
                    <TextBox x:Name="txtTitle"  Text="Test" Margin="2" />
                </StackPanel>
            </Border>
        </local:FacesPanel.BackFace>
    </local:FacesPanel>
</UserControl>

Picture.xaml.cs :

    public partial class PictureControl : UserControl
    {
        public PictureControl()
        {
            InitializeComponent();
        }

        private void FacesPanelControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            ((FacesPanel)sender).TurnFace();
        }
    }

Et voici le résultat en image :

Cap1 Cap2

Et en code

  • Lien du source de la démo du contrôle sur VS 2008 / SL3 (pas d’application photo car pas de drag / drop en SL3)
  • Lien du source de la démo VS  (Appli Photo Bucket)

Si vous avez des commentaires pour améliorer ce contrôle, n’hésitez pas.

[Bing Maps] Développement Microsoft Bing Maps Web Services – Part 5 : Search Service

Classé dans : .NET, Bing Maps, Débutant, Intermediaire, Virtual Earth — Mots-clefs :, , , — sebastiencourtois @ 21:22

Cet article est une suite de l’article se trouvant ici. Il était question de créer un compte Bing Maps et se connecter en C# aux différents services Web de Virtual Earth.

Liens des posts sur le sujet sur ce blog :

  1. Introduction à Microsoft Virtual Earth (Part 1)
  2. Géocoding / Géocoding Inverse (Part 2)
  3. Récupération des images satellites (Part 3)
  4. Calcul d’itinéraires (Part 4)
  5. Search Service (Part 5)

Bing Maps intègre un système de recherche permettant de trouver des entités géographiques afin de les afficher sur une carte.).

  • Configuration préliminaire

Afin de pouvoir utiliser le service de recherche, il est nécessaire d’avoir rajouté les références aux services Common et Search (http://staging.dev.virtualearth.net/webservices/v1/searchservice/searchservice.svc) et d’avoir récupérer un ticket de session valide.

L’accès au service se fera par l’intermédiaire de la classe proxy généré lors de l’ajout du Service Reference.

SearchServiceClient svc = new SearchServiceClient();

  • Requêtage du service de recherche

La définition de la requête à effectuer se réalise grâce à l’objet SearchRequest. Celui-ci contient une propriété StructuredQuery de type StructuredSearchQuery. Cette propriété contient deux champs textes :

    • Keywords : Liste des mots clés liés à la recherche
    • Location : Lieu autour duquel on veut trouver les entités

Ainsi la requête suivante ramène l’ensemble des pizzerias près des locaux de Microsoft.

SearchServiceClient svc = new SearchServiceClient();

SearchRequest sq = new SearchRequest();

sq.Culture = "fr-fr";

sq.Credentials = new Credentials();

sq.Credentials.Token = token;

sq.StructuredQuery = new StructuredSearchQuery()

{

Keyword = "Pizzeria",

Location = "Issy Les Moulineaux",

};

SearchResponse response = svc.Search(sq);

Une autre façon de réaliser cette requête est d’utiliser la propriété Query de la classe SearchRequest à la place de StructuredQuery. Vous pouvez ajouter des délimiteurs tels que le ‘;’ pour indiquer les différents paramètres.

SearchServiceClient svc = new SearchServiceClient();

SearchRequest sq = new SearchRequest();

sq.Credentials = new Credentials();

sq.Credentials.Token = token;

sq.Query = "Resturant; Issy-Les-Moulineaux";

  • Configuration supplémentaires

Il est possible de paramétrer cette requête afin que les résultats soient en correspondance avec ce que l’on souhaite afficher. Pour cela, on utilise la propriété SearchOptions de la classe SearchRequest.

Cette classe contient une propriété Count permettant de demander la récupération d’un nombre défini de résultat (par défaut 10) et une propriété StartingIndex (par défaut 0). Ces deux propriétés permettent la réalisation de grille avec gestion de pages lorsque le nombre de résultat est trop grand.

La propriété SortOrder permet de définir dans quel ordre doivent être envoyé. Cette propriété est une énumération de type SortOrder comprenant les quatres valeurs suivants :

    • Distance : Du plus proche au plus loin
    • Popularity : Du plus populaire au moins populaire
    • Rating : du mieux noté au moins bien noté
    • Relevance : du plus correspondant aux critères de recherche au moins correspondant.

La propriété ListingType permet de définir la base de données depuis laquelle va être effectué la recherche. Cette propriété est une énumération de type ListingType ayant les choix suivants :

    • Business : Entreprises,Commerces (valeur par défaut)
    • CommunityContent : Contenu fourni par la communauté (non disponible pour la France)
    • Person : Personne (non disponible pour la France)
    • Unknown : Entité dont le type est inconnu

Afin de filtrer encore plus spécifiquement les données, la propriété Filters permet de réaliser des recherches spécifiques à un domaine d’activité. Les filtres disponibles et les valeurs possibles sont disponibles sur le site MSDN (http://msdn.microsoft.com/en-us/library/cc966911.aspx).

La propriété AutocorrectQuery permet de demander à Bing Maps de corriger les mots mal orthographiés dans la requête avant de l’exécuter. Cette correction est effectuée en fonction d’un dictionnaire dont la langue dépend de la propriété Culture de la requête. Cette propriété est à true par défaut. En cas de correction sur la requête, les corrections apportées seront indiqué dans la propriété QuerySuggestion de la réponse.

La propriété ParseOnly permet d’indiquer si l’on souhaite que la requête soit uniquement analysé sans renvoyé les résultats de la recherche en elle-même. Cela permet notamment de vérifier les suggestions ou les corrections faites par Bing Maps sur la requête sans récupérer une série de résultats qui pourrait être lourde en termes de bande passante.

SearchServiceClient svc = new SearchServiceClient();
SearchRequest sq = new SearchRequest();
sq.Credentials = new Credentials();
sq.Credentials.Token = token;
sq.Query = "Resturant Issy-Les-Moulineaux";
sq.SearchOptions = new SearchOptions()
{
    StartingIndex = 0,
    Count = 20,
    SortOrder = SortOrder.Rating,
    ListingType = ListingType.Business,
    AutocorrectQuery = true
};
SearchResponse response = svc.Search(sq);

  • Analyse des résultats

La classe résultat récupéré suite à l’appel à la méthode Search est de type SearchResponse. Cette classe contient de nombreux champs que nous avons déjà vu précédement comme ExtensionData et ResponseSummary.

L’une des propriétés spécifiques est QuerySuggestion. Cette propriété n’est remplie que si la propriété AutoCorrectQuery de la requête est à true. Au sein de QuerySuggestion, on trouve la propriété Query qui contient la requête corrigé sous forme d’une chaine de caractères. La propriété StructuredQuery contient la requête corrigé sous forme plus structure : Le lieu recherché (dans la propriété Location) et l’objet de la recherche (la propriété Keyword). Dans notre exemple (‘Restaurant Issy-Les-Moulineaux’), on aura pour ‘Restaurant’ dans la propriété Keyword et ‘Issy-Les-Moulineaux’ dans la propriété Location.

SearchResponse response = svc.Search(sq);
Console.WriteLine("Nombre de résultats :" + response.ResultSets[0].EstimatedMatches);
foreach(SearchResultBase srs in response.ResultSets[0].Results)
{
   Console.WriteLine("----------------------------------");
   Console.WriteLine("Id : "+ srs.Id);
   Console.WriteLine("Nom : "+ srs.Name);
   Console.WriteLine("Distance : "+ srs.Distance);
   Console.WriteLine("Position : (" + srs.LocationData.Locations[0].Longitude + ";" + srs.LocationData.Locations[0].Latitude+")");
   Console.WriteLine("----------------------------------");
}
Console.ReadLine();

  • Affichage des résultats suite à la requête

L’autre propriété spécifique est ResultSet qui est le tableau des résultats des requêtes (type ResultSet[]). Chaque ResultSet contient une tableau de SearchResultBase[] contenu dans la propriété Result.

Le type SearchResultBase contient les propriétés suivantes :

    • Id : Identifiant Bing Maps de l’entité
    • Name : Nom de l’entité
    • Distance : Distance entre le lieu cible et ce que l’on recherche (dans notre exemple, distance entre le centre d’Issy Les Moulineaux et le restaurant).
    • LocationData : Informations plus précise sur le lieu de l’entité (Position géographique, Niveau de confiance …)

Voici l’article qui termine ma série sur le WebService Bing Maps. Vous retrouverez l’ensemble des liens vers ces articles au début de ce post.

28/01/2010

[.NET] Le mot clé sealed sur une classe permet-il permettre de gagner en performance ?

Classé dans : .NET, Débutant, Intermediaire, Optimisation — Mots-clefs :, , , , , — sebastiencourtois @ 00:19

Voici la question que Wilfried Woivre (MSP ainsi que “Ask The Expert” sur Azure aux prochains Tech Days 2010).

Ma première réponse a été… “J’en sais rien” :) . En effet, le mot clé sealed a toujours été, pour moi, utilisé pour des design OO spécifiques (type création de framework public …) où l’idée est d’interdire l’extension d’une classe (la fonction première du mot clé sealed). J’ai donc dit que je pensais que ce n’était pas le cas ni le rôle de ce mot clé.

Quelque temps plus tard, je me retrouve à lire un chapitre sur l’optimisation de code .NET dans lequel le mot clé “sealed” == performance. J’ai donc décidé de faire le benchmark suivant :

   public class ClassA
   {
       public void Method1() { }
   }

   public sealed class ClassASealed
   {
       public void Method1() { }
   }

   public class ClassAVirtual
   {
       public virtual void Method1() { }
   }

    
class Program
{
    static void Main(string[] args)
    {
        ClassA ca = new ClassA();
        ClassASealed cas = new ClassASealed();
        ClassAVirtual cav = new ClassAVirtual();

        Stopwatch swca = new Stopwatch();
        Stopwatch swcas = new Stopwatch();
        Stopwatch swcav = new Stopwatch();
        for (int i = 0; i < 10000000; i++)
        {
            swca.Start();
            ca.Method1();
            swca.Stop();

            swcas.Start();
            cas.Method1();
            swcas.Stop();

            swcav.Start();
            cav.Method1();
            swcav.Stop();
        }

        Console.WriteLine("ClassA  : " + swca.Elapsed);
        Console.WriteLine("ClassAS : " + swcas.Elapsed);
        Console.WriteLine("ClassAV : " + swcav.Elapsed);
        Console.WriteLine("Finish");
        Console.ReadLine();
    }
}

On teste, ici, le temps d’appel des méthodes en faisant 1 000 000 d’appels. On trouve les résultats suivants :

  • Classe Normale : 22.8844652 secondes
  • Classe Sealed : 22.8523417 secondes
  • Classe Normale avec une méthode virtuelle : 23.0062174 secondes

Premier constat : Le mot clé sealed nous permet de gagner environ 1 pour 1000 en performance.

Deuxième constat : la méthode virtuelle semble faire perdre aussi des performances ( 5 ou 6 pour 1000).

Pourquoi ces écarts ?

1°)Parlons tous d’abord de l’écart normale/sealed : Au niveau du code IL, il n’y a que le sealed de différence :

.class public auto ansi beforefieldinit ClassAVirtual
.class public auto ansi sealed beforefieldinit ClassASealed

L’optimisation à lieu au niveau du Runtime. En effet, sachant que la classe est scellé, le runtime peut faire un appel direct à la méthode (dans certains cas, exécuter le code sans faire d’appel).

2°) Pour la différence méthode classique /méthode virtuelle :

.method public hidebysig instance void Method1() cil managed
.method public hidebysig newslot virtual instance void Method1() cil managed

Le virtual ici oblige le runtime a créer une table d’appel pour cette méthode virtuelle dans laquelle seront contenu les méthodes des classes dérivées qui surchargeront cette méthode. Par conséquent, un appel à cette méthode est un appel ‘virtuel’ de méthode qui doit aller chercher dans cette table pour trouver la bonne méthode à appelé. Même si dans notre cas, la table ne contient qu’un seul élément, il y a tout de même un coût supplémentaire par rapport à un appel classique (accès à la table,recherche dans la table …)

Conclusion

Effectivement, le fait de mettre rendre classe ‘sealed’ permet de gagner des performances. Toutefois les performances sont tellement faible que dans 95 % des cas (disons : les applications non temps réel ou ne travaillant pas avec des énormes volumes de données), ces gains sont négligeables et il sera plus judicieux de voir s’il n’y a pas des optimisations à faire au niveau du code ou de l’architecture du projet.

25/01/2010

[Silverlight 4] Supprimer le context menu “Silverlight” lors d’un clic droit

Classé dans : Débutant, Silverlight 4, XAML — Mots-clefs :, , , , , — sebastiencourtois @ 22:41

Silverlight 4 apporte une nouveauté très demandée : La gestion du clic droit. Le problème est que, par défaut, le clic droit s’accompagne d’un menu contextuel “Silverlight” un peu énervant. Toutefois, il y a une manière de s’affranchir de celui-ci.

L’évènement MouseRightButtonDown est un RoutedEvent qui, lorsqu’il est déclenché, est transmis de contrôle enfant à contrôle parent (ainsi de suite jusqu’au contrôle racine). Or, s’il arrive en haut de l’arbre visuel, il affiche par défaut le menu contextuel “Silverlight”.

1°)Une solution simple est d’indiquer que l’évènement a été géré (e.Handled = true) dans la callback de l’événement.

<UserControl x:Class="RightClickHandled.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <Grid x:Name="LayoutRoot" Background="White">
        <Rectangle Fill="Red" Width="300" Height="200" MouseRightButtonDown="Rectangle_MouseRightButtonDown"></Rectangle>
    </Grid>
</UserControl>
public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void Rectangle_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
        }
    }

Le problème de cette méthode est qu’il faut le mettre sur chacune des callbacks de MouseRightButtonDown. De plus, cela arrête la remontée de l’évenement MouseRightClickDown aux contrôles parent qui doivent peut être y réagir.

2°) Utilisation d’un behavior : Etant donné mon dernier post, je ne pouvais m’empêcher de voir si un behavior peut régler la solution…. et c’est le cas. Pour ceux qui ne connaissent pas les behaviors, vous pouvez vous référez à cette introduction sur les behaviors).

    public class EraseSilverlightContextMenuBehavior : Behavior<UIElement>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.MouseRightButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseRightButtonDown);
        }

        void AssociatedObject_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.MouseRightButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseRightButtonDown);
        }
    }

Après on associé le behavior au contrôle et le tour est joué.

<UserControl x:Class="RightClickHandled.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:local="clr-namespace:RightClickHandled"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <Grid x:Name="LayoutRoot" Background="White">
        <Rectangle Fill="Red" Width="300" Height="200">
            <i:Interaction.Behaviors>
                <local:EraseSilverlightContextMenuBehavior />
            </i:Interaction.Behaviors>
        </Rectangle>
    </Grid>
</UserControl>

Le soucis avec cette méthode est qu’il faut appliquer le behavior à tous les contrôles ce qui peut devenir lourd.

3°)La solution la plus générique serait de s’abonner à l’événement MouseRightButtonDown du RootVisual contenu dans le App.xaml.cs afin de bloquer l’événement juste afin sa sortie de l’arbre visuel.

private void Application_Startup(object sender, StartupEventArgs e)
{
    this.RootVisual = new MainPage();
    this.RootVisual.MouseRightButtonDown += (sender2, args) => { args.Handled = true; };
}

Ainsi tous les contrôles de l’arbre visuel peuvent avoir accès à l’évènement sans contrainte. Plus besoin de l’implémenter sur chacun des contrôles car cela est géré au niveau de la racine de l’arbre.

Si vous avez des méthodes plus smart pour faire cela, n’hésitez pas à me les indiquer :) .

[Silverlight 3/4] Introduction sur les Behaviors

Classé dans : Débutant, Silverlight, Silverlight 3, Silverlight 4, WPF, XAML — Mots-clefs :, , , , — sebastiencourtois @ 19:47

Introduit dans la version 3 de Microsoft Expression Blend, les Behaviors sont un système permettant de créer des comportements génériques pour des contrôles. L’un des gros avantages est que ces comportements peuvent être utilisé directement en XAML ou par Blend.

Nous allons partir d’un exemple afin d’étudier comment marche les behaviors. Notre Behavior exemple sera un ResizeBehavior qui va doubler la taille du contrôle auquel il est associé lorsque l’on clique sur le bouton gauche et qui va diviser par deux sa taille quand on clique avec le bouton droit. Le but du jeu étant que ce behavior fonctionne pour un plus grand nombre de contrôles possibles.

  • Comment coder un behavior ?

Tout commence avec la classe Behavior<T>. Cette classe est la classe de base des Behaviors. Elle se trouve dans l’assembly System.Windows.Interactivity. Cet assembly ne se trouve pas dans la GAC et pour cause, il s’agit d’un ajout fait par Expression Blend.

Si vous n’avez pas Expression Blend, je vous encourage à télécharger la version Beta pour Silverlight 4 / .NET 4 (http://www.microsoft.com/downloads/details.aspx?FamilyID=6806e466-dd25-482b-a9b3-3f93d2599699&displaylang=en).

Une fois Blend installé, vous devriez trouver cet assembly dans le répertoire  :

C:\Program Files\Microsoft SDKs\Expression\Blend 3\Interactivity\Libraries\Silverlight

C:\Program Files\Microsoft SDKs\Expression\\Blend Preview for .NET 4\Interactivity\Libraries\Silverlight

Pour créer un nouveau behavior, il suffit de créer une classe dérivant de Behavior<T> en spécifiant, à la place du T, le type d’objet que va être appelé à manipuler le Behavior. Dans notre exemple, nous prendrons des contrôles de type FrameworkElement (afin d’avoir accès aux propriétés Width/Height).

    public class ResizeBehavior : Behavior<FrameworkElement>
    {
        public ResizeBehavior()    {}

        protected override void OnAttached()
        {
            base.OnAttached();
            //Code
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            //Code
        }
    }

Comme on peut le voir, la classe ResizeBehavior contient :

    • Un constructeur : Pour initialiser les données
    • Un méthode OnAttached : Méthode hérité de Behavior<T>. Cette méthode est appelé lorsque le behavior est “attaché” à l’objet. Nous verrons plus loin quand, exactement, à lieu l’attachement. Pour le moment,  on admettra que l’attachement se fait lors de la création du contrôle sur lequel va intervenir le behavior.
    • Une méthode OnDetaching : Méthode hérité de Behavior<T>. Cette méthode est appelé lorsque le behavior est “attaché” à l’objet. Le détachement est réalisé lorsque le contrôle sur lequel s’applique le behavior est détruit (Nettoyage de la mémoire, désabonnement des événements …).

On remarque aussi un appel aux méthodes OnAttached/OnDetaching parentes. Ces appels sont nécessaires au bon fonctionnement du behavior et doivent toujours être présente au début des méthodes correspondantes.

Comme il a été expliqué au début de cet article, un behavior est un comportement associé à un contrôle. Ce contrôle est disponible au sein du behavior sous la forme de la propriété AssociatedObject (défini dans Behavior<T>.). Vous pouvez ainsi manipuler l’objet comme si vous étiez dans le code behind du fichier XAML dont il dépend. Ainsi nous allons ajouter les évènements MouseLeftButtonDown/MouseRightButtonDown sur l’objet associé afin de modifier la taille de l’objet associé.

    public class ResizeBehavior : Behavior<FrameworkElement>
    {
        public ResizeBehavior()    {}

        protected override void OnAttached()
        {
            base.OnAttached();
            //Code
            this.AssociatedObject.MouseLeftButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
            this.AssociatedObject.MouseRightButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseRightButtonDown);
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            //Code
            this.AssociatedObject.MouseLeftButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
            this.AssociatedObject.MouseRightButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseRightButtonDown);
        }

        void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.AssociatedObject.Width *= 2;
            this.AssociatedObject.Height *= 2;
        }

        void AssociatedObject_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.AssociatedObject.Width /= 2;
            this.AssociatedObject.Height /= 2;
        }
    }
  • C’est bien beau tout ça, mais comment on utilise ça en XAML ?

Coté XAML, il faut tout d’abord ajouter deux références. Une pour avoir accès au namespace System.Windows.Interactivity et une pour avoir accès au behavior.

    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:local="clr-namespace:BehaviorDemo"  >

Ensuite, on ajoute un contrôle de type FrameworkElement (un Rectangle par exemple).

        <Rectangle Fill="Red" Width="200" Height="100">
            <i:Interaction.Behaviors>
                <local:ResizeBehavior />
            </i:Interaction.Behaviors>
        </Rectangle>

Le namespace System.Windows.Interactivity ajoute une classe Interaction permettant d’associer des Behaviors (mais aussi des Triggers / Actions… qui seront l’objet d’autres posts de blogs :) ) .

Lors de l’exécution de l’application, Silverlight va créer l’objet Rectangle puis le ResizeBehavior. Une fois les deux objets créés, la méthode OnAttached de Behavior<T> est appelée. Lors de la destruction de l’objet Rectangle, c’est la méthode OnDetaching qui est appelée.

  • Moi j’aime pas le XAML. Je veux du C# …

Le code suivant permet d’associer un behavior à un contrôle.

Rectangle b = new Rectangle();
b.Fill = new SolidColorBrush(Colors.Purple);
b.Width = 400;
b.Height = 200;
Interaction.GetBehaviors(b).Add(new ResizeBehavior());
this.LayoutRoot.Children.Add(b);

  • Pourquoi utiliser des behaviors ?

L’intérêt des behavior est multiple :

    • Réutilisation du code : Le code de comportement n’est pas lié à un contrôle défini mais à une famille de contrôle. Cela évite de réécrire le code à chaque fois.
    • Instanciation directement dans le XAML (donc utilisable dans Blend par un designer)
    • Intégration dans Blend : Blend 3/4 gère les behaviors nativement et permet d’ajouter facilement ses propres behaviors.
  • Saupoudrons avec un peu d’animations …  

On modifie le ResizeBehavior afin d’avoir une animation un peu plus sympa.

    public class ResizeBehavior : Behavior<FrameworkElement>
    {
        public ResizeBehavior()    {}

        protected override void OnAttached()
        {
            base.OnAttached();
            //Code
            this.AssociatedObject.MouseLeftButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
            this.AssociatedObject.MouseRightButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseRightButtonDown);
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            //Code
            this.AssociatedObject.MouseLeftButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
            this.AssociatedObject.MouseRightButtonDown -= new MouseButtonEventHandler(AssociatedObject_MouseRightButtonDown);
        }

        void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Storyboard sb = new Storyboard();
            DoubleAnimation da = new DoubleAnimation()
            {
                From = this.AssociatedObject.Width,
                To = this.AssociatedObject.Width * 1.5,
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                EasingFunction = new ElasticEase()
            };
            Storyboard.SetTarget(da, this.AssociatedObject);
            Storyboard.SetTargetProperty(da, new PropertyPath("(Width)"));
            sb.Children.Add(da);

            DoubleAnimation da2 = new DoubleAnimation()
            {
                From = this.AssociatedObject.Height,
                To = this.AssociatedObject.Height * 1.5,
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                EasingFunction = new ElasticEase()
            };
            Storyboard.SetTarget(da2, this.AssociatedObject);
            Storyboard.SetTargetProperty(da2, new PropertyPath("(Height)"));
            sb.Children.Add(da2);

            sb.Begin();
        }

        void AssociatedObject_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            Storyboard sb = new Storyboard();
            DoubleAnimation da = new DoubleAnimation()
            {
                From = this.AssociatedObject.Width,
                To = this.AssociatedObject.Width / 1.5,
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                EasingFunction = new ElasticEase()
            };
            Storyboard.SetTarget(da, this.AssociatedObject);
            Storyboard.SetTargetProperty(da, new PropertyPath("(Width)"));
            sb.Children.Add(da);

            DoubleAnimation da2 = new DoubleAnimation()
            {
                From = this.AssociatedObject.Height,
                To = this.AssociatedObject.Height / 1.5,
                Duration = new Duration(new TimeSpan(0, 0, 1)),
                EasingFunction = new ElasticEase()
            };
            Storyboard.SetTarget(da2, this.AssociatedObject);
            Storyboard.SetTargetProperty(da2, new PropertyPath("(Height)"));
            sb.Children.Add(da2);

            sb.Begin();
        }
    }

Code XAML Associé :

<UserControl x:Class="BehaviorDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:local="clr-namespace:BehaviorDemo"  >
    <StackPanel x:Name="LayoutRoot" Background="White">
        <Rectangle Fill="Red" Width="200" Height="100">
            <i:Interaction.Behaviors>
                <local:ResizeBehavior />
            </i:Interaction.Behaviors>
        </Rectangle>
        <Ellipse Fill="Yellow" Width="200" Height="100">
            <i:Interaction.Behaviors>
                <local:ResizeBehavior />
            </i:Interaction.Behaviors>
        </Ellipse>
    </StackPanel>
</UserControl>
  • … et voyons le résultat :

Voici des copies d”écrans du résultats :

behaviorAvant behaviorApres

Gauche : Après chargement / Droite : Après avoir cliqué avec le bouton gauche sur le rectangle rouge et l’ellipse jaune.

Ce post n’était qu’une introduction sur les behaviors. Je vais surement continuer sur ce sujet dans les prochains jours (notamment sur les behaviors/triggers et leurs applications plus business).

Si vous avez des questions et/ou remarques et/ou sujet de futur post, n’hésitez pas à poster un commentaire …

Remarque : Afin d’anticiper certains commentaires, je précise qu’il aurait été possible de créer l’animation dans le OnAttached et non en créer une nouvelle à chaque click :) . Je referais peut être l’exemple afin de le rendre plus propre. Toutefois, le but de cet article est de montrer le principe des behaviors.

05/05/2009

Introduction à Small Basic en français.

Classé dans : .NET, Débutant, Small Basic — Mots-clefs :, , , , , , — sebastiencourtois @ 09:21

Après plusieurs semaines de travail, une équipe de MSP a traduit la documentation anglaise de Small Basic en français.

Pour ceux qui ne connaissent pas Small Basic, il s’agit un projet de Microsoft fournissant aux débutants en programmation un environnement simple pour commencer doucement dans ce monde parfois obscur. Avec une syntaxe proche du Visual Basic, ce langage contient de nombreuses classes simplifiées fournissant facilement les fonctionnalités réellement nécessaires aux débutants.

SB

Petit tour de l’équipe qui a réalisé la traduction :

Au passage, un grand merci à Florie Perouze de l’équipe Education de Microsoft France qui nous a permis de réaliser ce projet.

Site officiel Small Basic

Blog officiel Small Basic

Documentation Français

24/04/2009

[Virtual Earth] Développement Microsoft Virtual Earth Web Services – Part 4 : Route Service

Classé dans : .NET, Débutant, Virtual Earth — Mots-clefs :, , , , , — sebastiencourtois @ 15:01

Liens des posts précédents :

  1. Introduction à Microsoft Virtual Earth (Part 1)
  2. Géocoding / Géocoding Inverse (Part 2)
  3. Récupération des images satellites (Part 3)
  4. Calcul d’itinéraires (Part 4)

Suite de notre grande exploration du Web Service Virtual Earth. Aujourd’hui, nous allons nous intéresser au calcul d’itinéraire. Cette fonctionnalité de Virtual Earth permet de calculer le trajet entre une série de point de contrôles (Waypoints). Il est possible de régler les options du calcul d’itinéraire (type de déplacement, trajet le plus rapide, le plus court …).

Comme d’habitude, il faut ajouter faire “Add Service Reference” avec l’adresse : http://staging.dev.virtualearth.net/webservices/v1/routeservice/routeservice.svc 

Remarque : Afin de fonctionner, il est nécessaire régler certains paramètres WCF de ce service. Dans le fichier app.config (ou web.config ou encore ServiceReference.clientconfig selon le type de projet) de l’application, dans la partie contenant les Binding WCF, il faut trouver le binding lié à RouteService (en général : <binding name="BasicHttpBinding_IRouteService") puis modifier l’attribut maxReceivedMessageSize (mettre la valeur 9000000 à la place de celle par défaut) et l’attribut maxBufferSize (mettre la valeur 9000000 à la place de celle par défaut). Cela est du au fait que RouteService renvoie un nombre d’informations supérieur à ce dont WCF est habitué à manipuler.

Ce service contient une classe principale RouteServiceClient  qui contient deux méthodes :

      • CalculateRoute(RouteRequest request) : Calcul d’un itinéraire entre plusieurs points
      • CalculateRoutesFromMajorRoads(MajorRoutesRequest request) : Calcul d’itinéraires partant des routes principales vers un point d’arrivée spécifié (carte d’accès).

 


  • Calcul d’un itinéraire entre plusieurs points

Le calcul d’itinéraire entre plusieurs points à besoin de deux informations : Les points de passages et là façons de calculer le trajet. Ces deux informations sont contenues dans la classe RouteRequest qui sera passer en paramètre de la méthode CalculateRoute de RouteServiceClient.

Les points de passages doivent être fournis dans la propriété WayPoints de  RouteRequest. Cette propriété prend un tableau de WayPoint, un WayPoint n’étant composé que d’une localisation GPS (latitude/longitude/altitude) et d’une propriété Description pour donner un titre au point de passage.

Les options de calcul d’itinéraire se trouvent dans la propriété Options. Cette propriété est de type RouteOptions et contient les champs suivantes :

Nom de la propriété Description
Mode Type de déplacement.
Enumération TravelMode :
Driving : Voiture (par défaut)
Walking : A pied (ne marche pas pour tous les pays)
Optimization Méthode de calcul du trajet. Marche en combinaison avec TrafficUsage
Enumération RouteOptimization :

MinimizeDistance : Distance minimale
MinimizeTime : Temps minimal

RoutePathType Type de données brutes renvoyées (en plus des données d’itinéraires)
Enumération RoutePathType :
None : Aucune donnée (par défaut)
Points : Un ensemble de points décrivant le chemin de la route.
TrafficUsage Type d’adaptation du trajet en fonction du trafic
Enumération TrafficUsage :

None : On ne tient pas compte du trafic (par défaut)

TrafficBasedRouteAndTime : On change le trajet pour minimiser le temps si nécessaire

TrafficBasedTime : On ne change pas la route mais on tient compte du trafic lors du calcul du trajet.

Combinaisons de Optimization et TrafficUsage :

Optimization TrafficUsage Description

MinimizeDistance

None

Trajet le plus direct (en distance) sans prendre en compte le trafic.

MinimizeDistance

TrafficBasedRouteAndTime

Une Exception lancée. Je pense que c’est du au fait que changer la route d’un trajet le plus direct en fonction du trafic => trajet plus long => incohérence.

MinimizeDistance

TrafficBasedTime

Trajet le plus direct (en distance) en tenant compte du trafic dans les calculs de durée (le trajet n’est pas changé… on passera dans les bouchons quand même :) ).

MinimizeTime

None

Trajet le plus rapide (en temps) sans prendre en compte le trafic.

MinimizeTime

TrafficBasedRouteAndTime

Trajet le plus rapide (en temps) en tenant compte du trafic et en le modifiant le cas échéant.

MinimizeTime

TrafficBasedTime

Trajet le plus rapide (en temps) en tenant compte du trafic dans les calculs de temps de trajet (la route n’est pas modifié en fonction du trafic).

Un petit code vaut mieux qu’un long discours : (les variables positionWinwise, positionMSFUniv, positionMSF sont des coordonnées géographiques calculées avant ce code)

RouteServiceClient rsc = new RouteServiceClient();
RouteRequest rq = new RouteRequest();
rq.Credentials = new VETutorial.RouteService.Credentials();
rq.Credentials.Token = token;
rq.Culture = "fr-FR";
rq.Waypoints = new Waypoint[]
{
	new Waypoint()
	{
		Description = "Winwise (Départ)",
		Location = new VETutorial.RouteService.Location()
		{
			Longitude = positionWinwise.Longitude,
			Latitude = positionWinwise.Latitude,
		}
	},
	new Waypoint()
	{
		Description = "Microsoft France (Rue de l'université)",
		Location =  new VETutorial.RouteService.Location()
		{
			Longitude = positionMSFUniv.Longitude,
			Latitude = positionMSFUniv.Latitude,
		}
	},
	new Waypoint()
	{
		Description = "Microsoft France (Rue du québec)",
		Location =  new VETutorial.RouteService.Location()
		{
			Longitude = positionMSF.Longitude,
			Latitude = positionMSF.Latitude,
		}
	},
};
rq.Options = new RouteOptions()
{
	Mode = TravelMode.Driving,
	Optimization = RouteOptimization.MinimizeTime,
	RoutePathType = RoutePathType.Points,
	TrafficUsage = TrafficUsage.TrafficBasedRouteAndTime,
};
RouteResponse routeResult = rsc.CalculateRoute(rq);

Comme vous pouvez voir dans ce code, on peut définir des informations supplémentaires comme les données d’authentifications ou encore la culture (langue) du résultat.

En réponse à l’appel de la méthode CalculateRoute, on obtient une classe de réponse de type RouteResponse contenant un champ ResponseSummary (déjà décrit dans les posts précédents) et un champ Result de type RouteResult contenant les informations sur le trajet.

La classe RouteResult fonctionne en arborescence :

Un RouteResult contient des Legs: Un Leg  est un trajet entre deux points de passages. Si plusieurs points de passages alors plusieurs Legs dans le RouteResult

Un Leg contient un tableau d’ItineraryItem (dans la propriété Itinerary) : Un ItineraryItem contient les informations entre deux changements de directions.

Chaque RouteResult, Leg et ItineraryItem contient une propriété Summary contenant le rectangle de leur localisation (propriété BoundingRectangle) ainsi que le temps de parcours en secondes (propriété TimeInSeconds) et la longueur de ce parcours dans la propriété Distance (par défaut en KM mais réglable dans la propriété UserProfile de la requête).

En dehors de ces données, la classe RouteResult contient aussi les données brutes du chemin dans la propriété RoutePath. Il contient un tableau de points géographiques représentant le trajet à faire. Cette propriété est la raison pour laquelle les données renvoyés par le service sont énormes. N’activer cette propriété que si nécessaire (désactivé par défaut : propriété RoutePathType de la requête).

La classe Leg a aussi deux propriétés spécifiques : ActualStart, ActualEnd. Ces propriétés indiquent les positions géographiques de départ et d’arrivés du Leg.

La classe ItineraryItem contient les propriétés suivantes :

Nom de la propriété Description
CompassDirection Orientation du déplacement (chaines de caractères “Nord” …)
Hints Indication permettant de mieux repérer le changement de direction (exemple : “la rue précédente est …”)
Location Position du changement de direction
ManeuverType Type de changement (tourner à gauche, droite, demi tour …)
Text Texte en format XML indiquant le changement à faire (Exemple : “<VirtualEarth:Action>Quitter</VirtualEarth:Action> <VirtualEarth:RoadName>Rue Gaillon</VirtualEarth:RoadName>”)
Warnings Danger sur la route. Lié aux infos traffic, cette propriété indique s’il y a un évènement sur votre trajet (accident, travaux …)

Voici le code d’affichage d’un trajet :

Console.WriteLine("--- Itinéraire  ---");
Console.WriteLine("Temps de calcul : "+ sw.Elapsed);
int i = 1;
foreach (var leg in routeResult.Result.Legs)
{
	var duree = new TimeSpan(0,0,(int)leg.Summary.TimeInSeconds);
	Console.WriteLine("-----    Partie " + i + " ------");
	Console.WriteLine("De (" + leg.ActualStart.Latitude +","+leg.ActualStart.Longitude+") à (" + leg.ActualEnd.Latitude +","+leg.ActualEnd.Longitude+")");
	Console.WriteLine("Distance " + leg.Summary.Distance + " km");
	Console.WriteLine("Temps estimé : " + duree.Hours + " h " + duree.Minutes + " min " + duree.Seconds+ " sec");
	Console.WriteLine("************ TRAJET ************");
	int j = 1;
	double distanceDone = 0;
	foreach (var turn in leg.Itinerary)
	{
		var duree2 = new TimeSpan(0, 0, (int)turn.Summary.TimeInSeconds);
		string HumanInfos = StripXMLFromText(turn.Text);
		Console.WriteLine("Indication " + j + " " + (distanceDone) + " km ["+duree2.Hours+":" + duree2.Minutes + ":" + duree2.Seconds+ "] : " + HumanInfos);
		distanceDone += turn.Summary.Distance;
		j++;
	}
	Console.WriteLine("----- Fin Partie " + i + " -----");
	i++;
}

Le résultat obtenu est le suivant :

— Itinéraire  —

Temps de calcul : 00:00:04.3373465

—–    Partie 1 ——

De (48,868681,2,334231) à (48,861122,2,309237)

Distance 2,788 km

Temps estimé : 0 h 7 min 46 sec

************ TRAJET ************

Indication 1 0 km [0:0:11] : Quitter Rue Gaillon

Indication 2 0,13 km [0:0:49] : Prendre à gauche Avenue de l’Opéra

Indication 3 0,156 km [0:0:53] : Tourner à droite Rue Saint-Roch

Indication 4 0,569 km [0:1:17] : Tourner à droite Rue de Rivoli

Indication 5 1,215 km [0:0:5] : Continuer tout droit Place de la Concorde

Indication 6 1,257 km [0:0:9] : Continuer à gauche Place de la Concorde

Indication 7 1,338 km [0:0:58] : Au rond-point prendre 4

Indication 8 1,61 km [0:0:57] : Prendre à droite Pont de la Concorde

Indication 9 1,835 km [0:1:17] : Tourner à droite Quai d’Orsay

Indication 10 2,531 km [0:0:6] : Continuer à droite Quai d’Orsay

Indication 11 2,584 km [0:0:2] : Tourner à gauche Quai d’Orsay

Indication 12 2,61 km [0:0:56] : Continuer tout droit Rue Surcouf

Indication 13 2,777 km [0:0:6] : Tourner à droite Rue de l’Université

Indication 14 2,788 km [0:0:0] : Arrivée Microsoft France (Rue de l’université)

—– Fin Partie 1 —–

—–    Partie 2 ——

De (48,861122,2,309237) à (48,6900826,2,2157051)

Distance 33,094 km

Temps estimé : 0 h 33 min 46 sec

************ TRAJET ************

Indication 1 0 km [0:0:36] : Quitter Rue de l’Université

Indication 2 0,28 km [0:0:28] : Tourner à droite Rue Malar

Indication 3 0,439 km [0:1:9] : Tourner à gauche Quai d’Orsay

Indication 4 0,685 km [0:2:26] : Continuer tout droit Quai Branly

Indication 5 2,079 km [0:1:27] : Le nom de la voie change Quai de Grenelle

Indication 6 2,877 km [0:2:34] : Le nom de la voie change Quai André Citroën

Indication 7 4,109 km [0:0:33] : Au rond-point prendre 1

Indication 8 4,378 km [0:0:55] : Le nom de la voie change Quai d’Issy-les-Moulin

eaux

Indication 9 4,925 km [0:5:21] : Voie Ad/15 prendre à gauche Boulevard Périphéri

que Lyon

Indication 10 10,008 km [0:2:57] : Prendre à droite E15 Nantes-Bordeaux/Aéroport

Orly-Rungis/Évry-Lyon

Indication 11 12,67 km [0:2:14] : Prendre à droite E15 Véhicules Lents-Toutes Di

rections

Indication 12 15,678 km [0:2:11] : Continuer à gauche E15

Indication 13 19,625 km [0:7:10] : Prendre à droite E5/E50

Indication 14 30,882 km [0:0:54] : 9 prendre à droite Chartres Par RN/Les Ulis/Z

.A. de Courtaboeuf/Villejust

Indication 15 31,465 km [0:0:31] : Prendre à droite D118

Indication 16 31,913 km [0:0:15] : Prendre à droite

Indication 17 32,063 km [0:0:36] : Tourner à droite Avenue de la Baltique

Indication 18 32,32 km [0:1:29] : Tourner à droite Avenue du Québec

Indication 19 33,094 km [0:0:0] : Arrivée Microsoft France (Rue du québec)

—– Fin Partie 2 —–

J’avoue que c’est moins sexy qu’une belle carte en 3D avec le chemin en surbrillance mais c’est mieux que rien. Je vais essayer de voir comment afficher ce trajet sur une carte (avec le RoutePath de RouteResult je pense). Sujet d’un prochain post.


  • Calcul d’itinéraires depuis des routes principales

Le principe de la méthode CalculateRoutesFromMajorRoads est de fournir les trajet allant (ou revenant) d’un point nommé Destination vers les grands axes routiers. Au niveau de la requête, la plupart des propriétés décrites ci dessus fonctionnent avec la classe MajorRoutesRequest. Deux différences toutefois :

La propriété Waypoints disparait au profit d’une propriété Destination ne permettant de définir qu’un WayPoint.

La propriété Options devient du type MajorRoutesOptions et cela ajoute un nouveau paramètre d’options : la propriété booléenne ReturnRoutes indique si on souhaite calculer les routes entre la destination et les routes majeurs (true) où si l’on souhaite uniquement les points d’entrées sur les routes majeures les plus proches (false. valeur par défaut).

MajorRoutesRequest mrr = new MajorRoutesRequest();
mrr.Credentials = new VETutorial.RouteService.Credentials();
mrr.Credentials.Token = token;
mrr.Destination = new Waypoint()
{
	Description = "Winwise (Départ)",
	Location = new VETutorial.RouteService.Location()
	{
		Longitude = positionWinwise.Longitude,
		Latitude = positionWinwise.Latitude,
	}
};
mrr.Culture = "fr-FR";
mrr.Options = new MajorRoutesOptions()
{
	Mode = TravelMode.Driving,
	Optimization = RouteOptimization.MinimizeTime,
	RoutePathType = RoutePathType.Points,
	TrafficUsage = TrafficUsage.TrafficBasedRouteAndTime,
	ReturnRoutes = true
};
MajorRoutesResponse MRresult = rsc.CalculateRoutesFromMajorRoads(mrr);

Au niveau de la réponse, on reste sur les mêmes types de données. On a ainsi la propriété Routes contenant des RouteResult (décrit plus haut) donnant les itinéraires entre le point destination et une route majeure. La propriété StartingPoints est un tableau de positions géographiques indiquant les entrées sur les routes majeures environnantes.

Ici se conclut ce post sur les calculs d’itinéraire sous Virtual Earth. Il ne reste plus qu’un service à découvrir : Le SearchService puis nous terminerons par une belle application WPF reprenant tout ce que l’on a appris aux cours de ces 5 posts.

N’hésitez pas à mettre des commentaires pour enrichir ces tutoriaux.

08/04/2009

[Virtual Earth] Utilisation des services web Virtual Earth derrière un proxy

Classé dans : .NET, Débutant, Virtual Earth — Mots-clefs :, , , , , — sebastiencourtois @ 16:17

Si, comme moi, vous travaillez sur Virtual Earth derrière un proxy d’entreprise, il y a un moyen de fournir les informations nécessaire pour laisser passer les requêtes Virtual Earth.

Pour cela, on utilise la classe WebProxy :

CommonService common = new VETutorial.TokenService.CommonService();
common.Credentials = new NetworkCredential("VE_login", "VE_password");

WebProxy wbproxy = new WebProxy("Url du proxy",8080); // 8080 est le port d'écoute du proxy dans mon exemple
wbproxy.Credentials = new NetworkCredential("Login pour le proxy", "Password pour le proxy");

common.Proxy = wbproxy;

Si on ne fait pas cette manipulation, on récupère une erreur 407 avec une WebException nous annonçant que nous ne sommes pas authentifiés sur le proxy. (Message exact: “The request failed with HTTP status 407: Proxy Authentication Required.”).

En espérant que cela vous servira :)

[Virtual Earth] Développement Microsoft Virtual Earth Web Services – Part 3 : Imagery Service

Classé dans : Débutant, Virtual Earth — Mots-clefs :, , , , , , — sebastiencourtois @ 16:00

Liens des posts sur le sujet sur ce blog :

  1. Introduction à Microsoft Virtual Earth (Part 1)
  2. Géocoding / Géocoding Inverse (Part 2)
  3. Récupération des images satellites (Part 3)
  4. Calcul d’itinéraires (Part 4)

Dans ce post, nous allons parler la partie la plus intéressante de Virtual Earth : La récupération des images satellites.

Tout d’abord, il est nécessaire d’ajouter une référence vers le service d’imagerie de Virtual Earth : http://staging.dev.virtualearth.net/webservices/v1/imageryservice/imageryservice.svc. On instancie ensuite une classe proxy nommée ImageryServiceClient afin d’avoir accès au service.

ImageryServiceClient imagerySvc = new ImageryServiceClient();

On peut utiliser ce service pour récupérer les images de deux manières : Récupération des images à la demande et Récupération des images à la volée (Tiles).

  • Récupération des images à la demande : MapUriRequest

Il est possible de récupérer des images satellites en fournissant des coordonnées géographiques puis de spécifier des informations supplémentaires pour avoir l’image souhaitée.

On utilise pour cela la classe MapUriRequest que l’on va fournir en paramètre de la méthode GetMapUri de la classe ImageryServiceClient.

Cette classe MapUriRequest est composée des propriétés spécifiques suivantes : (je ne mets que les propriétés spécifiques aux requêtes MapUri. Pour les autres paramètres, voir les posts précédents).

Nom de la propriété Description
Center Coordonnées géographiques du point central de l’image (classe Location avec des propriétés Latitude/Longitude/Altitude)
MajorRoutesDestination Paramètre ne fonctionnant pas chez moi et dont je ne comprends pas l’intérêt. Voir la documentation MSDN.
Options Options de paramétrage de l’image sous la forme d’une classe MapUriOptions. Cette classe est décrite par la suite.
Pushpins Ajout de puce pour indiquer les emplacements importants. Il s’agit d’un tableau de Pushpin. Il est possible de définir, pour chaque puce, sa position, son nom ainsi que l’icône à utiliser parmi une liste prédéfini par Microsoft (Liste des icones disponible par défaut). Le nombre maximum d’icones est de 10 par image.
    • MapUriOptions

La classe MapUriOptions permet de définir un certain nombre de paramètres sur l’image que l’on souhaite obtenir.

Nom de la propriété Description
DisplayLayers Il est possible de superposer sur l’image des calques d’informations. Pour cela, il suffit de fournir une chaine de caractères contenant la liste des calques disponibles.
ImageSize Taille de l’image souhaitée (valeurs entre 80 et 830 pixels sur les deux dimensions)
ImageType Format de l’image (GIF, JPEG, PNG). Par défaut, cela dépend du style de carte voulue. (voir la documentation MSDN)
PreventIconCollision Valeur booléen pour indiquer si l’on souhaite que les icones (pushpins) se superposent lorsqu’ils sont proche.
Style Type de carte parmi les suivantes : Route (Road), Aérien (Aerial), Aérien avec titre (AerialWithLabels).

Les images de type  Birdseye et BirdseyeWithLabel ne sont pas disponible sur le Web Service.
UriScheme Type d’URL : HTTP ou HTTPS
ZoomLevel Niveau de zoom compris entre 1 et 21. (1 étant le plus éloigné et 21 étant le plus proche de la cible).
    • Un exemple d’utilisation

N.B : Les variables positions et token sont reprises des exemples précédents.

MapUriRequest mapUriRequest = new MapUriRequest();
mapUriRequest.Credentials = new VETutorial.ImageryService.Credentials();
mapUriRequest.Credentials.Token = token;

mapUriRequest.Center  = new VETutorial.ImageryService.Location() { Latitude = position.Latitude, Longitude = position.Longitude };
mapUriRequest.Options = new MapUriOptions()
{
	ImageSize = new VETutorial.ImageryService.SizeOfint() {  Width=800, Height = 600},
	ImageType = ImageType.Jpeg,
	Style = MapStyle.AerialWithLabels,
	UriScheme = UriScheme.Https,
	ZoomLevel = 18
};
mapUriRequest.Pushpins = new Pushpin[] { new Pushpin() { Location = new VETutorial.ImageryService.Location() { Latitude = position.Latitude, Longitude = position.Longitude } } };

MapUriResponse reponseMapUrl = imagerySvc.GetMapUri(mapUriRequest);
string finalUrl = reponseMapUrl.Uri.Replace("{token}",token);

L’exemple ci-dessus, crée une requête MapUri et lui indique le token d’authentification à utiliser (ne pas oublier cette étape). On définit ensuite les différents paramètres comme la position centrale, taille et format de l’image et niveau de zoom. Après l’appel de la méthode GetMapUri, on obtient un objet MapUriResponse contenant une propriété Uri contenant un modèle d’URL pour récupérer l’image. Ce modèle permet de construire soit même l’url pour récupérer l’image.

Dans mon cas, je reçois l’url suivant : https://staging.tiles.virtualearth.net/api/GetMap.ashx?c=48.868681,2.334231&ppl=48.868681,2.334231&w=800&h=600&o=jpeg&b=h,mkt.en-US&z=18&token={token}

Sans rentrer dans les détails des différentes paramètres contenues dans cette url, nous nous intéresserons sur le dernier paramètre : &token={token}. Au sein des services Virtual Earth, le modèle URL contiennent des zones “à remplacer” toujours représenté par des {}. Dans ce cas précis, il faut remplacer le {token} par la valeur de notre token. La façon la plus simple pour remplacer ces zones est d’utiliser la méthode Replace de la classe String. On arrive finalement à une adresse finale qui pourra être utilisé dans un navigateur, WebClient ou autres pour récupérer l’image.

L’image obtenue est la suivante : GetMap

  Un des trucs sympas est que l’on peut mettre cet adresse sur la propriété Source des contrôles d’images (Winforms,ASP.NET …) et l’image est téléchargée directement et affichée.

  • Récupération des images à la volée (Tiles)

Demander à Virtual Earth de générer une image pour nous est pratique. Toutefois, il y a un certain cout en termes de performance qui fait que cela n’est utilisable que pour afficher des images ponctuellement. Un déplacement fluide sur une carte avec l’API MapURI n’est pas envisageable. C’est pourquoi Virtual Earth fournit une autre façon de récupérer des images : Le Tile System.

    • Qu’est ce que le Tile System ?

Un Tile est une image d’une taille fixe représentant une partie d’une image plus grande. Dans le cas de la cartographie, ces images représentent un morceau du monde et, une fois l’ensemble de ces Tiles assemblés, on obtient une image du monde. Au sein de Virtual Earth, ces Tiles font 256×256 pixels. Le nombre de Tiles représentant le monde dépend du zoom. Au zoom le plus haut, on a 4 Tiles. Au zoom suivant on a 16 Tiles et ainsi de suite. Chaque Tile est numéroté en fonction de son emplacement et son zoom. Ce concept est décrit par le schéma suivant :

tilesystem

Afin d’obtenir chacune des images, il est nécessaire de construire l’url vers l’image. On obtient l’url d’un Tile de la façon suivante :

https://staging.tiles.virtualearth.net/tiles/{type}{code}.jpeg?g={version}&mkt={culture}&token={token}

Nom Description
{type} Type de carte représenté par un caractère : h pour Hybrid (Aérien avec textes), a pour Aerial (Aérien), r pour Road (Carte routière)
{code} Identifiant du tile comme décrit dans le schéma ci-dessus
{version} Numéro de version de Virtual Earth (275 au moment de l’écriture de ce tutorial)
{culture} Culture à utiliser dans l’affichage du texte (‘en-US’, ‘fr-FR’ …)
{token} Token d’authentification

Exemple : https://staging.tiles.virtualearth.net/tiles/h0331.jpeg?g=275&mkt=fr-FR&token=12345  

h0331

    • Informations sur les Tiles

Il est possible d’avoir des informations d’un Tile en fonction de son emplacement (coordonnées géographiques). Pour cela, on utilise la classe ImageryMetadataRequest. Cette classe contient les  mêmes propriétés que la classe MapUriRequest décrit plus haut. La seule différence se trouve dans la propriété Options qui est une classe ImageryMetadataOptions. Elle contient les propriétés suivantes :

Nom de la propriété Description
Heading Orientation de la carte ( 0° = Nord en haut)
Location Centre de la carte (coordonnées géographiques)
ReturnImageryProviders Booléen indiquant si l’on souhaite récupérer les données sur les fournisseurs des cartes (copyrights …)
UriScheme Type d’URL : HTTP ou HTTPS
ZoomLevel Niveau de zoom
ImageryMetadataRequest metadataRequest = new ImageryMetadataRequest();
metadataRequest.Credentials = new VETutorial.ImageryService.Credentials();
metadataRequest.Credentials.Token = token;

metadataRequest.Style = MapStyle.AerialWithLabels;
metadataRequest.Options = new ImageryMetadataOptions()
{
	Heading = new VETutorial.ImageryService.Heading() { Orientation = 0 } ,
	Location = new VETutorial.ImageryService.Location() { Latitude = position.Latitude, Longitude = position.Longitude },
	ZoomLevel = 8,
	ReturnImageryProviders = true,
	UriScheme = UriScheme.Https
};

ImageryMetadataResponse resultImageMetadata = imagerySvc.GetImageryMetadata(metadataRequest);

En réponse de la méthode GetImageryMetadata, on récupère une classe  ImageryMetadataResponse  contenant une propriété Result étant représenté par un tableau de ImageryMetadataResult celui-ci contient les informations suivantes :

Nom de la propriété Description
ImageryProvider Liste des fournisseurs de données
ImageSize Taille de l’image (normalement 256×256 pixels)
ImageUri Schéma de l’URL pour récupérer l’image (voir plus loin dans ce post)
ImageUriSubDomains Liste des sous domaines disponible pour cette image
Vintage Date de validité des images
ZoomRange Zoom lié à l’image

L’une des informations importantes de cette structure de données est ImageUri. Il est nécessaire de la modifier comme décrit ci-dessus. Ainsi, dans mon exemple, on obtient : https://t1.staging.tiles.virtualearth.net/tiles/h12022001.jpeg?g=275&mkt={culture}&token={token}. Il suffit de remplacer {culture} et {token} pour avoir accès à l’image centrée sur la propriété Location définie dans la requête.

Ainsi se conclut la description du service d’imagerie de Virtual Earth. Comme certains d’entre vous l’auront remarqué, il manque de nombreuses fonctionnalités comme le BirdEye ou la 3D. Cela est du au fait que les services Web Virtual Earth ne sont pas aussi complète que le contrôle ASP.NET. Ces fonctionnalités arriveront par la suite.

Dans les prochains posts nous parlerons du service de calcul d’itinéraire ainsi que du moteur de recherche d’entités (magasins, écoles, points d’intérêt …) de Virtual Earth. Je vais aussi faire une démo intégrant l’ensemble des services décrit afin de montrer les possibilités de Virtual Earth de façon plus parlante. Cette démo sera surement en WPF ou en Silverlight (selon mes envies du moment).

N’hésitez pas à me faire des retours sur ces posts. Si vous avez des idées de sujets à traiter pour ce blog, merci de les indiquer dans les commentaires ci-dessous.

07/04/2009

[Virtual Earth] Développement Microsoft Virtual Earth Web Services – Part 2 : Géocoding Service

Classé dans : .NET, Débutant, Virtual Earth — Mots-clefs :, , , , — sebastiencourtois @ 20:40

Cet article est une suite de l’article se trouvant ici. Il était question de créer un compte Virtual Earth et se connecter en C# aux différents services Web de Virtual Earth.

Liens des posts sur le sujet sur ce blog :

  1. Introduction à Microsoft Virtual Earth (Part 1)
  2. Géocoding / Géocoding Inverse (Part 2)
  3. Récupération des images satellites (Part 3)
  4. Calcul d’itinéraires (Part 4)

L’article courant va parler de l’utilisation de un de ces services : Le Géocoding/Géocoding inverse.

Géocoding

Comme expliqué dans l’article précédent, le Géocoding est le processus permettant de retrouver des coordonnées géographiques d’un lieu à partir d’informations sur celui-ci (nom,adresse postale…). Un service de Virtual Earth est entièrement dédié à cette tache : Le GeocodeService.

Afin d’accéder aux informations de ce service, il faut d’abord générer la classe proxy permettant de communiquer avec ce service. Ce service étant un service WCF, il suffit d’ajouter la référence de service suivante (“Add Service Reference”) à votre projet : http://staging.dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc (nous travaillons toujours dans l’environnement de test).

Un fois la référence rajoutée, vous devriez avoir accès aux classes suivantes :

  • GeocodeServiceClient : Classe proxy principale gérant la connexion avec le service
  • GeocodeRequest : Classe permettant de fournir les données pour une requête de Géocoding
  • GeocodeResponse : Classe contenant les informations renvoyés par le service suite à un requête de Géocoding ou de Géocoding inverse.
  • ReverseGeocodeRequest : Classe permettant de fournir les données pour une requête de Géocoding inverse.

Fonctionnement générale d’une requête de Géocoding

Avant de commencer, il est nécessaire d’avoir un token d’authentification valide (voir le tutorial précédent). Ensuite, on utilise le code suivant :

GeocodeServiceClient geoCodeSvc = new GeocodeServiceClient();
GeocodeRequest geoCodeRequest = new GeocodeRequest();
geoCodeRequest.Credentials = new Credentials();
geoCodeRequest.Credentials.Token = token;
geoCodeRequest.Query = "Paris";
GeocodeResponse result = geoCodeSvc.Geocode(geoCodeRequest);

On crée le proxy vers le service de Géocoding en utilisant le constructeur par défaut. On crée ensuite une requête de Géocoding. On lui fournit les données d’authentification au travers de la propriété Credentials de la requête. On remplit la donnée token afin de prouver notre identité au serveur Virtual Earth. On remplit ensuite les données qui seront utilisées pour la requête (on utilise ici la propriété Query par simplicité, mais nous verrons plus loin qu’il y a beaucoup plus de possibilités). La requête proprement dite se réalise par la méthode Geocode de la classe proxy GeocodeServiceClient en lui fournissant les données de la requête. On récupère le résultat dans une classe GeocodeResponse.

Découverte de la classe GeocodeRequest

Pierre angulaire du système de Géocoding, la classe GeocodeRequest regroupe de nombreuses propriétés permettant d’affiner sa recherche géographique au maximum.

  • Query

La propriété la plus simple est la propriété Query. Cette propriété est une chaine de caractères dans laquelle il est possible de rentrer plusieurs mot clés (nom de pays,ville …). Cette propriété sera ensuite comparé par Virtual Earth avec la base de données d’entités géographiques afin de renvoyer les résultats les plus pertinents.

  • Options

Il est possible de restreindre ces résultats grâce à la propriété Options. Cette propriété possède une propriété Count permettant de définir le nombre maximal de réponses (5 par défaut). Il est aussi possible de définir des filtres (propriété Filters). Ces filtres permettent de supprimer des réponses en fonction de critères supplémentaires. Ces filtres/critères sont disponible au travers de classes spécialement conçues à cette effet. Ainsi si l’on souhaite n’avoir que les résultats avec une note de confiance forte, on utilisera le code suivant :

GeocodeService.ConfidenceFilter[] filters = new GeocodeService.ConfidenceFilter[1];
filters[0] = new GeocodeService.ConfidenceFilter();
filters[0].MinimumConfidence = GeocodeService.Confidence.High;
GeocodeService.GeocodeOptions geocodeOptions = new GeocodeService.GeocodeOptions();
geocodeOptions.Filters = filters; 

Le dernier paramètre, ExtensionData, de la propriété Options est lié à WCF et ne concerne pas ce tutorial.

  • Culture : Cette propriété permet de définir la langue dans laquelle vous souhaitez que les informations soient retournées. Par défaut, c’est l’anglais (‘en-US’).
  • ExecutionOptions : Cette propriété n’a qu’un booléen suppressFault. Par défaut à false, cette variable indique si vous souhaitez récupérer les erreurs Virtual Earth (false = je souhaite récupérer les erreurs).
  • UserProfile : Cette propriété permet de définir des informations sur l’utilisateur courant. On peut ainsi donner les informations concernant sa position, son déplacement, s’il est sur un appareil mobile … Cela permet au serveur de choisir les informations à retourner afin qu’elle soit le plus proche possible de ce que souhaite l’utilisateur.
  • Address

Après Query, c’est la propriété la plus important de GeocodeRequest. Cette propriété permet de rentrer les informations pour trouver les coordonnées d’une adresse postale en fonction de critères tel que la rue, la ville, le pays, le code postal. Les informations peuvent être incomplètes (fournir le code postal uniquement avec l’adresse suffit à localiser le lieu par exemple). Un tableau sur le site MSDN décrit les données minimales à fournir pour avoir une localisation précise : http://msdn.microsoft.com/en-us/library/cc966788.aspx.

Attention : Au sein d’une requête GeocodeRequest, il faut remplir, au minimum, le champ Query ou le champ Address afin d’obtenir une réponse de la part du serveur. Les autres champs sont optionnels

Afin de tester tout cela, nous allons prendre l’exemple de l’emplacement du siège de ma société Winwise (Winwise – 16 rue Gaillon,75002 PARIS). Je souhaite un seul résultat et je veux qu’il soit “sûr à 100 %”. On écrit donc le code suivant :

GeocodeServiceClient geoCodeSvc = new GeocodeServiceClient();
GeocodeRequest geoCodeRequest = new GeocodeRequest();

geoCodeRequest.Credentials = new VETutorial.GeoCodeService.Credentials();
geoCodeRequest.Credentials.Token = token;

ConfidenceFilter[] filters = new ConfidenceFilter[1] { new ConfidenceFilter() { MinimumConfidence = VETutorial.GeoCodeService.Confidence.High } };
geoCodeRequest.Options = new GeocodeOptions() { Count = 1, Filters = filters};

geoCodeRequest.Address = new Address()
{
	AddressLine = "16 rue Gaillon",
	PostalCode = "75002",
	PostalTown = "Paris",
};

GeocodeResponse result = geoCodeSvc.Geocode(geoCodeRequest);

J’obtiens le résultat suivant : Longitude : 2.334231 Latitude : 48.868681 ce qui correspond bien à l’emplacement de notre société comme le montre cette image ci-dessous prise avec le service d’imagerie de Virtual Earth (sujet d’un prochain tutorial).

winwise

Analyse  de la classe GeocodeResponse

Nous allons maintenant apprendre à traiter la réponse de Virtual Earth suite à une requête de Géocoding. Cette réponse possède deux propriétés : ResponseSummary et Results.

  • ResponseSummary

Cette propriété regroupe les informations sur le déroulement de la requête. On retrouve les informations sur les droits (propriété Copyright), l’identifiant de la requête (propriété TraceId), le statut de la requête (propriété StatusCode) indiquant s’il y a eu une erreur durant la requête. Si c’est le cas, la propriété FaultReason donne l’erreur lancée du coté Virtual Earth. Enfin la propriété AuthentificationResultCode indique si les informations concernant l’authentification sont valide.

  • Results

C’est dans cette propriété que l’on retrouve les résultats de la requête proprement dite. Il s’agit d’un tableau de GeocodeResult où chacune des cases représente une réponse possible. La taille du tableau peut varier de 0 jusqu’au nombre fourni dans la propriété Count des options de la requête.

Chacun des GeocodeResult contient les informations suivantes :

    • L’adresse du point dans la propriété Address.
    • Les deux points géographiques (haut gauche et bas droit) permettant d’avoir la meilleure vue de ce que l’on souhaite voir (Propriété BestView)
    • Le niveau de confiance du résultat (Propriété Confidence)
    • Un représentation en chaine de caractères de la confiance du résultat (Propriété MatchCode)
    • Un nom pour le résultat (propriété DisplayName)
    • Le type d’entité ciblé par le résultat sous forme d’une chaine de caractère (propriété EntityType)
    • Des positions géographiques possibles pour le résultat (propriété Locations). Cette propriété contient aussi le type de calcul de calcul a été utilisé (généralement “Interpolation”).

Parlons un peu du géocoding inverse : ReverseGeocodeRequest

Bien que beaucoup moins utilisé que le Géocoding classique, le Géocoding inverse est le processus permettant de retrouver une adresse depuis des coordonnées géographiques. Pour cela, on utilise la classe ReverseGeocodeRequest. Celle ci contient les mêmes champs que GeocodeRequest décrit précédemment sauf Query et Address (ce qui est normal vu que ce n’est pas l’objet de la requête). En revanche, on remarque l’apparition d’une propriété Location sur laquelle on peut indiquer la latitude, longitude, altitude désiré.

ReverseGeocodeRequest rgr = new ReverseGeocodeRequest();
rgr.Credentials = new VETutorial.GeoCodeService.Credentials();
rgr.Credentials.Token = token;

rgr.Location = new VETutorial.GeoCodeService.Location() { Longitude = 2.334231, Latitude = 48.868681 };

GeocodeResponse result2 = geoCodeSvc.ReverseGeocode(rgr);

Le résultat de la requête est, comme pour le Géocoding, un GeocodeResponse.

La suite au prochain épisode …

Dans le prochain article, nous parlerons de la récupération de cartes (images) au travers du ImageryService. Un autre article est prévu sur le calcul d’itinéraire entre N points.

N’hésitez pas à me faire des commentaires sur ces articles (bons ou mauvais je m’en fiche du moment que c’est constructif et que ça me permet de faire des articles meilleurs à l’avenir).

Articles plus anciens »

Publié sur WordPress.