Arcmap : Un script Python pour géocoder des adresses avec OpenStreetMap

A l’époque où QGis permet un geocodage d’adresses gratuit, illimité et simple d’utilisation (voir l’article Geocodages d’adresses avec QGis 2.8), le géocodage avec ArcGis s’englue dans des crédits, des clés API et autres artifices pour rendre payant et compliqué le même résultat. Heureusement que certaines portes ne peuvent pas être verrouillées et qu’il reste des moyens de faire, sans y passer forcément à la caisse. Voici un petit script Python qui permet de géocoder un fichier d’adresses avec OpenStreetMap à partir d’ArcMap.

La base de ce script nous la devons à Riccardo. Pour géocoder un fichier d’adresses, c’est à dire prendre un enregistrement avec l’adresse d’un point et le convertir en un point dans un shape, il nous faut:

1- le module geopy de Python

2-un fichier texte avec les adresses à géocoder

3-la toolbox d’ArcMap

Installation du module geopy

Pour télécharger la dernière version au jour de cet article, cliquez ici

Pour avoir un version actualisée, allez à la page d’accueil du Repository Python: , recherchez geopy avec la fenêtre de recherche et téléchargez la dernière version disponible de geopy en version source (et non en Python wheel).

Décompressez le fichier téléchargé et recherchez un répertoire geopy (sans autre caractère):

répertoire geopy

Copiez ce répertoire dans le répertoire Python d’ArcGis. En principe il se trouve sur votre disque C, répertoire Python27->ArcGis10.X->Lib

répertoire python arcgis

Voilà! Vous avez installé le module geopy.

Préparation du fichier d’adresses

Le géocodage va être effectué par un service web, dans notre cas le service Nominatim d’OpenStreetMaps. Il vous faut formater un minimum vos données si vous voulez que le service Web comprenne ce que vous voulez. Nous allons donc structurer un minimum les adresses. mais il y a un autre problème, c’est qu’en anglais cela suffirait, mais que nos sacrés caractères accentués français vont poser forcément problème. Nous devons donc résoudre aussi ce problème, sous peine que Nominatim ne trouve aucune adresse.

Pour commencer, si vous ne les avez pas déjà, chargez vos adresses dans un tableur. Vous devez avoir au moins deux colonnes:

  • dans la première vous mettrez l’adresse (numéro, rue,…)
  • dans la deuxième vous mettrez le code postal et la ville ou commune

La première ligne du tableau doit contenir des noms de champ. Évitez tout caractère spécial, le blanc, etc.

Pour nos deux champs, supposons que nous les nommons adresse et ville

Vous pouvez avoir plus de champs, mais par la suite nous n’utiliserons que ces deux champs là.

fichier excel d'adresses

Vous devez maintenant enregistrer le tableau sous forme de fichier texte. Vous avez plusieurs formats texte proposés par les tableurs. Dans le cas d’Excel, choisissez texte séparateur tabulations. En faisant ça vous choisissez non seulement le séparateur de champs mais aussi le type d’encodage du fichier. L’encodage, comme son nom l’indique, ce sont les codes ascii utilisés pour représenter les caractères, en particulier nos précieux caractères accentués. Si vous avez Notepad, vous pouvez voir l’encodage du fichier. Dans ce cas vous verrez encodage : ANSI.

Si vous ne suivez pas ces instructions, vous pourrez avoir un plantage du script sous Arcmap. Il vous faudra alors, soit modifier le script pour qu’il corresponde à l’encodage de votre fichier, soit revenir au fichier et l’encoder en ANSI (Latin-1).

Voilà! Le fichier d’adresses et prêt à être géocodé.

Le script Python

Le script proposé ici ne prétend pas être un outil fini. Il y a de multiples aspects qui ne sont pas pris en compte. Si tel quel vous suffit, c’est parfait. Si non, il vous servira de base pour que vous le modifiez et l’adaptiez à votre cas particulier.

Vous pouvez télécharger ici une boîte à outils contenant le script ainsi que le fichier texte d’exemple.

N’oubliez pas de configurer le chemin du script de la toolbox vers le fichier .py fourni.

Fonctionnement

Pour que le script fonctionne, il faut le fichier d’adresses que nous avons construit plus haut, ainsi qu’un shape vide de type point.

On pourrait créer le shape dans le script mais dans l’état actuel, vous devez créer un shape de type point, avec le système de coordonnées Géographique WGS84 et créer un champ texte appelé Nom où sera stockée l’adresse contenue dans l’enregistrement du fichier texte. Prévoyez assez de longueur de champ (100?) pour contenir cette adresse.

Sauf miracle, chaque fois qu’on géocode un fichier d’adresses, il y a des adresse qui ne sont pas trouvées par le service de geocodage. Ces adresses sont mises dans un fichier texte en sortie.

En lançant le script vous aurez la fenêtre de paramétrage suivante:

parametrage de la commande de geocodageShapesor est le fichier shape de points où vous allez récupérer les adresse géocodées

fichier adresses est le fichier texte contenant les adresses

adresse est le nom du champ du fichier adresses contenant l’adresse (numéro, rue, …)

ville est le nom du champ du fichier adresses contenant le code postal et la ville

fichier erreurs est le nom du fichier où seront mises les adresse qui n’auront pas été géocodées.

Contenu du script

Voici le script dans sa totalité:

import arcpy
import geopy
import csv
from geopy.geocoders import Nominatim
geolocator = Nominatim()

failed_text = » »
numbers_failed = 0
outShp=arcpy.GetParameterAsText(0)
inFC=arcpy.GetParameterAsText(1)
outFC=arcpy.GetParameterAsText(4)
adresse=arcpy.GetParameterAsText(2)
place=arcpy.GetParameterAsText(3)
cursor = arcpy.InsertCursor(outShp)
with open(outFC, ‘w’) as file:
    with open(inFC, ‘rb’) as csvfile:
        content = csv.DictReader(csvfile, delimiter=’\t’)
        for row in content:
            feature = cursor.newRow()
            vertex = arcpy.CreateObject(« Point »)
            coord = geolocator.geocode(row[adresse].decode(‘latin1’) + row[place].decode(‘latin1’) ,timeout=25)
            
                        if coord is None:
                failed_text += row[adresse] + row[place] + « \n »
                numbers_failed += 1
                                
            if coord is not None:
                                vertex.X = coord.longitude
                vertex.Y = coord.latitude
                                feature.shape=vertex
                                feature.Nom=row[adresse]
                cursor.insertRow(feature)
                               
    file.write(failed_text)
    file.close()
del cursor
print « failed geocodes:  » + str(numbers_failed) + « !!! check the file  » + outFC

dataframe = arcpy.mapping.ListDataFrames(arcpy.mapping.MapDocument(‘current’))[0]  
geocodelayer = arcpy.mapping.Layer(outShp)
arcpy.mapping.AddLayer(dataframe,geocodelayer, »BOTTOM »)
layer_extent = geocodelayer.getExtent()
dataframe.extent = layer_extent

Voyons maintenant les différentes parties.

Tout d’abord, nous avons le choix du geolocator:

from geopy.geocoders import Nominatim
geolocator = Nominatim()

Si vous souhaitez utiliser un autre service de geocodage, c’est ici qu’il faut le choisir. N’oubliez pas que certains services demandent une clé API, un nom d’utilisateur, un mot de passe. Dans ces cas il faudra ajouter le code nécessaire pour initialiser la connexion.

Deuxièmement, nous avons la récupération des paramètres fournis dans la fenêtre de paramétrage du script:

outShp=arcpy.GetParameterAsText(0)
inFC=arcpy.GetParameterAsText(1)
outFC=arcpy.GetParameterAsText(4)
adresse=arcpy.GetParameterAsText(2)
place=arcpy.GetParameterAsText(3)

Vous pouvez modifier le script pour avoir plus d’options de paramétrage, par exemple le séparateur utilisé dans le fichier d’adresses. Dans ce cas vous devez ajouter le paramètre dans les paramètres du script: clic droit sur le script dans la fenêtre de la toolbox ->Propriétés -> onglet Paramètres

ajout d'un paramètre au script

et rajouter une ligne de code avec un nom de variable et l’index du paramètre:

separateur=arcpy.GetParameterAsText(6)

La ligne:

cursor = arcpy.InsertCursor(outShp)

met en place un curseur sur le fichier shape qui permet l’insertion des points des adresses trouvées.

La ligne :

 content = csv.DictReader(csvfile, delimiter=’\t’)

utilise le module Python csv pour lire le contenu du fichier d’adresses dans une variable content. L’option delimiter=’\t’ indique au module que le séparateur des champs est la tabulation. Si vous utilisez un autre séparateur (ce que je vous déconseille), c’est ici qu’il faut l’indiquer.

 feature = cursor.newRow()
vertex = arcpy.CreateObject(« Point »)

correspondent à la création d’un nouvelle entité de type point dans le fichier shape en sortie.

L’appel au géocodage utilise les variables adresse et place qui correspondent aux champs adresse et ville des paramètres du script.

 coord = geolocator.geocode(row[adresse].decode(‘latin1’) + row[place].decode(‘latin1’) ,timeout=25)

Selon l’encodage du fichier texte, vous pourrez être amenés à modifier ‘latin-1’. Vous vaez de multiples possibilités, mais les plus courantes sont ‘utf-8’ et ‘UTF-16LE’.

Si le retour du géocodage (coord) est vide on écrit l’adresse dans le fichier d’erreurs et s’il n’est pas vide on créé le point dans le fichier shape.

 if coord is None:
                failed_text += row[adresse] + row[place] + « \n »
                numbers_failed += 1
                                
 if coord is not None:
                vertex.X = coord.longitude
                vertex.Y = coord.latitude
                feature.shape=vertex
                feature.Nom=row[adresse]
                cursor.insertRow(feature)

feature.Nom correspond au champ Nom créé dans le fichier shape au départ. Si vous appelez le champ autrement que Nom il faut modifier cette ligne.

La fin du script charge le fichier shape dans le document actif d’ArcMap.

Le résultat final de la commande est le suivant:

résultat final du geocodage

Si cet article vous a intéressé et que vous pensez qu'il pourrait bénéficier à d'autres personnes, n'hésitez pas à le partager sur vos réseaux sociaux en utilisant les boutons ci-dessous. Votre partage est apprécié !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *