Accueil / Blog / Métier / 2020 / Enrichissement de données ouvertes, croisement spatial et web sémantique

Enrichissement de données ouvertes, croisement spatial et web sémantique

Par Frédéric Rodrigo — publié 05/05/2020
Un cas pratique basé sur le besoin d’avoir des informations sur des monuments classés à l’UNESCO, l’utilisation du web sémantique est ici explorée avec Wikidata et DATAtourisme ainsi que le croisement de données ouvertes (OpenData) dans des formats classiques comme le CSV ou encore avec OpenStreetMap.

Chemins de Compostelle

Plusieurs approches ou outils pour la récupération et le croisement de données sont possibles. Les traitements ont été faits ici avec des outils en ligne de commande tels que « curl » pour le téléchargement, « csvkit » ou les manipulations de CSV et « ogr2ogr » ou les données géographiques.

Parmi les sites classés à l’UNESCO, il y en a un d’un peu particulier nommé « Chemins de Compostelle en France ». Celui-ci est composé de 71 constructions (cathédrales, églises, ponts…) et 7 tronçons de chemin répartis en France.

Ici, ous recherchons à obtenir des informations sur les différentes « composantes » de ce « site » et leurs environnements immédiats en utilisant et croisant des sources de données libres et ouvertes.

Web sémantique et Wikidata

Ils nous l’ont longtemps promis, mais aujourd’hui le web sémantique est disponible et utilisable, notamment via Wikidata. Ce dernier est un projet de la Fondation Wikimédia, dérivé de Wikipédia. Tous ceux qui ont déjà essayé de retraiter de façon automatique le contenu de Wikipédia savent combien c'est compliqué, en particulier pour en tirer des informations structurées. Aujourd’hui, Wikidata fournit cette information.

Wikidata est une base de données mais pas au sens le plus classique du terme. Il s'agit d'un graphe d’information structurée. Il est composé de « triplets » : sujet, prédicat, objet. Tout y est décrit suivant ce formalisme, par exemple :

  • Le sujet « Linux » (Q14579) a pour prédicat « nommé depuis » (P138) l’objet « Linus Torvalds » (Q34253),
  • Le sujet « Linus Torvalds » (Q34253) a pour prédicat « date de naissance » (P569) la valeur « 28 décembre 1969 ».

Ces informations servent par exemple à créer les boites d’information sur les pages de Wikipédia.

Info box Wikipédia

Exemple d’une boite d’information Wikipédia dont le contenu est issu de Wikidata.

Des recherches sur ce graphe pour répondre à des questions peuvent également être faites. Le langage pour faire de telles requêtes est le « SPARQL ». Wikidata dispose d’une API et d’une interface web pour l’interroger : Wikidata Query Service. L’interface permet d’écrire, d’exécuter et de visualiser les résultats de requêtes, mais aussi de charger des exemples de requêtes toutes faites.

Nous pouvons nous poser la question suivante : quelle est la date de naissance de « Linus Torvalds » ?

SELECT ?date
WHERE 
{
    # Sujet « Linus Torvalds » (Q34253)
    # Prédicat « date de naissance » (P569)
    # Variable de l’objet recherché : ?date
    wd:Q34253 wdt:P569 ?date.
}

Obtenir les composantes classées et leurs détails depuis Wikidata

Notre point de départ est l’entité Wikidata du « site » classé à l’UNESCO. Nous pouvons par exemple le retrouver sur la page Wikipédia : Q2962473, c’est un identifiant unique et stable, indépendant de la langue.

Fiche Wikidata

Les premières propriétés de la fiche Wikidata Q2962473.

En premier lieu, la propriété qui va nous intéresser est celle du prédicat « à pour partie » (P527) qui a autant d’instances que de composantes. La requête suivante va donc nous les renvoyer toutes.

SELECT
    ?compo ?compoLabel
WHERE {
    wd:Q2962473 wdt:P527 ?compo.
}
wd:Q20883
wd:Q106934
wd:Q207985
wd:Q217452
wd:Q222501
wd:Q333850
[...]

Certes le résultat est là, mais peu lisible. Nous allons utiliser en complément un service de Wikidata qui permet de retourner le nom textuel en français correspondant. Ce service recherche un intitulé pour les variables suffixées par « Label ».

SELECT
    ?compo ?compoLabel
WHERE {
    wd:Q2962473 wdt:P527 ?compo.

    SERVICE wikibase:label {
        bd:serviceParam wikibase:language "[AUTO_LANGUAGE],fr".
    }
}
wd:Q20883 mont Saint-Michel
wd:Q106934 cathédrale Notre-Dame d'Amiens
wd:Q207985 cathédrale Saint-Étienne de Bourges
wd:Q217452 basilique Sainte-Marie-Madeleine
wd:Q222501 église Saint-Pierre d'Aulnay
[...]

Le résultat se lit donc : Le sujet « Chemins de Compostelle en France » (Q2962473) a comme parties le « mont Saint-Michel » (Q20883), la « cathédrale Notre-Dame d'Amiens » (Q106934)…

Chaque composante est elle-même un objet qui possède des propriétés. Celles-ci sont variables suivant les types d’objets et les objets eux-mêmes.

Pour chaque composante, nous souhaitons ensuite récupérer la valeur de certaines propriétés comme les coordonnées géographiques, la commune de localisation, le site web, le type d’architecture, etc. Nous allons continuer en prenant pour exemple les plans des composantes. Ce sont en fait les URL des images de ces plans vers Wikimedia Commons (projet frère de Wikipédia pour stocker les médias).

Nous repartons donc des composantes pour obtenir la valeur de la propriété « image du plan » (P3311).

SELECT
    ?compo ?plan_image
WHERE {
    wd:Q2962473 wdt:P527 ?compo.
    ?compo wdt:P3311 ?plan_image.
}
wd:Q207985 commons:Plan.cathedrale.Bourges.png
wd:Q333850 commons:F07 Cadouin Grundriss.jpg
wd:Q333984 commons:Plan de l'abbaye (Drouyn 1851).png
wd:Q770459 commons:Plan.eglise.ND.du.port.Clermont.Ferrand.png
wd:Q847628 commons:Plan.cathedrale.Cahors.png
[...] [...]

Toutefois cette requête ne permet d’obtenir que les composantes qui ont un plan, ce qui correspond à un « INNER JOIN » en SQL. Or, nous voulons conserver toutes les composantes, même celles sans plan (« LEFT JOIN » en SQL). On va donc rendre le plan optionnel.

SELECT
    ?compo ?plan_image
WHERE {
    wd:Q2962473 wdt:P527 ?compo.
    OPTIONAL { ?compo wdt:P3311 ?plan_image. }
}
wd:Q222501
wd:Q333850 commons:F07 Cadouin Grundriss.jpg
wd:Q333984 commons:Plan de l'abbaye (Drouyn 1851).png
wd:Q334150
wd:Q334217
[...] [...]

Nous pouvons également avoir des composantes avec plusieurs instances de plan. Nous allons donc agréger les plans pour avoir une seule ligne par composante (équivalent du « GROUP BY » en SQL).

SELECT
    ?compo
    (GROUP_CONCAT(DISTINCT ?plan_image; SEPARATOR=", ") AS ?plan_images)
WHERE {
    wd:Q2962473 wdt:P527 ?compo.
    OPTIONAL { ?compo wdt:P3311 ?plan_image. }
}
GROUP BY ?compo

Parmi les propriétés des composantes, nous trouvons également une série de références vers d’autres bases de données, dont OpenStreetMap et les monuments historiques (base « Mérimée »). Nos allons donc utiliser ces références pour faire une jointure avec ces autres bases pour collecter plus d’informations.

Identifiant externes

Identifiants externes vers d’autres sources.

Les résultats de ces requêtes peuvent être incorrects ou incomplets, mais comme pour Wikipédia ou OpenStreetMap, c’est une œuvre collaborative libre et ouverte. Il est donc possible de corriger et d’ajouter des propriétés aux objets de Wikidata.

En pratique pour exécuter et récupérer les résultats de requêtes SPARQL,nous écrivons la requête dans un fichier texte. Nous l’envoyons ensuite à l’API de Wikidata, puis récupérons le résultat en CSV simplement avec une commande « curl ».

curl \
    --data-urlencode "query@wikidata.sparql" \
    --header "Accept: text/csv" \
    https://query.wikidata.org/sparql > wikidata.csv

Les données de Wikidata sont sous licence CC0.

Enrichir les données avec la base Mérimée des monuments historiques

La base « Mérimée » est le recensement et la description des monuments historiques classés en France. Nous pouvons la consulter en ligne. Cette base de données est librement accessible en Opendata. IL est possible de la télécharger au format CSV. Chaque monument possède un identifiant, le même que celui que nous avons pu obtenir depuis Wikidata. Le besoin se résume donc à faire une jointure sur un identifiant entre deux CSV. On va alors simplement utiliser les outils en ligne de commande de la suite csvkit. « csvcut » pour ne garder que les colonnes qui nous intéressent, puis « csvjoin » pour faire la jointure avec les données déjà extraites de Wikidata.

On télécharge la base Mérimée (36 Mo).

wget "https://data.culture.gouv.fr/explore/dataset/liste-des-immeubles-proteges-au-titre-des-monuments-historiques/download/" -O 20_merinee.csv

Nous filtrons pour ne conserver que certaines colonnes.

csvcut -d ';' \
    -c Référence,Statut,Historique \
    20_merinee.csv > 20_merinee-columns.csv

Nous réalisons la jointure de la colonne refs_merimee de Wikidata avec la colonne Référence de Mérimée. --left est pour « left join », on garde la ligne issue de Wikidata même s’il n’y a pas de correspondance dans Mérimée.

csvjoin -d ',' \
    --left --columns refs_merimee,Référence \
    15_wikidata.csv 20_merinee-columns.csv > 20_wikidata+merimee.csv

La base de données Mérimée est sous licence Ouverte (LO).

Enrichir les données avec OpenStreetMap

Depuis Wikidata nous avons pu également extraire des coordonnées géographiques. Or, pour les composantes de type chemin nous avons également un identifiant OpenStreetMap. Les coordonnées Wikidata représentent un point. Cela est acceptable pour un bâtiment, mais ne l'est pas pour un tronçon de chemin. Nous pouvons récupérer la géométrie du linéaire des chemins sur OpenStreetMap.

Nous ne l’utilisons pas ici, mais il est à noter que l’approche inverse aurait également été possible. OpenStreetMap contient des références vers les objets Wikidata.

L’API d’OpenStreetMap permet de récupérer des objets « composites » nommés relations depuis leur identifiant. Cependant, les résultats sont dans le format XML propre à OpenStreetMap. Il faut les convertir dans un format SIG plus classique. Nous avons choisi le WKT pour pouvoir l’embarquer dans un CSV. Nous utilisons pour cela l’utilitaire ogr2ogr dédié à la conversion de formats de données géographiques.

OSM relation 10266352

Exemple du tronçon 10266352 « Via Podiensis (12b) : Lectoure > Condom ».

Téléchargement d’un chemin depuis OpenStreetMap au format XML.

curl https://osm.org/api/0.6/relation/10266352/full > /tmp/path.osm

Fusion des segments en seul linéaire et conversion en WTK à embarquer dans le CSV.

ogr2ogr \
    -lco GEOMETRY=AS_WKT \
    -dialect sqlite -sql " \
         SELECT LineMerge(Collect(geometry)) AS wkt, 10266352 AS osm_rel_id \
         FROM lines" \
    /tmp/path.csv /tmp/path.osm

Les données d’OpenStreetMap et donc les géométries collectées sont sous licence ODbL.

Données de contexte

En plus des composantes elles-mêmes, nous souhaitons avoir des informations de contexte : c’est-à-dire des points d’intérêt (POI) à proximités pour les visiteurs et randonneurs du chemin de Compostelle. Ces derniers peuvent être des commerces, comme des restaurants, des supermarchés, des pharmacies mais aussi d’autres points d’intérêt touristiques.

Nous allons pouvoir trouver les établissements commerciaux comme les supermarchés ou les pharmacies dans la base SIREN. Les autres POI vont pouvoir être extraits de la base DATAtourisme.

POI de SIREN

La base SIREN du répertoire des entreprises produite par l’INSEE est disponible en OpenData sous Licence Ouverte (LO). Nous allons utiliser la version géocodée « geosiren » où chaque établissement commercial est en plus associé à une localisation géographique. Le téléchargement de la base de données de la France entière (1.3 Go compressé) est fait par un simple wget.

wget http://data.cquest.org/geo_sirene/last/etablissements_actifs.csv.gz

À chaque établissement sont associés des attributs comme un nom, un nombre de salariés, un statut... ainsi qu’un type d’activité principale. C’est le code APE. Par exemple, le code « 4773Z » correspond à « Commerce de détail de produits pharmaceutiques en magasin spécialisé », c’est-à-dire aux pharmacies. Nous faisons donc la liste des codes APE des activités qui nous intéressent pour filtrer le CSV. On pourrait utiliser « csvgrep », mais vu la taille du fichier un premier filtre avec « grep » fera aussi bien l’affaire.

zgrep -E "4711|4773Z" etablissements_actifs.csv.gz > 40_etablissements_filtre.csv

Contrairement aux jointures précédentes, une simple jointure par identifiant ne suffit pas. Nous devons donc retrouver les établissements à proximité.

Nous pouvons faire cette jointure spatiale en utilisant « ogr2ogr » qui permet d’écrire une requête de type SQL. Ici, les sources de données sont des CSV que nous assemblons dans un unique fichier SQLite. Au passage, nous changeons les coordonnées de latitude/longitude (exprimées en degrés) pour les passer en projection de référence en France X/Y dont l’unité est le mètre, permettant ainsi de faire un calcul de distance cohérent en mètres.

ogr2ogr \
    -s_srs epsg:4326 \
     -oo X_POSSIBLE_NAMES=longitude -oo Y_POSSIBLE_NAMES=latitude \
    -t_srs epsg:2154 -f sqlite -dsco SPATIALITE=YES -nln siren \
    40_etablissements_filtre.sqlite 40_etablissements_filtre.csv

ogr2ogr -append \
    -s_srs epsg:4326 -oo GEOM_POSSIBLE_NAMES=coor_ \
    -t_srs epsg:2154 -nln data \
    40_etablissements_filtre.sqlite 20_wikidata+merimee.csv

Il ne reste plus qu’à extraire les établissements à moins de 500m des composantes classées (commande simplifié).

ogr2ogr \
    -sql " \
        SELECT * \
        FROM data \
            JOIN siren ON \
                PtDistWithin(data.geometry, siren.geometry, 500)" \
    40_data.csv 40_etablissements_filtre.sqlite

POI de DATAtourisme

DATAtourisme est l’unification des données touristiques provenant des agences régionales et départementales du tourisme. Ces données hétérogènes sont rassemblées dans une base de données sémantique au même formalisme que Wikidata. Nous allons donc à nouveau pouvoir l’interroger en SPARSQL. Ces données sont également sous Licence Ouverte (LO).

Le contenu de cette base est consultable et interrogeable via une interface web. L’exécution des requêtes doit être planifiée et le résultat est obtenu plus tard par API (processus qui a été fait pour un cas d’utilisation qui correspond mal au nôtre).

Exemple d’une fiche d’un établissement (celle-ci peut également être téléchargée dans d’autres formats) : d70eecd7-4d40-3f83-8372-30c638e9d03d. Chaque propriété est affichée ou fait référence à un autre objet vers lequel nous pouvons naviguer, par exemple « est localisé à »: d281e154-2e64-328f-bfd5-818698b5231f.

Fiche DATAtourisme

Dans notre cas, nous ne voulons garder que certains types d’offres touristiques. De plus, nous voudrions qu’elles soient obligatoirement localisées par des coordonnées géographiques.

Prefix datatourisme: 
Prefix rdf: 
Prefix rdfs: 
Prefix schema: 

SELECT
  ?res ?type ?label
  ?latitude ?longitude
  ?hasCarPark
  ?reducedMobilityAccess
WHERE {
  ?res rdf:type ?type;
       rdfs:label ?label;
       datatourisme:isLocatedAt ?location.

  FILTER (?type IN (
    datatourisme:ArcheologicalSite,
    datatourisme:BoutiqueOrLocalShop,
    datatourisme:FastFoodRestaurant,
    datatourisme:HotelTrade,
    datatourisme:LocalTouristOffice
  )).

  ?location schema:geo ?geo.
  ?geo schema:latitude ?latitude;
       schema:longitude ?longitude.

  OPTIONAL {
    ?res datatourisme:reducedMobilityAccess ?reducedMobilityAccess.
  }

  BIND (exists{
    ?res datatourisme:isEquippedWith datatourisme:CarPark
  } AS ?hasCarPark).
}
res type label latitude longitude has Car Park reduced Mobility Access
70574f27-5213-30f6- a616-08103615ba70 FastFoodRestaurant Beloute Café 42.89402 -0.556057 false false
9aaca0e2-f81a-3b67- a766-f2acab10c037 HotelTrade Le Chateau d'Arance 42.974641 -0.600645 false true
a1795102-c94b-3dd9- a83f-ac4e4d1920a4 FastFoodRestaurant Snack Bar Le Fario 42.8795069 -0.3962667 false true
d70eecd7-4d40-3f83- 8372-30c638e9d03d HotelTrade Hôtel des Voyageurs 42.8726932 -0.554552 false true
efb75ac7-6102-3fcd- 8237-347e190f51d6 FastFoodRestaurant Restaurant Le Panoramic 42.8975857 -0.3960066 false false
95d05007-9c9a-31d8- b240-9f1bc016a68a HotelTrade Hôtel du Pourtalet 42.80684 -0.418914 false true

Nous appliquons ensuite la même stratégie de conversion en SQLite pour faire une jointure spatiale basée sur la distance avec les différentes composantes, comme nous l’avons déjà fait pour la base SIREN.

Conclusion

Nous montrons ici comment récupérer et croiser des données issues de formats divers. Nous faisons à la fois des requêtes SPARQL qui extraient et filtrent des données sur des serveurs distants, mais qui permettent également des croisements de données en local. L’usage de SPARQL permet de ne récupérer que les donnés souhaitées et évite l’utilisation de traitement et logiciel complexes en local. En conséquence les outils en ligne de commande utilisés ici restent accessibles.

Une fois les données obtenues, il ne reste plus qu’à les afficher.

Autres articles

Formations en lien

ABONNEZ-VOUS À LA NEWSLETTER !