Un peu de contexte

OsmApi est un module Python permettant d'interagir avec l'API d'OSM et ainsi manipuler tous les éléments qui constituent les données OSM au sein d'un script en Python. Ce module a été développé par mon collègue Étienne Chové. Il avait besoin d'une interface en Python pour accéder aux données OSM dans le cadre du développement d'osmose.

La façon la plus simple de récupérer le module est de le télécharger sur le dépôt subversion.

Je ne présenterai dans ce billet que les opérations de lecture des données, pas celles permettant de mettre à jour OSM.

Récupérer des noeuds, des chemins ou de relations

La bibliothèque permet d'accéder aux éléments classiques d'OSM : les nœuds (nodes), les chemins (ways), les relations (relations) ainsi que quelques autres moins classiques.

Manipuler des nœuds

La première étape pour utiliser la bibliothèque est de la charger à l'aide d'un import OsmApi, puis d'instancier l'API à l'aide du constructeur OsmApi.OsmApi(). Ainsi, le code ci-dessous permet de récupérer le nœud ayant pour identifiant 26686548.

import OsmApi
MyApi = OsmApi.OsmApi()
MyApi.NodeGet(26686548)

Le résultat de la méthode NodeGet est un dictionnaire :

{
  u'changeset': 7046402, 
  u'uid': 79386, 
  u'timestamp': u'2011-01-22T01:06:54Z', 
  u'lon': -1.5529162000000001, 
  u'visible': True, 
  u'version': 13, 
  u'user': u'philippekerla', 
  u'lat': 47.219167200000001, 
  u'tag': {
    u'ref:SIREN': u'214401093', 
    u'name': u'Nantes', 
    u'is_in:country': u'France', 
    u'name:oc': u'Nantas', 
    u'name:br': u'Naoned', 
    u'source:population': u'INSEE 2007', 
    u'name:ja': u'\u30ca\u30f3\u30c8', 
    u'code_departement': u'44', 
    u'postal_code': u'44000', 
    u'place': u'city', 
    u'is_in:continent': u'Europe', 
    u'name:ru': u'\u041d\u0430\u043d\u0442', 
    u'ref:INSEE': u'44109', 
    u'is_in': u'France,Europe,Pays-de-Loire,Loire-Atlantique', 
    u'name:ca': u'Nantas', 
    u'population': u'283025'
  }, 
  u'id': 26686548
}

Le dictionnaire retourné contient toutes les informations liés à la dernière version de ce nœud : la date à laquelle il a été modifié pour la dernière fois (timestamp), ses coordonnées géographiques (longitude (lon) et lattitude (lat)), l'utilisateur qui l'a modifié pour la dernière fois (user) ou encore l'ensemble des étiquettes (tag) qui définissent ce nœud.

On apprend ainsi que le nom du nœud est Nantes (name) mais qu'en breton il est nommé Naoned (name:br), qu'il s'agit d'une ville (place=city) dont la référence INSEE est 44109.

La méthode NodeHistory permet d'accéder à l'historique des modifications du nœud :

MyApi.NodeHistory(26686548)

Le résultat de cette méthode est un dictionnaire qui associe numéro de version et informations du nœud dans ladite version. On apprend ainsi que le nœud a été créé par un certain Alban, certainement le tout premier contributeur d'OSM à Nantes :)

{
  1: {
    u'changeset': 241544, 
    u'uid': 2488, 
    u'timestamp': u'2007-03-22T19:57:17Z', 
    u'user': u'Alban',
    ...
  }
  2: {
    u'changeset': 88621,
    u'uid': 10927, 
    u'timestamp': u'2008-02-09T23:07:03Z', 
    u'user': u'Skywave',
    ...
  }
  ...
  13 : {
    u'changeset': 7046402, 
    u'uid': 79386, 
    u'timestamp': u'2011-01-22T01:06:54Z', 
    ...
  }

Obtenir les chemins et relations liées à un nœud

Si vous êtes contributeur OSM, vous savez certainement qu'un nœud peut potentiellement appartenir à un ou plusieurs chemins (ways) ou une ou plusieurs relations (relations). L'API permet de retrouver les chemins ou les relations dans lesquels est impliqué un nœud grâce aux méthodes NodeWays et NodeRelations :

MyApi.NodeWays(26686548)
MyApi.NodeRelations(26686548)

Notre nœud n'appartient à aucun chemin, par contre il appartient à une relation nommée Loire-Atlantique qui est une frontière administrative (type=boundary, boundary=administrative) dans laquelle elle a le rôle de admin_centre :

[{
  u'changeset': 6844662, 
  u'uid': 334389, 
  u'timestamp': u'2011-01-02T22:42:45Z', 
  u'tag': {
    u'admin_level': u'6', 
    u'name': u'Loire-Atlantique', 
    u'converted_by': u'Editop_2_OSM_v0016', 
    u'type': u'boundary', 
    u'note': u'Reverted to v92', 
    u'boundary': u'administrative', 
    u'ref': u'44'
  }, 
  u'member': [
    {u'role': u'', u'ref': 83827117, u'type': u'way'}, 
    ...
    {u'role': u'admin_centre', u'ref': 26686548, u'type': u'node'}
  ]
  ...
}]

Manipuler des chemins

Tout comme pour les nœuds, il est possible de récupérer un chemin à l'aide de la méthode WayGet. Si toutefois on cherche à récupérer une liste de nœuds, de chemins ou de relations, on utilisera respectivement les méthodes NodesGet, WaysGet et RelationsGet avec en paramètre une liste d'identifiants :

list_ways = MyApi.WaysGet([28201830,45332107,28201721])

Ces méthodes retournent un dictionnaire associant le contenu à l'identifiant :

list_ways[28201830]
{
  u'changeset': 6639572,
  u'uid': 93291, 
  u'timestamp': u'2010-12-12T18:09:34Z', 
  u'nd': [
    309756291, 
    309756292, 
    309756293,
    339037635
  ], 
  u'tag': {
    u'name': u'Rue du Ch\xe2teau',
    u'highway': u'pedestrian', 
    u'surface': u'cobblestone'
  }, 
  u'visible': True,
  u'version': 6,
  u'user': u'Ashar Voultoiz', 
  u'id': 28201830
}

Travailler sur une zone précise

Vous me direz que tout ceci est bien joli, mais que la plupart du temps on ne connaît pas les identifiants des nœuds, des chemins ou des relations, mais que l'on préfère travailler sur une zone géographique. Par exemple, le cœur de la ville de Nantes. La méthode Map est faite pour ça.

La méthode Map prend en paramètre une bbox. Ainsi, pour le cœur de la ville de Nantes, nous choisirons la bbox [-1.57,47.21,-1.54,47.22] :

data = MyApi.Map(-1.57, 47.21, -1.54, 47.22)

Attention : une telle requête représente une dizaine de Mo de données.

Une telle requête renvoie une liste d'éléments OSM de la forme suivante :

[
  {
    u'data': {
      u'changeset': 7046402, 
      u'uid': 79386, 
      u'timestamp': u'2011-01-22T01:06:54Z', 
      u'lon': -1.5529162000000001, 
      u'visible': True, 
      ...
      u'id': 26686548
    },
    u'type': u'node'
  },
  {
    u'data': {
      u'changeset': 2226053, 
      u'uid': 37548,
      ...
      u'tag': {}, 
      u'id': 35676755
    }, 
    u'type': u'node'
  },
  ...
]

On peut facilement compter le nombre de chaque élément dans le cœur de ville :

>>> for i in data:
...     t = i["type"]
...     if not counters.has_key(t):
...             counters[t] = 1
...     else:
...             counters[t] += 1
... 
>>> counters
{u'node': 34135, u'relation': 234, u'way': 5135}

ou bien lister les principaux contributeurs :

users = {}
for i in data:
	u = i["data"]["user"]
	if not users.has_key(u):
		users[u] = 1
	else:
		users[u] += 1
 
from operator import itemgetter
users_sorted = sorted(users.items(), key=itemgetter(1), reverse=True)
for user,nbelt in users_sorted[:10]:
	print u"%s (%s)" % (user, nbelt)
  1. BrunoC (22204)
  2. lcroc (7511)
  3. Stéphane Péchard (2239)
  4. snotling (2194)
  5. DrazziB (1521)
  6. Ashar Voultoiz (1120)
  7. yoann (681)
  8. cycloo (466)
  9. ab_fab (374)
  10. philippekerla (307)

N'hésitez pas à aller leur payer des bières pour les remercier de leur travail... quant à moi je ferais bien de me remettre un peu au Mapping Nantais :D