Connaître la distance et le temps de parcours entre plusieurs villes grâce à l'API JSON Google Distance Matrix
Ou comment utiliser Google Map pour trouver le meilleur emplacement de son futur logement.
Date de publication : 19/06/2011. Date de mise à jour : 19/06/2011.
Par
Immobilis (accueil)
Je vis dans une région qui n'est pas aussi bien desservie que Paris par les transports publics.
La majorité des déplacements ce font en voiture. En cas de changement de logement, il faut trouver
une ville ou un village le plus près possible de toutes les commodités. Le petit bout de code que je vous propose
dans ce chapitre va vous permettre de rapidement connaître les distances et les temps de parcours entre toutes
les agglomérations et bourgs que vous souhaitez.
Accessoirement, vous verrez comment désérialiser une réponse JSON à l'aide du Framework 4.
6 commentaires
I. Introduction
II. Création du model
II-A. Les objets JSON
II-B. L'objet GoogleDistanceMatrix
II-C. L'objet Row
II-D. L'objet Element
II-E. L'objet Distance
II-F. L'objet Duration
II-G. Le modèle complet
III. La logique métier
III-A. La méthode de désérialisation
III-B. Exploitation de l'objet GoogleDistanceMatrix
III-B-1. Le fichier de configuration
III-B-2. Le programme
III-B-3. Résultat
IV. Conclusion
Remerciements
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é.
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.
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 :
| Code JSON de la réponse |
{
"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".
- "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.
| Version JSON: |
{
"destination_addresses" : [ "" ],
"origin_addresses" : [ "" ],
"rows" : [ { } ],
"status" : ""
}
|
| Version C# |
[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.
| Version C# |
[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.
| Version JSON |
{
"distance" : { },
"duration" : { },
"status" : "OK"
}
|
| Version C# |
[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.
| Version JSON |
{
"text" : "15,2 km",
"value" : 15165
}
|
| Version C# |
[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.
| Version JSON |
{
"text" : "23 minutes",
"value" : 1381
}
|
| Version C# |
[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:
| Méthode de désérialisation de la réponse JSON |
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>
</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.
| Paramètres de configuration des points de départ et de destination |
<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>
|
III-B-2. Le programme
J'espère que les commentaires du code parleront d'eux-mêmes.
| Version C# |
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
NameValueCollection origins = (NameValueCollection)ConfigurationManager.GetSection("Cities/origins");
NameValueCollection destinations = (NameValueCollection)ConfigurationManager.GetSection("Cities/destinations");
string destConcat = ConcatDestinations(destinations);
List<GoogleDistanceMatrix> list = new List<GoogleDistanceMatrix>();
foreach (var origin in origins)
{
list.Add(GoogleMapManager.GetMatrix(origins[origin.ToString()], destConcat, IPAddress.Parse("192.168.1.10")));
}
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();
}
StringBuilder csv = new StringBuilder();
GenerateCsvHeader(csv, destinations);
foreach (var gdm in list)
{
AddDistances(gdm, csv);
AddTime(gdm, csv);
}
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>
</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>
</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>
</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>
</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>
</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
Executez 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 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_ pour sa relecture.


Les sources présentées sur cette page sont libres de droits
et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation
constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ©
2011 Immobilis. Aucune reproduction,
même partielle, ne peut être faite de ce site et de l'ensemble de son contenu :
textes, documents, images, etc. sans l'autorisation expresse de l'auteur.
Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 €
de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.
Cette page est déposée.