Suite et fin de cette série d’articles… Après une 1ère partie sur l’utilisation du contrôle Map dans une application Metro , puis une 2ème partie sur la recherche de localités via les services REST Bing Maps et le Search Charm , nous allons maintenant intégrer le calcul d’itinéraire et l’affichage des incidents de circulation.
Commençons par le plus simple, l’affichage des incidents de circulation. Bing Maps met à disposition un service REST permettant de récupérer ce type d’information. Suivez ce lien pour une documentation complète du service d’incidents.
L’URL de base du service Bing Maps est la suivante : http://dev.virtualearth.net/REST/v1/Traffic/Incidents/
Il faut ensuite ajouter un paramètre représentant la zone géographique. Une zone géographique est représentée par un rectangle (Latitude Sud, Longitude Ouest, Latitude Nord, Longitude Est).
On peut également ajouter des paramètres supplémentaires optionnels afin de filtrer les résultats (severity : gravité des incidents, type : type d’incidents).
Pour l’intégration de ces informations dans notre application Bing Maps Metro, nous allons nous connecter à l’évènement ViewChangeEnded du contrôle Map :
- < Maps : Map x : Name = »myMaps »
- Credentials = »Your Bing Maps Key »
- MapType = »Aerial »
- ShowTraffic = »True »
- ShowScaleBar = »False »
- ZoomLevel = »17″
- ViewChangeEnded = »myMaps_ViewChangeEnded »
- >
- < Maps : Map.Center >
- < Maps : Location Latitude = »48.830617″ Longitude = »2.261645″ />
- </ Maps : Map.Center >
- </ Maps : Map >
Pour ne pas surcharger la carte avec trop d’informations, nous n’afficherons les incidents que dans le cas où le niveau de zoom est supérieur à 10 et si l’option d’affichage du trafic est activée. A chaque modification de la vue (manipulation de la carte par l’utilisateur), nous supprimons tous les points d’intérêt représentant des incidents, pour ensuite ajouter les nouveaux :
- private List < Pushpin > incidentPushpins = new List < Pushpin >();
- private async void myMaps_ViewChangeEnded( object sender, ViewChangeEndedEventArgs e)
- {
- if (myMaps.ZoomLevel > 10 && myMaps.ShowTraffic == true )
- {
- foreach ( var item in incidentPushpins)
- {
- if (myMaps.Children.Contains(item))
- myMaps.Children.Remove(item);
- }
- incidentPushpins.Clear();
- …
- }
- }
La zone géographique représentant le paramètre mapArea du service REST Bing Maps peut être composée de cette manière en utilisant la propriété TargetBounds du contrôle Map :
- string mapArea = myMaps.TargetBounds.South + « , » + myMaps.TargetBounds.West + « , » + myMaps.TargetBounds.North + « , » + myMaps.TargetBounds.East;
L’URL pour interroger le service de récupération des incidents de circulation est la suivante :
- string uri = « http://dev.virtualearth.net/REST/v1/Traffic/Incidents/ » + mapArea + « ?key= » + BingMapsKey;
Il nous faut maintenant instancier un objet HttpClient afin d’envoyer notre requête et de récupérer le résultat :
- HttpClient client = new HttpClient ();
- var result = await client.GetStringAsync(uri);
Le résultat est un flux Json contenant la liste des incidents dans la zone demandée :
{ "authenticationResultCode": "ValidCredentials", "brandLogoUri": "http://dev.virtualearth.net/Branding/logo_powered_by.png", "copyright": "Copyright © 2012 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.", "resourceSets": [ { "estimatedTotal": 2, "resources": [ { "__type": "TrafficIncident:http://schemas.microsoft.com/search/local/ws/rest/v1", "point": { "type": "Point", "coordinates": [ 48.81982, 2.32425 ] }, "description": "entre Porte d'Orléans (E2) et Porte de Gentilly - Fermé.", "end": "/Date(1342258789000)/", "incidentId": 323566599, "lastModified": "/Date(1342085989000)/", "roadClosed": true, "severity": 4, "start": "/Date(1342085940000)/", "toPoint": { "type": "Point", "coordinates": [ 48.81912, 2.34364 ] }, "type": 8, "verified": true }, { "__type": "TrafficIncident:http://schemas.microsoft.com/search/local/ws/rest/v1", "point": { "type": "Point", "coordinates": [ 48.81982, 2.32425 ] }, "description": "entre Porte d'Orléans (E2) et Porte de Gentilly - Travaux sur tronçon.", "end": "/Date(1342389600000)/", "incidentId": 322895570, "lastModified": "/Date(1341847492000)/", "roadClosed": false, "severity": 2, "start": "/Date(1341847260000)/", "toPoint": { "type": "Point", "coordinates": [ 48.81912, 2.34364 ] }, "type": 9, "verified": true } ] } ], "statusCode": 200, "statusDescription": "OK", "traceId": "468e0ef9b5694d3e81d26623bbf5a3fe|AMSM001106|02.00.138.500|" }
Dans ce flux, nous devons récupérer et parcourir le tableau resources :
- var values = JsonValue .Parse(result);
- var incidents = values.GetObject()[ « resourceSets » ].GetArray()[0].GetObject()[ « resources » ].GetArray();
- foreach ( var item in incidents)
- {
- …
- }
Pour chacun des incidents, récupérer sa description et ses coordonnées :
- var description = item.GetObject()[ « description » ].GetString();
- var coordinates = item.GetObject()[ « point » ].GetObject()[ « coordinates » ].GetArray();
Et pour terminer, créer un objet de type Pushpin, le positionner et l’ajouter à la carte, puis ajouter un tooltip :
- Location location = new Location (coordinates[0].GetNumber(), coordinates[1].GetNumber());
- Pushpin pushpin = new Pushpin () { Text = « » };
- MapLayer .SetPosition(pushpin, location);
- myMaps.Children.Add(pushpin);
- ToolTipService .SetToolTip(pushpin, description);
- incidentPushpins.Add(pushpin);
Et voilà le résultat :
Pour mieux identifier ces points d’intérêt comme étant des incidents, il est possible de personnaliser le style des contrôles Pushpin.
Nous allons ajouter cette image dans notre solution :
Ensuite dans les ressources de la page MainPage nous ajoutons le ControlTemplate suivant :
- < ControlTemplate x : Key = »IncidentPushpin » TargetType = »Maps:Pushpin »>
- < Image Source = »Images/incident.png » Stretch = »None » HorizontalAlignment = »Left » />
- </ ControlTemplate >
Il nous faut maintenant appliquer ce template lorsque l’on crée les objets de type Pushpin :
- Pushpin pushpin = new Pushpin () { Text = « » };
- pushpin.Template = Resources[ « IncidentPushpin » ] as ControlTemplate ;
Voici le code complet de la méthode myMaps_ViewChangeEnded :
- private async void myMaps_ViewChangeEnded( object sender, ViewChangeEndedEventArgs e)
- {
- if (myMaps.ZoomLevel > 10 && myMaps.ShowTraffic == true )
- {
- foreach ( var item in incidentPushpins)
- {
- if (myMaps.Children.Contains(item))
- myMaps.Children.Remove(item);
- }
- incidentPushpins.Clear();
- string mapArea = myMaps.TargetBounds.South + « , » + myMaps.TargetBounds.West + « , » + myMaps.TargetBounds.North + « , » + myMaps.TargetBounds.East;
- string uri = « http://dev.virtualearth.net/REST/v1/Traffic/Incidents/ » + mapArea + « ?key= » + BingMapsKey;
- JsonValue values = null ;
- try
- {
- HttpClient client = new HttpClient ();
- var result = await client.GetStringAsync(uri);
- values = JsonValue .Parse(result);
- }
- catch
- {
- MessageDialog dialog = new MessageDialog ( « Unable to find traffic incidents… » );
- dialog.ShowAsync();
- return ;
- }
- if (values == null )
- return ;
- var incidents = values.GetObject()[ « resourceSets » ].GetArray()[0].GetObject()[ « resources » ].GetArray();
- foreach ( var item in incidents)
- {
- var description = item.GetObject()[ « description » ].GetString();
- var coordinates = item.GetObject()[ « point » ].GetObject()[ « coordinates » ].GetArray();
- Location location = new Location (coordinates[0].GetNumber(), coordinates[1].GetNumber());
- Pushpin pushpin = new Pushpin () { Text = « » };
- pushpin.Template = Resources[ « IncidentPushpin » ] as ControlTemplate ;
- MapLayer .SetPosition(pushpin, location);
- myMaps.Children.Add(pushpin);
- ToolTipService .SetToolTip(pushpin, description);
- incidentPushpins.Add(pushpin);
- }
- }
- }
Et le résultat en image :
Nous allons maintenant développer la dernière fonctionnalité, qui est d’intégrer le calcul d’itinéraire. Pour consulter la documentation complète de l’API Routes des services Bing Maps, suivez ce lien .
L’URI de base pour calculer un itinéraire via les services Bing Maps est la suivante : http://dev.virtualearth.net/REST/v1/Routes/
Il faut ensuite ajouter des paramètres de type waypoint.n pour indiquer les différents points de passage. Un point de passage (début, intermédiaire, fin) est représenté soit par des coordonnées géographiques ou un requête (par exemple, une adresse).
Voici ici une URI qui nous permet de rechercher un itinéraire pour aller de Toulouse à Paris : http://dev.virtualearth.net/REST/V1/Routes/Driving?wp.0=toulouse&wp.1=paris&avoid=minimizeTolls&routePathOutput=points&key=XXXXXXXXXXXX
On remarque dans cette URI, le paramètre Driving. Ce paramètre permet d’indiquer le type de déplacement souhaité et peut prendre comme valeur driving, walking ou transit.
Le paramètre avoid permet d’indiquer comment nous voulons optimiser l’itinéraire. La valeur minimizeTolls indique d’éviter les péages autant que possible.
Et le paramètre routePathOutput permet d’indiquer si l’on veut la liste des points représentant le chemin de l’itinéraire (valeur points ou none).
Voici le format de la réponse Json :
{ "authenticationResultCode": "ValidCredentials", "brandLogoUri": "http://dev.virtualearth.net/Branding/logo_powered_by.png", "copyright": "Copyright © 2012 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.", "resourceSets": [ { "estimatedTotal": 1, "resources": [ { "__type": "Route:http://schemas.microsoft.com/search/local/ws/rest/v1", "bbox": [ 43.61451, 1.463354, 43.617219, 1.465543 ], "id": "v63,i0,a2,cen-US,dAAAAAAAAAAA=,y0,s1,m1,o1,t4,wtYbgA6hMIQA=~A1rKBEkRzUwuAADgAaxvKz8A~QXZlbnVlIGR1IENvbW1hbmRhbnQgVGFpbGxhbmRpZXIsIDMxNTAwIFRvdWxvdXNl~~~,wQ3ngA/VPIQA=~A1rKBEkhH04uAADgAQAAgD8A~QXZlbnVlIEzDqW9uIEJsdW0sIDMxNTAwIFRvdWxvdXNl~~~,k1,u", "distanceUnit": "Kilometer", "durationUnit": "Second", "routeLegs": [ { "actualEnd": { "type": "Point", "coordinates": [ 43.614548, 1.46396 ] }, "actualStart": { "type": "Point", "coordinates": [ 43.616874, 1.463354 ] }, "endLocation": { "bbox": [ 43.610703, 1.456808, 43.618429, 1.471034 ], "name": "Avenue Léon Blum, 31500 Toulouse", "point": { "type": "Point", "coordinates": [ 43.614566, 1.463921 ] }, "address": { "addressLine": "Avenue Léon Blum", "adminDistrict": "Midi-Pyrénées", "adminDistrict2": "Haute-Garonne", "countryRegion": "France", "formattedAddress": "Avenue Léon Blum, 31500 Toulouse", "locality": "Toulouse", "postalCode": "31500" }, "confidence": "High", "entityType": "RoadBlock", "geocodePoints": [ { "type": "Point", "coordinates": [ 43.614566, 1.463921 ], "calculationMethod": "Interpolation", "usageTypes": [ "Display", "Route" ] } ], "matchCodes": [ "Good" ] }, "itineraryItems": [ { "compassDirection": "southeast", "details": [ { "compassDegrees": 114, "endPathIndices": [ 1 ], "maneuverType": "DepartStart", "mode": "Driving", "names": [ "Avenue du Commandant Taillandier" ], "roadType": "Street", "startPathIndices": [ 0 ] } ], "exit": "", "iconType": "Auto", "instruction": { "maneuverType": "DepartStart", "text": "Depart Avenue du Commandant Taillandier toward Rue Benjamin Baillaud" }, "maneuverPoint": { "type": "Point", "coordinates": [ 43.616874, 1.463354 ] }, "sideOfStreet": "Unknown", "tollZone": "", "towardsRoadName": "Rue Benjamin Baillaud", "transitTerminus": "", "travelDistance": 0.078, "travelDuration": 8, "travelMode": "Driving" }, { "compassDirection": "northeast", "details": [ { "compassDegrees": 53, "endPathIndices": [ 5 ], "maneuverType": "TurnLeft", "mode": "Driving", "names": [ "Rue Benjamin Baillaud" ], "roadType": "Street", "startPathIndices": [ 1 ] } ], "exit": "", "iconType": "Auto", "instruction": { "maneuverType": "TurnLeft", "text": "Turn left onto Rue Benjamin Baillaud" }, "maneuverPoint": { "type": "Point", "coordinates": [ 43.6165, 1.464191 ] }, "sideOfStreet": "Unknown", "tollZone": "", "transitTerminus": "", "travelDistance": 0.142, "travelDuration": 30, "travelMode": "Driving" }, { "compassDirection": "southwest", "details": [ { "compassDegrees": 214, "endPathIndices": [ 8 ], "maneuverType": "TurnRight", "mode": "Driving", "names": [ "Avenue Yves Brunaud" ], "roadType": "Highway", "startPathIndices": [ 5 ] } ], "exit": "", "iconType": "Auto", "instruction": { "maneuverType": "TurnRight", "text": "Turn right onto Avenue Yves Brunaud" }, "maneuverPoint": { "type": "Point", "coordinates": [ 43.617128, 1.4655 ] }, "sideOfStreet": "Unknown", "tollZone": "", "transitTerminus": "", "travelDistance": 0.17, "travelDuration": 33, "travelMode": "Driving" }, { "compassDirection": "southeast", "details": [ { "compassDegrees": 270, "endPathIndices": [ 13 ], "maneuverType": "EnterRoundabout", "mode": "Driving", "names": [ "Rond-point du Capitaine Alfred Dreyfus" ], "roadType": "Highway", "startPathIndices": [ 8 ] }, { "compassDegrees": 143, "endPathIndices": [ 15 ], "maneuverType": "ExitRoundabout", "mode": "Driving", "names": [ "Boulevard des Crêtes" ], "roadType": "MajorRoad", "startPathIndices": [ 13 ] } ], "exit": "2", "iconType": "Auto", "instruction": { "maneuverType": "EnterThenExitRoundabout", "text": "At roundabout, take 2nd exit onto Boulevard des Crêtes" }, "maneuverPoint": { "type": "Point", "coordinates": [ 43.615653, 1.46506 ] }, "sideOfStreet": "Unknown", "tollZone": "", "transitTerminus": "", "travelDistance": 0.168, "travelDuration": 33, "travelMode": "Driving" }, { "compassDirection": "west", "details": [ { "compassDegrees": 272, "endPathIndices": [ 17 ], "maneuverType": "TurnRight", "mode": "Driving", "names": [ "Avenue Léon Blum" ], "roadType": "MajorRoad", "startPathIndices": [ 15 ] } ], "exit": "", "iconType": "Auto", "instruction": { "maneuverType": "TurnRight", "text": "Turn right onto Avenue Léon Blum" }, "maneuverPoint": { "type": "Point", "coordinates": [ 43.614521, 1.465543 ] }, "sideOfStreet": "Unknown", "tollZone": "", "transitTerminus": "", "travelDistance": 0.13, "travelDuration": 28, "travelMode": "Driving" }, { "compassDirection": "west", "details": [ { "compassDegrees": 272, "endPathIndices": [ 17 ], "maneuverType": "ArriveFinish", "mode": "Driving", "names": [ "Avenue Léon Blum" ], "roadType": "MajorRoad", "startPathIndices": [ 17 ] } ], "exit": "", "hints": [ { "hintType": null, "text": "The last intersection is Boulevard des Crêtes" }, { "hintType": null, "text": "If you reach Avenue Yves Brunaud, you've gone too far" } ], "iconType": "Auto", "instruction": { "maneuverType": "ArriveFinish", "text": "Arrive at Avenue Léon Blum, 31500 Toulouse" }, "maneuverPoint": { "type": "Point", "coordinates": [ 43.614548, 1.46396 ] }, "sideOfStreet": "Unknown", "tollZone": "", "transitTerminus": "", "travelDistance": 0, "travelDuration": 0, "travelMode": "Driving" } ], "startLocation": { "bbox": [ 43.613011, 1.45624, 43.620737, 1.470468 ], "name": "Avenue du Commandant Taillandier, 31500 Toulouse", "point": { "type": "Point", "coordinates": [ 43.616874, 1.463354 ] }, "address": { "addressLine": "Avenue du Commandant Taillandier", "adminDistrict": "Midi-Pyrénées", "adminDistrict2": "Haute-Garonne", "countryRegion": "France", "formattedAddress": "Avenue du Commandant Taillandier, 31500 Toulouse", "locality": "Toulouse", "postalCode": "31500" }, "confidence": "High", "entityType": "RoadBlock", "geocodePoints": [ { "type": "Point", "coordinates": [ 43.616874, 1.463354 ], "calculationMethod": "Interpolation", "usageTypes": [ "Display", "Route" ] } ], "matchCodes": [ "Good" ] }, "travelDistance": 0.688, "travelDuration": 133 } ], "routePath": { "generalizations": [], "line": { "type": "LineString", "coordinates": [ [ 43.616873, 1.463354 ], [ 43.6165, 1.464191 ], [ 43.617171, 1.465092 ], [ 43.617219, 1.465162 ], [ 43.617197, 1.465269 ], [ 43.617128, 1.465499 ], [ 43.61701, 1.465419 ], [ 43.616462, 1.46521 ], [ 43.615652, 1.46506 ], [ 43.615652, 1.464968 ], [ 43.61562, 1.464877 ], [ 43.61547, 1.46484 ], [ 43.6154, 1.464942 ], [ 43.615438, 1.465129 ], [ 43.615379, 1.465172 ], [ 43.614521, 1.465542 ], [ 43.61451, 1.46543 ], [ 43.614547, 1.46396 ] ] } }, "travelDistance": 0.688, "travelDuration": 133 } ] } ], "statusCode": 200, "statusDescription": "OK", "traceId": "74808b399c51451dab1758f61ddd1c04|AMSM002207|02.00.138.500|AMSMSNVM002460, AMSMSNVM001314, AMSMSNVM001313, AMSMSNVM001322, AMSMSNVM002154, AMSMSNVM001307, AMSMSNVM002153, AMSMSNVM002408, AMSMSNVM001859, AMSMSNVM001857, AMSMSNVM001321, AMSMSNVM001863" }
Dans ce flux Json nous avons l’objet bbox qui va nous permettre de centrer la carte sur la zone géographique de l’itinéraire via la méthode SetView :
- var values = JsonValue .Parse(result);
- // bbox represents the Bounding box of route : SouthLatitude, WestLongitude, NorthLatitude, and EastLongitude
- var bbox = values.GetObject()[ « resourceSets » ].GetArray()[0]
- .GetObject()[ « resources » ].GetArray()[0]
- .GetObject()[ « bbox » ].GetArray();
- var southLat = bbox[0].GetNumber();
- var westLong = bbox[1].GetNumber();
- var northLat = bbox[2].GetNumber();
- var eastLong = bbox[3].GetNumber();
- // center map on boundingbox of route
- var rect = new LocationRect ( new Location (northLat, westLong), new Location (southLat, eastLong));
- CurrentMap.SetView(rect);
Dans le flux Json nous retrouvons également les valeurs travelDistance et travelDuration, ainsi que distanceUnit et durationUnit. Nous avons ici accès aux informations de temps de parcours et de la distance totale.
Ensuite 2 objets nous intéresse particulièrement dans le cadre de notre recherche d’itinéraire. Tout d’abord l’objet routePath, qui lui même contient un objet line avec les coordonnées de notre parcours :
- var coordinates = values.GetObject()[ « resourceSets » ].GetArray()[0]
- .GetObject()[ « resources » ].GetArray()[0]
- .GetObject()[ « routePath » ]
- .GetObject()[ « line » ]
- .GetObject()[ « coordinates » ].GetArray();
Avec ces coordonnées nous allons pouvoir construire un objet MapPolyline pour représenter le parcours sur la carte. Un objet MapPolyline est composé d’un objet LocationCollection, d’une couleur et d’une taille :
- var locationCollection = new LocationCollection ();
- foreach ( var coordinate in coordinates)
- {
- var latitude = coordinate.GetArray()[0].GetNumber();
- var longitude = coordinate.GetArray()[1].GetNumber();
- var location = new Bing.Maps. Location (latitude, longitude);
- locationCollection.Add(location);
- }
- MapPolyline polyline = new MapPolyline ();
- polyline.Locations = locationCollection;
- polyline.Color = Windows.UI. Colors .Blue;
- polyline.Width = 5;
Ce type d’objet doit être ajouté à la carte par l’intermédiaire d’un layer de type MapShapeLayer :
- MapShapeLayer shapeLayer = new MapShapeLayer ();
- shapeLayer.Shapes.Add(polyline);
- CurrentMap.ShapeLayers.Add(shapeLayer);
Voici le résultat que l’on obtient après ajout de l’objet MapPolyline :
Il nous reste à positionner les différentes étapes de l’itinéraire.
Dans le flux Json nous retrouvons la liste de ces étapes au niveau du tableau itineraryItems :
- var itineraryItems = values.GetObject()[ « resourceSets » ].GetArray()[0]
- .GetObject()[ « resources » ].GetArray()[0]
- .GetObject()[ « routeLegs » ].GetArray()[0]
- .GetObject()[ « itineraryItems » ].GetArray();
Dans une étape d’itinéraire, nous récupérons les coordonnées du point de manœuvre et les instructions. Nous utilisons un incrément afin de représenter le numéro de l’étape sur l’objet Pushpin :
- int stepCounter = 1;
- foreach ( var item in itineraryItems)
- {
- var step = item.GetObject()[ « instruction » ].GetObject()[ « text » ].GetString();
- var point = item.GetObject()[ « maneuverPoint » ].GetObject()[ « coordinates » ].GetArray();
- var latitude = point[0].GetNumber();
- var longitude = point[1].GetNumber();
- var location = new Location (latitude, longitude);
- Pushpin pushpin = new Pushpin () { Text = stepCounter.ToString() };
- MapLayer .SetPosition(pushpin, location);
- CurrentMap.Children.Add(pushpin);
- ToolTipService .SetToolTip(pushpin, step);
- }
Et voici le résultat :
On peut ensuite mettre en place un UserControl qui va nous permettre de saisir les adresses de départ, de destination et afficher les instructions dans une ListView :
L’archive suivante contient la solution complète au format VS 2012 RC :
Dans cette archive vous retrouverez les exemples décrits dans les 3 articles.
Il vous faudra éditer le fichier App.xaml afin de renseigner le paramètre BingMapsKey avec votre propre clé :
- < x : String x : Key = »BingMapsKey »> Your Bing Maps Key </ x : String >