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.
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
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
Merci Adrien d’avoir pris de votre temps pour cette réponse.
Très bonne continuation
Cordialement
Jean-Yves LE NAOUR
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
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
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
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 ».
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
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).
et pour le lancer en ligne de commande je tape quoi ? juste le nom de mon fichier script ?
Merci
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 ».
Merci pour tout, je vais tester tout cela
Merci encore
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 !!!
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]
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
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
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]
Bonsoir,
Je pense que cela doit venir du fichier CSV, auriez vous un exemple de sa structure a me fournir ?
Merci
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
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 ?
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.
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
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
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
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
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
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
Bonjour,
merci beaucoup pour ce script très utile pour géocoder en masse. J’ai cependant un problème justement lié au fait que les requêtes soient nombreuses :
HTTP Error 429: Too Many Requests
Suis-je le seul dans cette situation ? (mon fichier adresse fait 40000 éléments).
Serait-il possible d’ajouter un paramètre de temps afin que l’exécution soit étalée (1 requête toutes les 2 secondes par exemple, cela éviterait aux paramètres de sécurité des serveurs de s’affoler) ?
Encore merci en tout cas !
Bonjour Adrien et merci pour ce script très synthétique et fonctionnel.
J’ai néanmoins une question. Je dois scanner un fichier CSV constitué de milliers d’adresses et je me heurte aléatoirement à l’erreur suivante « GeocoderServiceError: » et depuis ce matin à celle-ci « GeocoderServiceError: HTTP Error 429: Too Many Requests ».
J’ai du mal à interpréter l’origine de ces problèmes. Avez-vous une idée ou un conseil pour lever ces barrières?
Je précise que le script fonctionnait bien la veille pour environ les 260 premières adresses de mon CSV.
Merci d’avance
Mathieu
Bonjour,
tuto très intéressant, j’aimerais utiliser ceci pour définir des itinéraires de manière industrielle, avez-vous des pistes ?
Merci
Loïc
Bonsoir,
Génial ce géocodage de masse.
J’aimerais l’utiliser mais il me faudrait récupérer une donnée supplémentaire qui ne semble pas être présente dans le résultat d’une requete, tant chez google map que chez nomatim, c’est l’IRIS pour chaque adresse géocodée.
Avez vous une idée ?
Juste pour dire que le géocodage avec OpenStreeMap Nominatim est soumis à des conditions d’utilisation, décrites ici :
https://operations.osmfoundation.org/policies/nominatim/
et dont la principale me parait :
*** pas plus d’une requête par seconde, svp ***
OSM c’est gratuit, ils n’ont pas de sous, il ne faut pas saturer leurs serveurs.
Dans le prog python, il serait donc ‘poli’ de mettre une petite pause dans la boucle :
import time au début
dans la boucle :
time.sleep(2.0) # pause 2.0 secondes
ça fait encore > 10000 requêtes par 24h…
Bonjour, je cherche une solution pour afficher la position temps réel sur une application python. Les données de positionnement sont rafraichis dans une base de données MySql. J’ai aussi d’autres données a afficher sur cette application (graphique, barometre…). Que me conseillez vous ?