Ban de AddOk

BAN de ADDOK

La base d'adresses nationale

La BAN 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 par exemple utilisée pour fournir l'API publique Adresse.

ADDOK

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.

Installation ah-hoc de ADDOK

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.

Alimentation du ADDOK par la BAN

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

Alors, ça géocode ?

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"|json_pp
{
"attribution" : "BAN",
"features" : [
    {
        "geometry" : {
            "coordinates" : [
            7.761454,
            48.579831
            ],
            "type" : "Point"
        },
        "properties" : {
            "city" : "Strasbourg",
            "citycode" : "67482",
            "context" : "67, Bas-Rhin, Grand Est",
            "id" : "67482",
            "importance" : 0.57347,
            "label" : "Strasbourg",
            "name" : "Strasbourg",
            "population" : 287228,
            "postcode" : "67000",
            "score" : 0.961224545454545,
            "type" : "municipality",
            "x" : 1051008.43,
            "y" : 6841654.82
        },
        "type" : "Feature"
    },
    {
        "geometry" : {
            "coordinates" : [
            1.445584,
            43.608391
            ],
            "type" : "Point"
        },
        "properties" : {
            "city" : "Toulouse",
            "citycode" : "31555",
            "context" : "31, Haute-Garonne, Occitanie",
            "id" : "31555_8328",
            "importance" : 0.81772,
            "label" : "Boulevard de Strasbourg 31000 Toulouse",
            "name" : "Boulevard de Strasbourg",
            "postcode" : "31000",
            "score" : 0.710701818181818,
            "type" : "street",
            "x" : 574477.65,
            "y" : 6280050.91
        },
        "type" : "Feature"
    },
    {
        "geometry" : {
            "coordinates" : [
            -0.554358,
            47.457152
            ],
            "type" : "Point"
        },
        "properties" : {
            "city" : "Angers",
            "citycode" : "49007",
            "context" : "49, Maine-et-Loire, Pays de la Loire",
            "id" : "49007_7460",
            "importance" : 0.7985,
            "label" : "Boulevard de Strasbourg 49000 Angers",
            "name" : "Boulevard de Strasbourg",
            "postcode" : "49000",
            "score" : 0.708954545454545,
            "type" : "street",
            "x" : 432292.98,
            "y" : 6712336.87
        },
        "type" : "Feature"
    },
    {
        "geometry" : {
            "coordinates" : [
            4.051151,
            49.258581
            ],
            "type" : "Point"
        },
        "properties" : {
            "city" : "Reims",
            "citycode" : "51454",
            "context" : "51, Marne, Grand Est",
            "id" : "51454_8430",
            "importance" : 0.78636,
            "label" : "Rue de Strasbourg 51100 Reims",
            "name" : "Rue de Strasbourg",
            "postcode" : "51100",
            "score" : 0.707850909090909,
            "type" : "street",
            "x" : 776529.48,
            "y" : 6907056.44
        },
        "type" : "Feature"
    },
    {
        "geometry" : {
            "coordinates" : [
            2.486589,
            48.924254
            ],
            "type" : "Point"
        },
        "properties" : {
            "city" : "Aulnay-sous-Bois",
            "citycode" : "93005",
            "context" : "93, Seine-Saint-Denis, Île-de-France",
            "id" : "93005_3380",
            "importance" : 0.78296,
            "label" : "Boulevard de Strasbourg 93600 Aulnay-sous-Bois",
            "name" : "Boulevard de Strasbourg",
            "postcode" : "93600",
            "score" : 0.707541818181818,
            "type" : "street",
            "x" : 662378.29,
            "y" : 6869485.62
        },
        "type" : "Feature"
    }
],
"licence" : "ETALAB-2.0",
"limit" : 5,
"query" : "strasbourg",
"type" : "FeatureCollection",
"version" : "draft"
}

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

Fichier 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

Previous Post