2007-11-06
Vélib, Mappoint, et .NET 3.5
Après le démarrage de velib, plusieurs mashup ont consolidé le service Vélib et les services de localisation géographique (la majorité a opté pour Google Map) afin de permettre aux internautes de localiser les stations vélib les plus proches et leurs statuts en temps réel: nombre de vélos, et bornes libres. Un des sites les plus abouti est http://www.unvelovite.com/Velib/
Le souci avec l’utilisation des API de Google Map par exemple, est le javascript inclus dans les pages rendues. Ce javascript exclut les utilisateurs mobiles
Or lors des dernières grèves combien d’entre nous ont souhaité avoir accès en fonction de leur position à une carte avec les stations les plus proches et leurs états . Pour ceux qui se séparent pas de leur PC portable et ont une carte d’accès internet 3G+ le problème ne se pose pas
.
Bon ! Passons aux choses sérieuses :
L’application, baptisée VelibLightUp, doit pouvoir consommer les services fournis par deux fournisseurs de service : Velib et Mappoint (Si vous vous demandez pourquoi MapPoint et pas GoogleMap la reponse est simple : Vivement Microsoft
).
Premier constat Mappoint offre via son service web un grand nombre de services et de classes, il y en a tellement qu’on risque de ce perdre, donc afin de simplifier la tâche aux couches applicatives supérieures, une facade de MapPoint est de grande utilité.
Pour le service Vélib et vu qu’il est de type REST (http://en.wikipedia.org/wiki/REST) et afin d’homogénéiser la consommation de ces services, un Velib proxy nous donnera l’illusion d’avoir un web service proxy (classique), ceci va aussi nous permettre de mapper l’XML rendu par le service Velib à des objets qui auront un sens fonctionnel et qui formeront le domaine velib.
Ayant la facade MapPoint et le Proxy Velib, on va quand même pas laisser la couche présentation ou business manipuler ces deux services. Non ! Un Service Agent s’impose, c’est lui qui va consolider les deux fournisseurs de service et offrir aux couches supérieures les méthodes nécessaires.
Basta Blabla voyons voir l’organisation du projet:

Le diagramme de classe du domaine Velib est le suivant, ce domaine n’est que le mapping de l’XML rendu par les services Velib :
+Le listing de toutes les stations. http://www.velib.paris.fr/service/carto
+Pour une station donnée il retourne son état (vélo dispo, bornes libres …) : par exemple pour la station qui a pour id 20020 http://www.velib.paris.fr/service/stationdetails/20020
voir le diagramme: http://www.dotnetguru2.org/media/diagram.jpg
Voyons comment le proxy velib encapsule les appels au service REST :
Par exemple pour récupérer la liste des stations:
/// <summary> /// Return the list of all stations by calling the rest service: /// http://www.velib.paris.fr/service/carto /// </summary> /// <returns>List of Domain Station</returns> public List<Station> GetStationList() { //Opps Linq to XML XDocument xDocument =null; xDocument = XDocument.Load( "http://www.velib.paris.fr/service/carto"); List<Station> stationList = new List<Station>(); // call GetDocumentElementByTagName to // get only the part of the xDocument holding station data var stations = GetDocumentElementByTagName(xDocument,"markers"); // for each XElement in the Stations to build Station Domain Object foreach (XElement element in stations) { //Get the station Id int stationId = Convert.ToInt32( GetAttributeValue(element, "number")); //Get the Station Name string stationName = GetAttributeValue(element, "name"); //Get the station address string stationAddress = GetAttributeValue(element, "address"); //Get the station Full Address string stationFullAddress = GetAttributeValue(element, "fullAddress"); //Get the station latitude string stationLatitude = GetAttributeValue(element, "lat"); //Get the station longitude string stationlongitude = GetAttributeValue(element, "lng"); //Map the station status (open = 1, close = 0) StationStatusEnum stationStatus = (StationStatusEnum) Convert.ToInt32(GetAttributeValue(element, "open")); //Build the station Geographic Coordinates GeographicCoordinates stationCoordinates; stationCoordinates = new GeographicCoordinates( ConvertStringToDouble(stationlongitude), ConvertStringToDouble(stationLatitude)); //Finaly build a station domain object Station station; station= new Station(stationId, stationName, stationAddress, stationFullAddress, stationCoordinates, stationStatus); //Finaly add the station to the list of station stationList.Add(station); } return stationList; }
Pour la méthode GetAttributeValue, qui est appelée plusieurs fois dans la méthode précédente, elle permet de retourner la valeur d’un attribut en fonction de son nom, le code est le suivant:
/// <summary> /// Based on an element and an attribute name return the value of that attribute /// </summary> /// <param name="element">element to fetch in the attribute value</param> /// <param name="attributeName">element name</param> /// <returns></returns> private string GetAttributeValue( XElement element, string attributeName) { var query = from attribute in element.Attributes() where attribute.Name == attributeName select attribute.Value; List<string> attibuteValueList = new List<string>(query); return (attibuteValueList.Count > 0) ? attibuteValueList[0] : string.Empty; }
Passons à Mappoint, le code de la méthode principale GetLocationsMap est le suivant:
/// <summary> /// Based on a set of positions, messages return a /// map that highlights these positins /// and associate a message for each position /// </summary> /// <param name="positionList">List of the positions to place the pushpins</param> /// <param name="messageList">list of messages to print on the map (pushpin)</param> /// <param name="scale">map scale</param> /// <param name="width">Image width</param> /// <param name="height">Image width</param> /// <param name="returnType">Type of the returned map</param> /// <returns></returns> public MapImage GetLocationsMap(List<LatLong> positionList, List<string> messageList, double scale, int width, int height, MapReturnType returnType) { // Map point service used to get the map RenderServiceSoap renderService = new RenderServiceSoap(); //set the user culture CultureInfo culture = new CultureInfo(); culture.Name = "fr"; culture.Lcid = 12; //User info Render Header UserInfoRenderHeader userHeader = new UserInfoRenderHeader(); userHeader.Culture = culture; //Set service properties renderService.Credentials = GetCredential(); renderService.Timeout = 30000; renderService.PreAuthenticate = true; renderService.Credentials = GetCredential(); renderService.UserInfoRenderHeaderValue = userHeader; //Set the view scale ViewByScale[] viewArray = new ViewByScale[1]; viewArray[0] = new ViewByScale(); viewArray[0].CenterPoint = positionList[0]; viewArray[0].MapScale = scale; //For each position set a pushpin Pushpin[] pinArray = new Pushpin[positionList.Count]; for (int i = 0; i < positionList.Count; i++) { pinArray[i] = new Pushpin(); //set the message to associate with the pushpin pinArray[i].Label = messageList[i]; pinArray[i].PinID = i.ToString(); if (i == 0) { //for the first pusshpin it represents the user position //so use a different ICON (RED FLAG) pinArray[i].IconName = "29"; } else { //For other position use the default icon pinArray[i].IconName = "0"; } //set pushpin data source pinArray[i].IconDataSource = "MapPoint.Icons"; LatLong latLong = new LatLong(); pinArray[i].LatLong = positionList[i]; } //Set the map options MapOptions options = new MapOptions(); ImageFormat imageFormat = new ImageFormat(); imageFormat.Height = height; imageFormat.Width = width; options.Format = imageFormat; options.ReturnType = returnType; // set the map specification MapSpecification mapSpecification = new MapSpecification(); mapSpecification.Options = options; mapSpecification.Views = viewArray; mapSpecification.Pushpins = pinArray; mapSpecification.DataSourceName = "MapPoint.EU"; // ouuf finaly call the service MapImage[] imageArray = renderService.GetMap(mapSpecification); // return the first image hoping that we'll get just onereturn imageArray[0]; }
Disons que Map point service et le velib proxy sont finalisés on passe au Service Agent qui va marier les deux services. la méthode GetNearStation prend les paramètres suivants : la rue, le code postal, la ville et pays et retourne une url d’une image (qu’on peut afficher sur un mobile par exemple
) cette image contient un drapeau rouge qui représente la position de l’utilisateur et des drapeaux bleus qui correspondent aux stations velib autour de la position de l’utilisateur. En plus, le statut de chaque station est associé respectivement à chaque drapeau. Pour limiter la recherche de l’utilisateur aux stations qui sont les plus proches, on se base sur les coordonnées géographiques de son adresse et on fait varier la latitude et longitude de ~0.03 degrés. Ce qui donne pour 101 avenue des Champs-Elysées, 75008:

Pour voir la carte (taille réelle 800px/800px): http://www.dotnetguru2.org/media/demo.gif
Le code de cette méthode est le suivant:
public string GetNearStation(string streetAddress, string postCode, string city, string country) { // List of Station geo-position List<LatLong> stationPositionList = new List<LatLong>(); //List of station status List<StationStatus> stationStatusList = new List<StationStatus>(); //List of messages associate with a station List<string> stationInfoList = new List<string>(); // Format the address for Map point service string formatedAddress = FormatAddress(streetAddress, postCode,city, country); //Init the map point service MapPointService service = new MapPointService(); //Get the user lat & long LatLong addressLatLong = service.GetAddressLatLong(formatedAddress); //Init the Velib service VelibServiceProxy velibServiceProxy = new VelibServiceProxy(); //Find the station in the same Arrondissement as the user // ~O.OO3 ° var query = from nearStation in velibServiceProxy.GetStationList() where nearStation.Coordinate.Latitude < addressLatLong.Latitude + 0.003 && nearStation.Coordinate.Latitude > addressLatLong.Latitude - 0.003 && nearStation.Coordinate.Longitude < addressLatLong.Longitude + 0.003 && nearStation.Coordinate.Longitude > addressLatLong.Longitude - 0.003 select nearStation; //Set the list from the query List<Station> stationList = query.ToList<Station>(); //Get Station Info stationStatusList = GetStationStatusList(stationList); //Build the messages stationInfoList = StartBuildingStationInfoList(stationList); stationInfoList = EndBuildingStationInfoList(stationInfoList, stationStatusList); //Prepare the list of station lat and long //Convert the list of station to LatLong Mappoint //object stationPositionList = stationList.ConvertAll<LatLong>( new Converter<Station, LatLong>(delegate(Station station) { LatLong latLong = new LatLong(); latLong.Latitude = station.Coordinate.Latitude; latLong.Longitude = station.Coordinate.Longitude; return latLong; })); //Call the GetLocationsByAddress of the mappoint // service facade we the appropriate param MapImage map = service.GetLocationsByAddress( formatedAddress, POSITION_MSG, stationPositionList, stationInfoList, 5000, 800, 800, MapReturnType.ReturnUrl); //Yep! get the img url return map.Url; }
Pour l’utilisation de cette méthode par une page aspx il y pas plus simple. En prenant cette page par exemple: http://www.dotnetguru2.org/media/aspx.jpg
Le code associé au button click est le suivant:
protected void Button1_Click(object sender, EventArgs e) { ServiceFacade service = new ServiceFacade(); string mapUrl = service.GetNearStation(TextBox1.Text, TextBox2.Text, "Paris", "France"); Image1.ImageUrl = mapUrl; Image1.Visible = true; }
Je vais publier le code sur Codeplex, mais d’ici là pour ceux qui veulent avoir le code de la solution (orcas beta 2) ils peuvent me contacter.
19.09.06
1ier post
Voilà, on y est mon premier post !![]()
Sous l’ampleur de l’émotion, je me limite à inaugurer mon blog et à remercier Sami et Sébastien …