Skip to content

Géocoder en masse plusieurs milliers d’adresses avec Python et Nominatim

Pour de multiples projets, j’ai souvent utilisé le même géocodeur en masse (gratuit). Celui-ci s’appuyait sur l’API de Google Maps et a vu par conséquent son nombre de géocodages autorisés par jour diminuer fortement (2500 env.). A la recherche d’une alternative, je n’ai pas trouvé mon bonheur (trop cher, trop long à déployer, pas la fonctionnalité désirée, etc.). Par conséquent, j’ai pris quelques minutes pour développer un petit script Python qui, s’appuyant sur le géocodeur Nominatim lié à OpenStreetMap, permet de géocoder plusieurs dizaines de milliers d’adresses.

Utiliser la librairie GeoPy

Après avoir été installée, la librairie GeoPy permet d’utiliser un certain nombre de géocodeurs différents. Ici, j’ai volontairement utilisé Nominatim.

from geopy.geocoders import Nominatim

L’utilisation de GeoPy est très simple puisque la méthode « geocode » n’a besoin que d’un argument, à savoir l’adresse à géocoder. Pour ma part, j’ai utilisé deux arguments supplémentaires : le premier me permet d’indiquer au géocodeur que je ne souhaite récupérer qu’un seul et unique résultat, et le second me permet de modifier le « timeout » par défaut du géocodeur (ici, à 30 secondes).

geocoder = Nominatim()
adresse = "191 rue Saint-Jacques, Paris, France"
location = geocoder.geocode(adresse, True, 30)
print((location.latitude, location.longitude))

Seul point un peu frustrant de cette méthode, le géocodage d’une adresse prend une grosse demi-seconde. Pour des dizaines de milliers d’adresses, le géocodage en masse peut donc prendre plusieurs heures. Ce script est donc une bonne solution pour un géocodage unique, du « one-shot ».

Bon. Voilà pour une adresse. Mais, justement, pour plusieurs dizaines de milliers maintenant ? Et bien bouclons !

Géocoder des adresses contenues dans un fichier CSV

Pour se faire, j’ai utilisé la librairie « csv » native :

import csv

Avec cette librairie, en quelques lignes, il est possible de faire une boucle sur toutes les lignes d’un fichier CSV.

file = open('adresses.csv', 'rb')
csv = csv.reader(file, delimiter=';')
for ligne in csv:
    adresse = ligne[0] + ", " + ligne[1] + ", FRANCE"

Notez que la variable « ligne » est une liste (utilisation des crochets pour pointer sur telle ou telle colonne). Bon, maintenant ajoutons-y le géocodage :

import csv
from geopy.geocoders import Nominatim
geocoder = Nominatim()
file = open('adresses.csv', 'rb')
csv = csv.reader(file, delimiter=';')
for ligne in csv:
    adresse = ligne[0] + ", " + ligne[1] + ", FRANCE"
    location = geocoder.geocode(adresse, True, 30)
    print((location.latitude, location.longitude))

Écrire le résultat du géocodage dans un fichier CSV

C’est encore la librairie « csv » qui va nous servir ici.

import csv
file = open('result_int.csv', 'w')
data = csv.writer(file, delimiter=';', lineterminator='\n')
data.writerow(('colonne1', 'colonne2'))
data.writerow(('1','42'))

Ce bout de code crée un fichier CSV (mode « w » pour « write ») et y ajoute deux lignes : une avec les entêtes de deux colonnes et une première ligne contenant des valeurs. Notez bien les doubles parenthèses nécessaires (la méthode « writerow » n’attend qu’un argument, on lui donne donc un tuple).

Il ne reste plus qu’à mettre ce « writerow » dans notre boucle, non ?

Le script complet

import csv
from geopy.geocoders import Nominatim
geocoder = Nominatim()
inputFile = open('adresses.csv', 'rb')
outputFile = open('resultat.csv', 'w')
try:
	outputData = csv.writer(outputFile, delimiter=';', lineterminator='\n')
	outputData.writerow(('adresse', 'latitude', 'longitude'))
	inputData = csv.reader(inputFile, delimiter=';')
	for ligne in inputData:
		adresse = ligne[0] + ", " + ligne[1]
		try:
			location = geocoder.geocode(adresse, True, 30)
			outputData.writerow((adresse, location.latitude, location.longitude))
		except Exception as inst:
			print(inst)
finally:
	inputFile.close()
	outputFile.close()

Quoi de nouveau dans ce script ? Pas grand chose, mise à part l’ajout de deux blocs d’instructions de type « try/except/finally ». Ces blocs permettent « d’attraper » des erreurs, afin d’éviter que le script ne plante en cours de route. Dans notre cas, des erreurs peuvent être effectivement « jetées » par la méthode « geocode » (géocodeur injoignable, quota atteint, requête « timed out », etc.).

Notez que dans le bloc « finally » (qui est joué que l’on soit passé dans le try ou dans l’except), se trouvent deux instructions qui « referment » les deux fichiers CSV ouverts.

Pour ma part, je préfère lire et écrire dans deux fichiers CSV différents mais ce n’est probablement pas obligatoire.

Conclusion

Ce script, opérationnel, n’est pas du tout optimisé en termes de performance. Néanmoins, il fait le « job » et est pour moi une alternative au batch géocodeur que j’utilisais jusqu’ici. Il faudrait peut-être regarder du côté du multithreading ou du multiprocessing pour paralléliser toutes ces demandes de géocodage.

  1. Bonjour Adrien,

    Bravo pour votre travail !
    J’essaie moi aussi de contourner les limitations mises en place par Google et plus par curiosité intellectuelle de nécessité professionnelle, essaie de trouver des solutions parallèles.
    Je pense pouvoir adapter le programme « .py » mais ne parviens pas à le faire fonctionner (vieille machine sur Windows XP)
    Je suppose qu’il me faut télécharger des programmes, mais est-ce bien cela ?
    Faut il les mettre dans un répertoire précis ?
    programmes à charger bibli CSV, Nominatim, Geopy ? (Geopy déja fait)
    Je vous remercie pour votre aide si vous trouvez un peu de temps.
    L’idée serait de mieux préciser pour les néophytes, comment faire fonctionner tout cela. Ce qu’il faut télécharger, où les placer…
    Vous en remerciant par avance
    Bien cordialement

    Commentaire déposé le 31/01/2015
    • Bonjour,
      Effectivement, mon billet n’est pas adapté à des néophytes…
      Le téléchargement et l’installation de Python sur une machine Windows est assez simple.
      L’installation de la librairie GeoPy (qui implémente Nominatim) peut se faire automatiquement via PIP (ligne de commande : pip install geopy).
      L’installation de PIP, si ce n’est pas fait, est également rapide (ligne de commande : python get-pip.py).
      La librairie CSV, quant à elle, est native (c’est à dire, présente dans Python par défaut).
      Cordialement,
      Adrien

      Commentaire déposé le 03/02/2015
      • jy56 Permalien

        Merci Adrien d’avoir pris de votre temps pour cette réponse.
        Très bonne continuation
        Cordialement
        Jean-Yves LE NAOUR

        Commentaire déposé le 04/02/2015
      • Rickou Permalien

        Bonjour,

        J’ai installé PYTHON mais quand je tape la ligne de commande python get-pip.py il me renvoi syntaxError : invalid syntax sur le GET.

        Merci

        Commentaire déposé le 08/02/2015
        • Bonjour,
          Où avez-vous lancer cette ligne de commande ? Il faut la lancer depuis l’invite de commandes (cmd) en ayant pris soin d’indiquer le « path » de Python dans les variables d’environnement de votre machine…
          Cdt,
          Adrien

          Commentaire déposé le 08/02/2015
  2. Rickou Permalien

    Merci pour votre réponse, effectivement je ne l’avait pas lancer depuis le cmd.

    Cependant même en lançant depuis le cmd ca ne fonctionne pas j’ai un message d’erreur

    can’t open file ‘get-pip.py [errno 31] no such file or directory

    Si ensuite je lance pip install geopy il renvoi ‘pip’ n’est pas reconnu en tant que commande interne …

    Merci pour votre aide

    Commentaire déposé le 08/02/2015
    • Je peux me tromper mais je crois que vous n’êtes pas au bon endroit de l’arborescence. Il faut vous déplacer dans le bon dossier à l’aide de la commande « cd ». Le dossier à cibler est celui où vous avez téléchargé le script « get-pip.py ».

      Commentaire déposé le 08/02/2015
  3. Rickou Permalien

    Super merci,
    plusieurs choses, je n’avais même pas compris qu’il fallait télécharger le script, donc du coup j’ai réussi l’install.

    Me reste plus qu’a lancer ce script …

    Donc je fait un copier coller du votre et le sauvegarde das un fichier .py ?

    Quelle est la démarche et quelle commande pour le lancer ?

    Merci

    Commentaire déposé le 08/02/2015
    • Oui, c’est cela… Prenez juste soin de modifier le nom du fichier en entrée et celui du fichier en sortie. Pensez également à modifier la recomposition de l’adresse (adresse = ligne[0] + « ,  » + ligne[1]) en fonction des colonnes de votre propre CSV !

      Une fois votre script prêt, vous pouvez le lancer en ligne de commande (en étant toujours dans le bon dossier bien entendu).

      Commentaire déposé le 08/02/2015
  4. Rickou Permalien

    et pour le lancer en ligne de commande je tape quoi ? juste le nom de mon fichier script ?

    Merci

    Commentaire déposé le 08/02/2015
  5. Il suffit de faire exactement pour le script get-pip : python monscript.py
    Faites juste attention à être dans le bon dossier. Même chose en ce qui concerne les deux fichiers CSV. Je vous conseille de mettre votre script et vos deux fichiers CSV dans le même dossier. Et de vous placer dans ce dossier avec la commande « cd ».

    Commentaire déposé le 08/02/2015
  6. Rickou Permalien

    Merci pour tout, je vais tester tout cela
    Merci encore

    Commentaire déposé le 08/02/2015
  7. Rickou Permalien

    Bonjour,

    Je reviens vers vous encore une fois, qd je lance le script il me renvoi

    File « geo.py », line 16
    print inst

    SyntaxError : Missing parentheses in call to ‘print’

    Après avoir mis les parenthèse pour (inst) plus ce message d’erreur.

    Par contre et c’est peut être lié a mon csv mais j’ai une erreur a la ligne 10
    NameError: name ‘dataInput’ is not defined.

    exemple csv :
    12 AV LOUIS ARAGON 31700 BLAGNAC
    13 AV LOUIS ARAGON 31700 BLAGNAC
    15 AV LOUIS ARAGON 31700 BLAGNAC
    10 AV LUCIEN SERVANTY 31700 BLAGNAC
    11 AV LUCIEN SERVANTY 31700 BLAGNAC
    12 AV LUCIEN SERVANTY 31700 BLAGNAC
    13 AV LUCIEN SERVANTY 31700 BLAGNAC

    merci encore

    La je ne sais plus faire !!!

    Commentaire déposé le 09/02/2015
  8. Cette seconde erreur vient de mon script ! Il faut remplacer dataInput par inputData…

    Et au vu de votre CSV, il vous faut changer aussi : adresse = ligne[0]

    Commentaire déposé le 09/02/2015
  9. Rickou Permalien

    Bonjour,

    Après modif j’ai un autre message d’erreur :

    Traceback
    File « geo.py », line 10, in
    for ligne in inputData:
    _csv.Error: iterator should return strings, not byte

    merci

    Commentaire déposé le 09/02/2015
  10. Rickou Permalien

    Le ‘rb » n’allait pas bien, je l’ai donc modifié en ‘r’ mais du coup j’ai un autre message d’erreur :

    ‘Nonetype’ object has no attribute ‘latitude’

    Merci

    Commentaire déposé le 10/02/2015
  11. Rickou Permalien

    Bonjour,

    Voila mon script modifié en mettant 4 espaces a la place des TAB car j’avais un message d »erreur TABError……

    Par contre je n’ai pas de geolocalisation, le fichier résultat reste vide hormis les noms des champs.

    Si vous avez une idée a me soumettre je suis preneur, merci par avance !!

    [CODE]import csv
    from geopy.geocoders import Nominatim
    geocoder = Nominatim()
    inputFile = open(‘adresses.csv’, ‘r’)
    outputFile = open(‘resultat.csv’, ‘w’)
    try:
    outputData = csv.writer(outputFile, delimiter=';’, lineterminator=’\n’)
    outputData.writerow((‘adresse’, ‘latitude’, ‘longitude’))
    inputData = csv.reader(inputFile, delimiter=';’)
    for ligne in inputData:
    adresse = ligne[0]
    try:
    location = geocoder.geocode(adresse, True, 30)
    if location is not None:
    outputData.writerow((adresse, location.latitude, location.longitude))
    except Exception as inst:
    print(inst)
    finally:
    inputFile.close()
    outputFile.close()[/CODE]

    Commentaire déposé le 10/02/2015
  12. Rickou Permalien

    Bonsoir,

    Je pense que cela doit venir du fichier CSV, auriez vous un exemple de sa structure a me fournir ?

    Merci

    Commentaire déposé le 10/02/2015
  13. Rickou Permalien

    Bon c’est OK en fait il voulais une « , » a la fin de chaque ligne, cependant il a geolocalisé 65 adresse sur les 2500 est ce du au moteur « Nominatim » ?

    Comment fair pour essayer avec un autre moteur ?

    Merci

    Commentaire déposé le 10/02/2015
  14. Effectivement, ce n’est pas beaucoup…

    Cela peut venir de Nominatim. Vous pouvez changer de géocodeur en consultant la doc de GeoPy.

    Essayez peut-être avant cela de modifier vos données car, Nominatim ne trouve pas l’adresse 12 AV LOUIS ARAGON, 31700 BLAGNAC mais il trouve l’adresse 12 AV LOUIS ARAGON, BLAGNAC. Etonnant, non ?

    Commentaire déposé le 13/02/2015
  15. belhassinek Permalien

    Bonjour,

    Je vous remercie pour ce tutoriel.

    Je voudrais juste avoir de l’information sur la limite de cette méthode de géocoder car j’ai 1.2 million d’adresse à travers le Canada et les États-Unis à géocoder. Pensez-vous que cette méthode est suffisante pour cela. Sinon, pourriez-vous m’orienter vers un autre algorithme.

    Un grand merci.

    Commentaire déposé le 12/07/2015
    • Bonjour,
      Cette méthode est assez lente (0,5 sec par adresse). Pour vous, cela pourrait prendre au moins 7 jours de traitement. Je pense donc que la méthode n’est pas viable au vu de votre volumétrie. Il faudrait passer sur des solutions professionnelles (payantes) ou alors voir du côté des BDD d’adresses telle qu’OpenAdresses.
      Adrien

      Commentaire déposé le 15/07/2015
  16. Nicolas Permalien

    Bonjour,

    tout d’abord merci pour votre script !

    Je dois dans le cadre d’un travail de recherche géocoder un nombre très important d’adresses. J’aurai plusieurs questions.

    1/ Etant donné que l’API est nominatim, les coordonnées géographiques proviennent-elles d’OpenStreetMap ?

    2/ Pourriez vous me donner un exemple de présentation de vos données en csv ?

    D’avance merci,

    Nicolas

    Commentaire déposé le 15/07/2015
    • Bonjour,
      1. Oui, les coordonnées géographiques proviendront d’OSM.
      2. Dans mon exemple, on voit que je recompose une adresse à partir de deux colonnes de mon CSV (à savoir les colonnes 0 et 1). Du coup, le fichier CSV pourrait ressembler à :
      54 rue Gauthey;PARIS
      191 rue Saint-Jacques;PARIS
      etc.
      Mais en fait vous pouvez faire ce que vous voulez, tant que vous indiquez le bon « delimiter » (ici le point virgule) et les bons numéros de colonnes :)
      Cdt,
      Adrien

      Commentaire déposé le 17/07/2015
  17. Nicolas Permalien

    Merci de votre réponse !

    J’aurai une autre question. Mon fichier d »origine (celui que vous avez nommé « adresses ») contient des colonnes (nom d’entreprise par exemple) que je souhaiterai conserver dans le fichier de sortie (celui que vous avez appelé « resultat »). Auriez-vous une idée de script ? (J’ai cherché mais je n’ai rien trouvé)

    D’avance merci,

    Bien cordialement,

    Nicolas

    Commentaire déposé le 17/07/2015
  18. Nicolas Permalien

    Bonjour,

    finalement, j’aimerais écrire le résultat du géocodage dans le fichier d’origine afin d’identifier les adresses non géolocalisées. L’idéal serait que les colonnes latitude et longitude restent vides si la géolocalisation ne s’est pas effectuée.

    Si vous avez une idée de script, je suis preneur !

    Encore merci pour votre aide,

    Cdt

    Commentaire déposé le 18/07/2015
    • Bonjour Nicolas,

      Je pense que c’est faisable en « ouvrant » qu’un seul CSV mais en changeant la manière de l’ouvrir? Vous voyez les lettres « rb », cela doit signifier que je ne veux qu’ouvrir le fichier pour le lire sans écrire dedans. A l’inverse, la lettre « w » utilisée lors de la préparation du second CSV traduit le fait que je veux qu’écrire dedans. Il doit être possible d’utiliser un seul CSV en utilisant la bonne lettre !

      Plus d’infos sur la doc officielle.

      Adrien

      Commentaire déposé le 11/08/2015

Répondre à jy56 Annuler la réponse.

Vous pouvez utiliser un peu d'HTML dans votre commentaire.
Votre adresse e-mail ne sera pas publiée.

S'inscrire au flux RSS des commentaires de ce billet