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 chère à 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, à pieds ou à vélo.
II. Création du model▲
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 presence 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".
- "element" :
- "elements" : tableau d'"element" ;
- "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édement, 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 noeud "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 methode, lisez le tutoriel de Nico-pyright(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
les
unes
après
les
autres.
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);
}
//
Ecriture
du
fichier
dans
le
repertoire
d'execution
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
localisation
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éé
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éé
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▲
IV. Conclusion▲
Ainsi, certaines des petites difficultés de la vie quotidiennes peuvent souvent être résolues avec quelques lignes de code. Avec ces informations en mains, nul doute que Monsieur Leguilvinec fera un choix éclairé.
Si jamais Visual Studio pouvait référencer les service Web JSON directement ce serait encore plus simple.
Remerciements▲
Merci à _Max__Max_ pour sa relecture.