I. Introduction▲
Monsieur Leguilvinec, souhaite connaître le meilleur emplacement géographique pour vivre dans la banlieue sud-ouest de Nantes. Il nous propose une liste de villes :
- Bouaye, parce que c'est un joli village proche de la réserve naturelle de Grand-Lieu ;
- Saint-Mars-de-Coutais, parce que sa mère y vit ;
- Saint-Aignan-Grandlieu, parce que ce n'est vraiment pas cher à cause de l'aéroport ;
- Les Sonnières, parce que c'est vraiment tout près du travail.
Ces villes seront nos points de départ. Les points d'arrivée sont :
- Le CHU, parce qu'il y travaille ;
- Saint-Mars-de-Coutais, parce que sa mère y vit ;
- Le point géographique 47.123819 -1.66783, parce que c'est là que se trouve sa barque ;
- Saint-Herblain, parce que c'est là que se trouve son supermarché préféré.
Le code va utiliser le service JSON (JavaScript Object Notation) de la Google Distance Matrix API. Ce service permet de connaître la distance et le temps de parcours entre deux points géographiques en voiture, à pied ou à vélo.
II. Création du modèle▲
Les données renvoyées par un service JSON sont au format texte. Elles ne sont donc pas interprétables directement par le code. Pour résoudre cette difficulté, le plus simple est de créer des objets .Net et de désérialiser la chaîne JSON obtenue grâce aux fonctionnalités du Framework 4.
Pour ceux qui, comme moi, ne lisent pas le JSON dans le texte et qui sont plus habitués au XML, c'est là que vous aurez le plus de difficultés. Le reste du code utile tient en quelques lignes.
II-A. Les objets JSON▲
La requête http est très simple : http://maps.googleapis.com/maps/api/distancematrix/json?parameters
Les paramètres sont :
- origins : les points de départ séparés par des « pipe » ;
- destinations : les points d'arrivée séparés par des « pipe » ;
- sensor : à false ;
- userip : l'adresse IP du client qui fait la requête. Cela permet d'indiquer à Google que ce n'est pas toujours le même client qui initie les requêtes.
Voici l'URL pour le village de BouayeURL pour le village de Bouaye :
http://maps.googleapis.com/maps/api/distancematrix/json?origins=Bouaye,%20Loire-Atlantique&destinations=C.H.U.%20Saint-Jacques,%2044200%20Quartiers%20Sud,%20Nantes|Saint-Mars-de-Coutais|47.123819,-1.66783|Saint-Herblain&sensor=false&userip=192.168.1.1
Une fois la requête émise, voici ce qui revient :
{
"destination_addresses"
:
[
"C.H.U. Saint-Jacques, 44200 Nantes, France"
,
"Saint-Mars-de-Coutais, France"
,
"Route du Lac, 44860 Saint-Aignan-Grandlieu, France"
,
"Saint-Herblain, France"
],
"origin_addresses"
:
[
"Bouaye, France"
],
"rows"
:
[
{
"elements"
:
[
{
"distance"
:
{
"text"
:
"15,2 km"
,
"value"
:
15165
},
"duration"
:
{
"text"
:
"23 minutes"
,
"value"
:
1381
},
"status"
:
"OK"
},
{
"distance"
:
{
"text"
:
"5,6 km"
,
"value"
:
5593
},
"duration"
:
{
"text"
:
"7 minutes"
,
"value"
:
430
},
"status"
:
"OK"
},
{
"distance"
:
{
"text"
:
"4,0 km"
,
"value"
:
3967
},
"duration"
:
{
"text"
:
"6 minutes"
,
"value"
:
386
},
"status"
:
"OK"
},
{
"distance"
:
{
"text"
:
"15,3 km"
,
"value"
:
15288
},
"duration"
:
{
"text"
:
"20 minutes"
,
"value"
:
1224
},
"status"
:
"OK"
}
]
}
],
"status"
:
"OK"
}
La lecture de ce code se fait comme indiqué sur l'image ci-dessous :
Avec JSON, on peut se dire qu'une accolade ouverte indique la présence d'un objet. Nommez-les en fonction du nom de la propriété.
On identifie les propriétés suivantes :
- « destination_addresses » : tableau de chaînes ;
- « origin_addresses » : tableau de chaînes ;
-
« rows » : tableau de « Row » ;
-
« elements » : tableau d'« element » ;
-
« element » :
- « distance » ;
- « duration » ;
- « status ».
-
-
- « status » : chaîne.
On constate que les destinations ne sont pas reprises dans chacun des elements. Il faudra donc absolument respecter l'ordre.
Vous pouvez nommer vos classes comme vous le souhaitez. Pour le processus de désérialisation, seul compte le nom des propriétés.
II-B. L'objet GoogleDistanceMatrix▲
Cet objet contient une série de propriétés de type chaîne ou tableau de chaînes et une de type tableau de Row.
{
"destination_addresses"
:
[
""
],
"origin_addresses"
:
[
""
],
"rows"
:
[
{
}
],
"status"
:
""
}
[Serializable]
[DataContractAttribute]
public
class
GoogleDistanceMatrix
{
[DataMemberAttribute]
public
string
[]
destination_addresses;
[DataMemberAttribute]
public
string
[]
origin_addresses;
[DataMemberAttribute]
public
Row[]
rows;
[DataMemberAttribute]
public
string
status;
}
II-C. L'objet Row▲
Cet objet est encore plus simple, il ne dispose que d'une seule propriété, un tableau d'un nouveau type : Element.
{
"elements"
:
[
{
}
]
}
[Serializable]
[DataContractAttribute]
public
class
Row
{
[DataMemberAttribute]
public
Element[]
elements;
}
II-D. L'objet Element▲
Cet objet est constitué de trois propriétés dont deux sont des nouveaux objets : Distance et Duration.
{
"distance"
:
{
},
"duration"
:
{
},
"status"
:
"OK"
}
[Serializable]
[DataContractAttribute]
public
class
Element
{
[DataMemberAttribute]
public
Distance distance;
[DataMemberAttribute]
public
Duration duration;
[DataMemberAttribute]
public
string
status;
}
II-E. L'objet Distance▲
Très simple, il se compose de deux propriétés, une chaîne et un entier. L'entier est la valeur en mètres de la distance.
{
"text"
:
"15,2 km"
,
"value"
:
15165
}
[Serializable]
[DataContractAttribute]
public
class
Distance
{
[DataMemberAttribute]
public
int
value
;
[DataMemberAttribute]
public
string
text;
}
II-F. L'objet Duration▲
Encore très simple, il se compose de deux propriétés, une chaîne et un entier. L'entier est la valeur en secondes de la durée.
{
"text"
:
"23 minutes"
,
"value"
:
1381
}
[Serializable]
[DataContractAttribute]
public
class
Duration
{
[DataMemberAttribute]
public
int
value
;
[DataMemberAttribute]
public
string
text;
}
II-G. Le modèle complet▲
III. La logique métier▲
Pour pouvoir utiliser la désérialisation JSON, vous devez référencer l'espace de nom « System.Runtime.Serialization », et utiliser « System.Runtime.Serialization.Json » dans votre programme.
III-A. La méthode de désérialisation▲
Comme je le disais précédemment, grâce au Framework 4, elle tient en quelques lignes :
public
static
GoogleDistanceMatrix GetMatrix
(
string
origins,
string
destinations,
IPAddress ip)
{
Uri uri =
new
Uri
(
string
.
Format
(
"http://maps.googleapis.com/maps/api/distancematrix/json?origins={0}&destinations={1}&sensor=false&userip={2}"
,
origins,
destinations,
ip.
ToString
(
)));
string
rep =
GetRequest
(
uri);
GoogleDistanceMatrix gdm =
new
GoogleDistanceMatrix
(
);
using
(
MemoryStream mem =
new
MemoryStream
(
Encoding.
UTF8.
GetBytes
(
rep)))
{
DataContractJsonSerializer ser =
new
DataContractJsonSerializer
(
gdm.
GetType
(
));
gdm =
ser.
ReadObject
(
mem) as
GoogleDistanceMatrix;
}
return
gdm;
}
///
<
summary
>
/// Méthode pour envoyer la requête http
///
<
/summary
>
///
<
param
name
=
"uri"
><
/param
>
///
<
returns
><
/returns
>
private
static
string
GetRequest
(
Uri uri)
{
string
answer =
string
.
Empty;
HttpWebRequest req =
(
HttpWebRequest)WebRequest.
Create
(
uri);
using
(
HttpWebResponse res =
(
HttpWebResponse)req.
GetResponse
(
))
{
if
(
req.
HaveResponse &&
res.
StatusCode ==
HttpStatusCode.
OK)
using
(
Stream resin =
res.
GetResponseStream
(
))
{
using
(
StreamReader rea =
new
StreamReader
(
resin))
{
answer =
rea.
ReadToEnd
(
);
}
}
}
return
answer;
}
Copiez ce code dans une classe à part. Ainsi, vous pourrez l'utiliser dans d'autres applications.
III-B. Exploitation de l'objet GoogleDistanceMatrix▲
Créez une application console. Vérifiez que la propriété « Target Framework » de votre projet est bien à « .Net Framework 4 » et non « .Net Framework 4 Client Profile ». Ce dernier n'est pas compatible avec la référence « System.Web ». Vous ne pourrez pas compiler.
Ajoutez les objets du modèle ainsi que les méthodes de désérialisation et de requête http. Pour monter une architecture selon les règles de l'art, vous pouvez toujours aller faire un petit tour sur ma page d'accueil, vous y trouverez un article sur l'architecture multicouche.
III-B-1. Le fichier de configuration▲
Afin d'éviter de recompiler l'application à chaque fois qu'on souhaite ajouter des localisations, nous allons utiliser le fichier app.config. Le nœud « configSections » doit être le tout premier.
<configSections>
<sectionGroup
name
=
"Cities"
>
<section
name
=
"origins"
type
=
"System.Configuration.NameValueSectionHandler"
/>
<section
name
=
"destinations"
type
=
"System.Configuration.NameValueSectionHandler"
/>
</sectionGroup>
</configSections>
<Cities>
<origins>
<add
key
=
"Bouaye"
value
=
"Bouaye, Loire-Atlantique, France"
/>
<add
key
=
"SaintMars"
value
=
"Saint-Mars-de-Coutais, Loire-Atlantique, France"
/>
<add
key
=
"SaintAignanGrandlieu"
value
=
"Saint-Aignan-Grandlieu, Loire-Atlantique, France"
/>
<add
key
=
"LesSonnières"
value
=
"Les Sorinières, Loire-Atlantique, France"
/>
</origins>
<destinations>
<add
key
=
"CHU"
value
=
"C.H.U. Saint-Jacques, 44200 Nantes, France"
/>
<add
key
=
"SaintMars"
value
=
"Saint-Mars-de-Coutais, Loire-Atlantique, France"
/>
<add
key
=
"LeLac"
value
=
"Route du Lac, 44860 Saint-Aignan-Grandlieu, France"
/>
<add
key
=
"SaintHerblain"
value
=
"Saint-Herblain, France"
/>
</destinations>
</Cities>
Si vous voulez plus de détails sur cette méthode, lisez le tutoriel de Nicopyright(c) Travailler avec les fichiers de configuration en C#.
III-B-2. Le programme▲
J'espère que les commentaires du code parleront d'eux-mêmes.
namespace
ConsoleApplication
{
class
Program
{
static
void
Main
(
string
[]
args)
{
// Récupération de la configuration
NameValueCollection origins =
(
NameValueCollection)ConfigurationManager.
GetSection
(
"Cities/origins"
);
NameValueCollection destinations =
(
NameValueCollection)ConfigurationManager.
GetSection
(
"Cities/destinations"
);
// Concatenation des destinations pour former le paramètre "destinations" de l'URL
string
destConcat =
ConcatDestinations
(
destinations);
// Nous allons stocker les objets désérialisés dans une liste
List<
GoogleDistanceMatrix>
list =
new
List<
GoogleDistanceMatrix>(
);
// Création d'un objet pour chaque point de départ.
// L'API supporterait très bien une seule requête avec tous les points de départ. Il faudrait ensuite décomposer les collections.
// Je préfère faire une requête pour chaque point de départ et les envoyer l'une après l'autre.
foreach
(
var
origin in
origins)
{
list.
Add
(
GoogleMapManager.
GetMatrix
(
origins[
origin.
ToString
(
)],
destConcat,
IPAddress.
Parse
(
"192.168.1.10"
)));
}
// Affichage du résultat à l'écran de la console
foreach
(
var
gdm in
list)
{
Console.
WriteLine
(
string
.
Format
(
"Distance/Durée depuis: {0}{1}"
,
gdm.
origin_addresses[
0
].
Split
(
','
).
FirstOrDefault
(
),
Environment.
NewLine));
for
(
int
i =
0
;
i <
gdm.
destination_addresses.
Length;
i++
)
{
Console.
Write
(
"
\t
-> {0}{1}"
,
gdm.
destination_addresses[
i].
Split
(
','
).
FirstOrDefault
(
),
ReturnSpaces
(
gdm.
destination_addresses[
i].
Split
(
','
).
FirstOrDefault
(
),
25
));
Console.
WriteLine
(
"
\t
{0}
\t\t
{1}"
,
gdm.
rows[
0
].
elements[
i].
distance.
text,
gdm.
rows[
0
].
elements[
i].
duration.
text);
}
Console.
WriteLine
(
);
}
// Convertion du résultat dans un StringBuilder en vue d'une sauvegarde au format csv.
StringBuilder csv =
new
StringBuilder
(
);
GenerateCsvHeader
(
csv,
destinations);
foreach
(
var
gdm in
list)
{
AddDistances
(
gdm,
csv);
AddTime
(
gdm,
csv);
}
// Écriture du fichier dans le répertoire d'exécution du programme.
using
(
StreamWriter sw =
new
StreamWriter
(
"Distances.csv"
,
false
,
Encoding.
UTF8))
{
sw.
Write
(
csv.
ToString
(
));
}
Console.
WriteLine
(
"C'est fini. Appuyez sur une touche."
);
Console.
ReadLine
(
);
}
///
<
summary
>
/// Permet de concaténer les localisations en les séparant avec des "pipe".
///
<
/summary
>
///
<
param
name
=
"destinations"
><
/param
>
///
<
returns
><
/returns
>
private
static
string
ConcatDestinations
(
NameValueCollection destinations)
{
string
sDestinations =
string
.
Empty;
foreach
(
var
item in
destinations)
{
sDestinations +=
string
.
Format
(
"{0}|"
,
destinations[
item.
ToString
(
)].
ToString
(
));
}
return
sDestinations;
}
///
<
summary
>
/// Génère les en-têtes de colonnes
///
<
/summary
>
///
<
param
name
=
"csv"
><
/param
>
///
<
param
name
=
"destinations"
><
/param
>
private
static
void
GenerateCsvHeader
(
StringBuilder csv,
NameValueCollection destinations)
{
csv.
Append
(
"
\"
Points de départ
\"
;
\"
Type
\"
;"
);
foreach
(
var
item in
destinations)
{
csv.
AppendFormat
(
"
\"
{0}
\"
;"
,
destinations[
item.
ToString
(
)].
Split
(
','
).
FirstOrDefault
(
));
}
csv.
Append
(
Environment.
NewLine);
}
///
<
summary
>
/// Crée une ligne avec les distances entre le point de départ et les destinations
///
<
/summary
>
///
<
param
name
=
"gdm"
><
/param
>
///
<
param
name
=
"csv"
><
/param
>
private
static
void
AddDistances
(
GoogleDistanceMatrix gdm,
StringBuilder csv)
{
csv.
AppendFormat
(
"
\"
{0}
\"
;
\"
Distance
\"
;"
,
gdm.
origin_addresses[
0
].
Split
(
','
).
FirstOrDefault
(
));
for
(
int
i =
0
;
i <
gdm.
destination_addresses.
Length;
i++
)
{
csv.
AppendFormat
(
"{0};"
,
gdm.
rows[
0
].
elements[
i].
distance.
value
/
1000
);
}
csv.
Append
(
Environment.
NewLine);
}
///
<
summary
>
/// Crée une ligne avec les durées entre le point de départ et les destinations
///
<
/summary
>
///
<
param
name
=
"gdm"
><
/param
>
///
<
param
name
=
"csv"
><
/param
>
private
static
void
AddTime
(
GoogleDistanceMatrix gdm,
StringBuilder csv)
{
csv.
AppendFormat
(
"
\"
{0}
\"
;
\"
Durée
\"
;"
,
gdm.
origin_addresses[
0
].
Split
(
','
).
FirstOrDefault
(
));
for
(
int
i =
0
;
i <
gdm.
destination_addresses.
Length;
i++
)
{
csv.
AppendFormat
(
"{0};"
,
gdm.
rows[
0
].
elements[
i].
duration.
value
/
60
);
}
csv.
Append
(
Environment.
NewLine);
}
///
<
summary
>
/// Ajoute des espaces au bout de la valeur du point de départ. C'est plus joli sur l'écran de la console :)
///
<
/summary
>
///
<
param
name
=
"sentence"
><
/param
>
///
<
param
name
=
"nb"
><
/param
>
///
<
returns
><
/returns
>
private
static
string
ReturnSpaces
(
string
sentence,
int
nb)
{
string
sp =
string
.
Empty;
for
(
int
i =
0
;
i <
nb -
sentence.
Length;
i++
)
{
sp +=
" "
;
}
return
sp;
}
}
}
III-B-3. Résultat▲
Exécutez le programme. La console devrait afficher ceci :
Une fois le fichier CSV ouvert avec Excel, vous pouvez mettre en forme les données ainsi :
IV. Conclusion▲
Ainsi, certaines des petites difficultés de la vie quotidienne peuvent souvent être résolues avec quelques lignes de code. Avec ces informations en main, nul doute que Monsieur Leguilvinec fera un choix éclairé.
Si jamais Visual Studio pouvait référencer les services Web JSON directement ce serait encore plus simple.
Remerciements▲
Merci à _Max__Max_ pour sa relecture.