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 one :D 
return 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 :D ) 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 !:D
Sous l’ampleur de l’émotion, je me limite à inaugurer mon blog et à remercier Sami et Sébastien …

Blog de Rochdi Chakroun

<  Novembre 2007  >
Lun Mar Mer Jeu Ven Sam Dim
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Référents récents


Référents les plus fréquents

powered by
b2evolution