Dans cet article nous expliquons comment il est possible de mettre en place de la détection de changement
(ou change detection
)
d’objects OSM (nodes
, ways
ou relation
) à l’aide de l’outil Osmose.
Détection de changement de données OSM
Comme OpenStreetMap (OSM
) est une base de données qui évolue en permanence
(un utilisateur peut, par exemple, ajouter un bâtiment, changer le nom d’un commerce, supprimer une poubelle),
il peut être intéressant de vouloir suivre ces changements : si vous tenez un
commerce, vous pouvez vouloir être informé des changements des informations qui y sont liées.
Cette problématique est appelée détection de changement
(ou change detection
) et fait partie du sujet plus général de l'
assurance qualité
(ou quality assurance). Chez Champs-Libres
nous aimons beaucoup ce sujet !
Osmose
Osmose est un des nombreux outils qualité disponibles afin de signaler de possibles problèmes dans les données OSM.
Plus précisément, cet outil fait passer des analyseurs
(grosses requêtes sql) qui détectent de possibles
erreurs qui sont répertoriées, au final, sur une carte.
Dans les erreurs erreurs détectées nous pouvons citer la collision de bâtiments, les sens uniques en cul de sac, …
Toutes ces analyses se font sur une version des données OSM à un moment précis. Il n’y a donc pas de notion d’évolution des données. Nous allons voir comment adapter l’outil pour permettre de traiter cette question.
Avant de parler de l’évolution des données, nous allons aborder le point de la mise à jour des données.
Mise à jour des données via Osmosis et schéma pg_snapshot
Lorsque l’outil osmose
fait ses analyses, il le fait sur une copie de la base de données OSM.
Pour stocker les données en local, osmose
utilise une base de données PostgresSQL
avec le schéma pgsnapshot.
Ce schéma est une version modifiée et simplifiée du schéma principal de la base de données OSM
qui permet, entre autre, la génération de géométries,
le stockage des balises dans une seule colonne hstore
pour une utilisation et une indexation
plus faciles.
Le schéma pgsnapshot
contient entre autres 3 tables : nodes
, ways
et relations
qui stockeront ces objets
(voir définition du schéma).
Comme les données OSM
évoluent en permanence, la base doit être mise à jour régulièrement.
L’outil Osmosis est utilisé pour cette tâche.
Une fois la mise à jour des données fait par Osmosis
la base contient la dernière
version des objets OSM et si des objets ont été supprimés ils n’apparaissent plus.
Pour faire de la détection de changement, nous avons besoin d’enregistrer les modifications et les suppressions des objets OSM.
Voici comment améliorer le schéma de la base de données afin de traiter cette problématique.
Modification du schéma pg_snapshot pour enregistrer les changements
Pour simplifier la présentation, nous allons seulement présenter la manière de gérer
les nodes
, la manière de gérer les autres objets se déduit facilement de ce cas.
L’entièrement de la solution est téléchargeable via ce lien.
Deux nouvelles tables sont rajoutées à la base :
nodes_deleted
qui va récupérer l’ancienne version des noeuds qui ont été supprimésnodes_history
qui va récupérer les mises à jour (seule la dernière version est conservée).
Le code à ajouter :
DROP TABLE IF EXISTS nodes_deleted;
-- cette table récupère l'ancienne version des nodes deleted
-- Cette table est vidée à la fin de la mise à jour d'un changeset
-- par la fonction osmosisUpdate
CREATE TABLE nodes_deleted AS TABLE nodes WITH NO DATA;
DROP TABLE IF EXISTS nodes_history;
CREATE TABLE public.nodes_history
(
gid SERIAL PRIMARY KEY,
id bigint NOT NULL,
version integer NOT NULL,
user_id integer NOT NULL,
tstamp timestamp without time zone NOT NULL,
changeset_id bigint NOT NULL,
tags hstore,
visible boolean DEFAULT TRUE,
geom geometry(Point,4326)
);
DROP TABLE IF EXISTS nodes_history_temp;
-- durant le processus de mise à jour des ways, la première passe
-- met à jour les tags et les noeuds, et le second met à jour
-- la géométrie. Cette table contient les mises à jour et seule la dernière
-- version est conservée.
CREATE TABLE nodes_history_temp AS TABLE nodes WITH NO DATA;
ALTER TABLE nodes_history_temp ADD CONSTRAINT nodes_history_temp_unique_id UNIQUE(id);
Nous créons ensuite une fonction save_deleted_nodes
qui sera appelée juste avant la
suppression de noeuds lors de la procédure de mise à jour.
CREATE OR REPLACE FUNCTION save_deleted_nodes() RETURNS trigger AS
$$BEGIN
INSERT INTO nodes_deleted (id, version, user_id, tstamp, changeset_id, tags, geom)
VALUES (OLD.id, OLD.version, OLD.user_id, OLD.tstamp, OLD.changeset_id, OLD.tags, OLD.geom);
RETURN OLD;
END;$$ LANGUAGE plpgsql;
Un trigger
est défini pour déclencher la fonction save_deleted_nodes
quand des nodes
sont supprimés durant la mise à jour.
DROP TRIGGER IF EXISTS save_deleted_nodes_tg ON nodes;
CREATE TRIGGER save_deleted_nodes_tg
BEFORE DELETE ON nodes
FOR EACH ROW
EXECUTE PROCEDURE save_deleted_nodes();
Nous faisons pareil pour garder la version d’un nodes
avant sa mise à jour.
CREATE OR REPLACE FUNCTION save_update_nodes() RETURNS trigger AS
$$BEGIN
-- insert only the first updated version on same update. Subsequent updates will be alteration of geometry or other
-- stuff dones by osmosis. We want to keep only the first version, which was the previous valid one in the table
-- nodes.
INSERT INTO nodes_history_temp (id, version, user_id, tstamp, changeset_id, tags, geom)
VALUES (OLD.id, OLD.version, OLD.user_id, OLD.tstamp, OLD.changeset_id, OLD.tags, OLD.geom)
ON CONFLICT ON CONSTRAINT nodes_history_temp_unique_id
DO NOTHING
;
RETURN NEW;
END;$$ LANGUAGE plpgsql;
Un trigger est déclenché lors d’une mise à jour d’un node
.
DROP TRIGGER IF EXISTS save_update_nodes_tg ON nodes;
CREATE TRIGGER save_update_nodes_tg
BEFORE UPDATE ON nodes
FOR EACH ROW
EXECUTE PROCEDURE save_update_nodes();
La dernière étape revient à implémenter la fonction osmosisupdate
qui est
appelée par OSMOSIS
à la fin d’une étape de mise à jour.
Nous l’utilisons pour ne garder dans la table nodes_history
que la version du node
avant sa mise à jour.
-- cette fonction est exécutée par osmosis avant de terminer
-- l'import d'un changeset.
CREATE OR REPLACE FUNCTION public.osmosisupdate(
)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
BEGIN
/* (code for actions) */
/* (code for ways) */
- copy the record from nodes_history_temp to nodes_history
INSERT INTO nodes_history (id, version, user_id, tstamp, changeset_id, tags, visible, geom)
SELECT id, version, user_id, tstamp, changeset_id, tags, TRUE, geom FROM nodes_history_temp;
DELETE FROM nodes_history_temp;
/* (code for relations) */
END;
$BODY$;
Exploitation des données et conclusion
Une fois les modifications faites vous avez deux nouvelles tables : nodes_deleted
et nodes_history
qui stockent automatiquement une trace des mises à jour des données OSM à chaque mise à jour de la base via
Osmosis
.
Voici, par exemple, l’historique du node 3704377488
enregistré dans une db de test :
gid |id |version|tstamp |changeset_id|tags |visible|geom |
-----+----------+-------+-------+-----------------------+------------+-----------------------------------------------------------------------------------+-------+---------------------------------------------+
59|3704377488| 3|2020-10-03 19:35:21.000| 91912685|"railway"=>"crossing", "crossing"=>"traffic_signals", "crossing:barrier"=>"no" |true |POINT (2.2401226000000003 48.897935700000005)|
210|3704377488| 4|2022-03-29 20:34:02.000| 119087786|"railway"=>"tram_crossing", "crossing"=>"traffic_signals", "crossing:barrier"=>"no"|true |POINT (2.2401226 48.8979357) |
Nous venons de voir comment adapter une base OSM pour enregistrer les changements. Cette modification ouvre la voie à de nombreuses possibilités.
L’entièrement de la solution est téléchargeable via ce lien.
Le travail a été effectué par Marc Ducobu, Julien Fastré et Julien Minet et l’article de blog écrit par Marc Ducobu.