Journal BAN de ADDOK

Posté par  (site web personnel) . Licence CC By‑SA.
35
7
avr.
2021

La BAN (Base Adresse Nationale) est un jeu de données publié en Open Data et issu d'une collaboration entre plusieurs acteurs (communes, IGN, La Poste, OpenStreetMap,…).

La BAN contient 25 millions d'adresses françaises géocodées ainsi que d'autres informations (population des communes par exemple).

Elle est notamment utilisée pour fournir l'API publique Adresse.

ADDOK est un moteur de géocodage open source développé par l'Etalab. C'est ce moteur qui fourni l'API Adresse citée précédemment.

Ces deux éléments étant disponibles librement, il est donc possible de monter son propore service de géocodage reposant sur des données fiables.

Le projet ADDOK fourni une documentation plutôt bien réalisée sur l'installation du logiciel et son alimentation par la BAN, mais elle date un petit peu. Je vous propose ici une version simplifiée et un peu plus à jour pour une Debian Buster.

Dans un premier temps on commence par installer un certain nombre de pré-requis.

sudo apt install redis-server python3.7 python3.7-dev python-pip python3.7-venv

Le reste de l'installation et de la configuration peut se faire en tant qu'utilisateur standard.

On commence par créer un venv dédié.

cd $HOME
python3.7 -m venv addok
source addok/bin/activate

Une fois dans le venv on installe Addok.

pip install wheel
pip install addok
pip install addok-fr
pip install addok-france

Le logiciel étant installé, il faut maintenant créer un fichier de configuration.
Un exemple est fourni addok/lib/python3.7/site-packages/addok/config/default.py et pourrait être utilisé directement.
Il faut cependant le modifier quelque peu pour profiter des plugins addok-fr et addok-france.

On crée donc le fichier addok/addok.conf dont le contenu un peu long est à la fin de l'article.

Il reste maintenant à obtenir les données à injecter dans le géocodeur.
Ce dernier utilise une base Redis et en injectant la totalité de la BAN, il faudrait plusieurs Go de RAM.
Heureusement, il est possible de télécharger des extraits de la BAN par département.

Par exemple pour le Bas-Rhin:

wget https://adresse.data.gouv.fr/data/ban/adresses/latest/addok/adresses-addok-67.ndjson.gz
gunzip adresses-addok-67.ndjson.gz

Il suffit maintenant d'injecter les données récupérées:

addok batch adresses-addok-67.ndjson
addok ngrams

Un premier test simple consiste à ouvrir un shell addok et à géocoder une adresse:

addok --config addok/addok.conf shell
Addok 1.0.2
Loaded local config from addok/addok.conf
Loaded plugins:
addok.shell==1.0.2, addok.http.base==1.0.2, addok.batch==1.0.2, addok.pairs==1.0.2, addok.fuzzy==1.0.2, addok.autocomplete==1.0.2, france==1.1.0, fr==1.0.1

Welcome to the Addok shell o/
Type HELP or ? to list commands.
Type QUIT or ctrl-C or ctrl-D to quit.


> Strasbourg
Strasbourg (J6Ol | 0.9610972727272726)
Route de la Wantzenau 67000 Strasbourg (k5Pr | 0.7135927272727272)
Avenue de Colmar 67100 Strasbourg (xkZE | 0.7129881818181817)
Route de Schirmeck 67200 Strasbourg (76wG | 0.7129481818181818)
Route des Romains 67200 Strasbourg (P19l | 0.7128863636363635)
Route d’Oberhausbergen 67200 Strasbourg (667Q | 0.7127390909090908)
Route de Mittelhausbergen 67200 Strasbourg (NxnK | 0.7122436363636363)
Route du Polygone 67100 Strasbourg (qjrR | 0.7112654545454544)
Rue de la Ganzau 67100 Strasbourg (oYRk | 0.7110227272727272)
Rue Boecklin 67000 Strasbourg (y83E | 0.7103163636363635)
71.6 ms — 1 run(s) — 10 results
--------------------------------------------------------------------------------

Si cette étape fonctionne on peut lancer le serveur intégré:

addok --config addok/addok.conf serve
Addok 1.0.2
Loaded local config from addok/addok.conf
Loaded plugins:
addok.shell==1.0.2, addok.http.base==1.0.2, addok.batch==1.0.2, addok.pairs==1.0.2, addok.fuzzy==1.0.2, addok.autocomplete==1.0.2, france==1.1.0, fr==1.0.1
Serving HTTP on 127.0.0.1:7878…

Et enfin utiliser l'API pour géocoder l'adresse:

curl  "http://localhost:7878/search/?q=Strasbourg"
{"type": "FeatureCollection", "version": "draft", "features": [{"type": "Feature", "geometry": {"type": "Point", "coordinates": [7.761454, 48.579831]}, "properties": {"label": "Strasbourg", "score": 0.9610972727272726, "id": "67482", "type": "municipality", "name": "Strasbourg", "postcode": "67200", "citycode": "67482", "x": 1051008.43, "y": 6841654.82, "population": 279284, "city": "Strasbourg", "context": "67, Bas-Rhin, Grand Est", "importance": 0.57207}}, {"type": "Feature", "geometry": {"type": "Point", "coordinates": [7.787474, 48.616029]}, "properties": {"label": "Route de la Wantzenau 67000 Strasbourg", "score": 0.7135927272727272, "id": "67482_7080", "name": "Route de la Wantzenau", "postcode": "67000", "citycode": "67482", "x": 1052680.41, "y": 6845787.55, "city": "Strasbourg", "context": "67, Bas-Rhin, Grand Est", "type": "street", "importance": 0.84952}}, {"type": "Feature", "geometry": {"type": "Point", "coordinates": [7.749156, 48.558389]}, "properties": {"label": "Avenue de Colmar 67100 Strasbourg", "score": 0.7129881818181817, "id": "67482_1430", "name": "Avenue de Colmar", "postcode": "67100", "citycode": "67482", "x": 1050246.21, "y": 6839220.87, "city": "Strasbourg", "context": "67, Bas-Rhin, Grand Est", "type": "street", "importance": 0.84287}}, {"type": "Feature", "geometry": {"type": "Point", "coordinates": [7.709492, 48.567865]}, "properties": {"label": "Route de Schirmeck 67200 Strasbourg", "score": 0.7129481818181818, "id": "67482_6290", "name": "Route de Schirmeck", "postcode": "67200", "citycode": "67482", "x": 1047261.71, "y": 6840097.22, "city": "Strasbourg", "context": "67, Bas-Rhin, Grand Est", "type": "street", "importance": 0.84243}}, {"type": "Feature", "geometry": {"type": "Point", "coordinates": [7.707628, 48.578578]}, "properties": {"label": "Route des Romains 67200 Strasbourg", "score": 0.7128863636363635, "id": "67482_5690", "name": "Route des Romains", "postcode": "67200", "citycode": "67482", "x": 1047053.47, "y": 6841277.85, "city": "Strasbourg", "context": "67, Bas-Rhin, Grand Est", "type": "street", "importance": 0.84175}}], "attribution": "BANO", "licence": "ODbL", "query": "Strasbourg", "limit": 5}

L'ensemble de l'API est documenté sur le site geo.api.gouv.fr.

addok/addok.conf

import os
from pathlib import Path

REDIS = {
    'host': os.environ.get('REDIS_HOST') or 'localhost',
    'port': os.environ.get('REDIS_PORT') or 6379,
    'unix_socket_path': os.environ.get('REDIS_SOCKET'),
    'indexes': {
        'db': os.environ.get('REDIS_DB_INDEXES') or 0,
    },
    'documents': {
        'db': os.environ.get('REDIS_DB_DOCUMENTS') or 1,
    }
}
# Min/max number of results to be retrieved from db and scored.
BUCKET_MIN = 10
BUCKET_MAX = 100

# Above this treshold, terms are considered commons.
COMMON_THRESHOLD = 10000

# Above this treshold, we avoid intersecting sets.
INTERSECT_LIMIT = 100000

# Min score considered matching the query.
MATCH_THRESHOLD = 0.9

# Do not consider result if final score is below this threshold.
MIN_SCORE = 0.1

QUERY_MAX_LENGTH = 200

GEOHASH_PRECISION = 7

MIN_EDGE_NGRAMS = 3
MAX_EDGE_NGRAMS = 20

SYNONYMS_PATH = None

# Pipeline stream to be used.
PROCESSORS_PYPATHS = [  # Rename in TOKEN_PROCESSORS / STRING_PROCESSORS?
    'addok.helpers.text.tokenize',
    'addok.helpers.text.normalize',
    'addok_france.glue_ordinal', #ajout addok_france
    'addok_france.fold_ordinal', #ajout addok_france
    'addok_france.flag_housenumber', #ajout addok_france
    'addok.helpers.text.flag_housenumber',
    'addok.helpers.text.synonymize',
    'addok_fr.phonemicize', #ajout addok_france
]
QUERY_PROCESSORS_PYPATHS = [
    'addok.helpers.text.check_query_length',
    'addok_france.extract_address', #Ajout addok_france
    'addok_france.clean_query', #Ajout addok_france
    'addok_france.remove_leading_zeros', #Ajout addok_france
]
# Remove SEARCH_PREFIXES.
SEARCH_PREPROCESSORS_PYPATHS = [
    'addok.helpers.search.tokenize',
    'addok.helpers.search.search_tokens',
    'addok.helpers.search.select_tokens',
    'addok.helpers.search.set_should_match_threshold',
]
BATCH_PROCESSORS_PYPATHS = [
    'addok.batch.to_json',
    'addok.helpers.index.prepare_housenumbers',
    'addok.ds.store_documents',
    'addok.helpers.index.index_documents',
]
BATCH_FILE_LOADER_PYPATH = 'addok.helpers.load_file'
BATCH_CHUNK_SIZE = 1000
# During imports, workers are consuming RAM;
# let one process free for Redis by default.
BATCH_WORKERS = max(os.cpu_count() - 1, 1)
RESULTS_COLLECTORS_PYPATHS = [
    'addok.helpers.collectors.no_tokens_but_housenumbers_and_geohash',
    'addok.helpers.collectors.no_available_tokens_abort',
    'addok.helpers.collectors.only_commons',
    'addok.helpers.collectors.bucket_with_meaningful',
    'addok.helpers.collectors.reduce_with_other_commons',
    'addok.helpers.collectors.ensure_geohash_results_are_included_if_center_is_given',  # noqa
    'addok.helpers.collectors.extend_results_reducing_tokens',
    'addok.helpers.collectors.extend_results_extrapoling_relations',
]

SEARCH_RESULT_PROCESSORS_PYPATHS = [
    'addok.helpers.results.match_housenumber',
    'addok_france.make_labels', #Ajout addok_france
    'addok.helpers.results.make_labels',
    'addok.helpers.results.score_by_importance',
    'addok.helpers.results.score_by_autocomplete_distance',
    'addok.helpers.results.score_by_ngram_distance',
    'addok.helpers.results.score_by_geo_distance',
]
REVERSE_RESULT_PROCESSORS_PYPATHS = [
    'addok.helpers.results.load_closer',
    'addok.helpers.results.make_labels',
    'addok.helpers.results.score_by_geo_distance',
]
RESULTS_FORMATTERS_PYPATHS = [
    'addok.helpers.formatters.geojson',
]
INDEXERS_PYPATHS = [
    'addok.helpers.index.HousenumbersIndexer',
    'addok.helpers.index.FieldsIndexer',
    # Pairs indexer must be after `FieldsIndexer`.
    'addok.pairs.PairsIndexer',
    # Edge ngram indexer must be after `FieldsIndexer`.
    'addok.autocomplete.EdgeNgramIndexer',
    'addok.helpers.index.FiltersIndexer',
    'addok.helpers.index.GeohashIndexer',
]
# Any object like instance having `loads` and `dumps` methods.
DOCUMENT_SERIALIZER_PYPATH = 'addok.helpers.serializers.ZlibSerializer'

DOCUMENT_STORE_PYPATH = 'addok.ds.RedisStore'

# Fields to be indexed
# If you want a housenumbers field but need to name it differently, just add
# type="housenumbers" to your field.
FIELDS = [
    {'key': 'name', 'boost': 4, 'null': False},
    {'key': 'street'},
    {'key': 'postcode',
     'boost': lambda doc: 1.2 if doc.get('type') == 'municipality' else 1},
    {'key': 'city'},
    {'key': 'housenumbers'},
    {'key': 'context'},
]

# Sometimes you only want to add some fields keeping the default ones.
EXTRA_FIELDS = []

# Weight of a document own importance:
IMPORTANCE_WEIGHT = 0.1

# Default score for the relation token => document
DEFAULT_BOOST = 1.0

# Data attribution
# Can also be an object {source: attribution}
ATTRIBUTION = "BANO"

# Data licence
# Can also be an object {source: licence}
LICENCE = "ODbL"

# Available filters (remember that every filter means bigger index)
FILTERS = ["type", "postcode"]

LOG_DIR = os.environ.get("ADDOK_LOG_DIR", Path(__file__).parent.parent.parent)

LOG_QUERIES = False
LOG_NOT_FOUND = False

INDEX_EDGE_NGRAMS = True
  • # Ressources requises

    Posté par  . Évalué à 2.

    Merci pour ce partage, c'est très instructif.
    Nos serveurs sont sous opensuse, mais les dépendances sont légères => je vais tester.
    De quel dimensionnement du serveur (ram/disque) as-tu eu besoin pour un département ?

    • [^] # Re: Ressources requises

      Posté par  (site web personnel) . Évalué à 1.

      De rien…
      A priori juste avec le Bas-Rhin, Redis+addok en mode serveur, il faut un peu moins de 200Mo de RAM.
      Pour le disque, c'est essentiellement la sauvegarde Redis qui pourra prendre de la place, pour un département ce n'est que 30Mo.

      Pour l'ensemble de la France, il faut compter 20Go de RAM par contre.

      L'installation décrite est pour test (le serveur HTTP intégré n'est pas le plus performant), pour une installation plus définitive il vaut mieux passer par UWSGI.
      L'installation est décrite dans la documentation addok.

  • # Ban ! Again ?

    Posté par  (site web personnel) . Évalué à 8.

    En lisant le titre je me suis dit que c'était bizarre un deuxième utilisateur de LinuxFR qui se faisait bannir en si peu de temps…

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.