Mehr

Wie verwende ich interagierende PostgreSQL/PostGIS-Trigger in QGIS, um mehrere Beschriftungen auf Polygonen zu erstellen?

Wie verwende ich interagierende PostgreSQL/PostGIS-Trigger in QGIS, um mehrere Beschriftungen auf Polygonen zu erstellen?


BEARBEITEN:

Die geposteten Triggerfunktionen sind gut gemacht, aber es gibt immer noch ein paar Probleme. Ich habe zwei Bildschirmaufnahmen hochgeladen:

http://workupload.com/file/ygRnYtp9

http://workupload.com/file/OBY7i9Fh


Ich verwende Punktgeometrien (Tabelle 'label_point'), um mehrere Beschriftungen auf Polygongeometrien (Tabelle 'Boden') zu platzieren. Nachdem ich Layer-Bearbeitungen vorgenommen habe, muss ich die betroffenen label_point-Features aktualisieren. Deshalb habe ich eine Triggerfunktion erstellt.

CREATE TABLE label_point ( gid serial NOT NULL, geom geometrie(point, SRID), label_sample varchar(255), CONSTRAINT label_point_pkey PRIMARY KEY (gid) ); CREATE TABLE boden ( gid serial NOT NULL, geom geometrie(polygon, SRID), label varchar(255), CONSTRAINT boden_pkey PRIMARY KEY (gid) ); FUNKTION ERSTELLEN ODER ERSETZEN sample_label() RETURNS trigger AS $body$ BEGIN IF GeometryType(NEW.geom) = 'POINT' THEN EXECUTE 'SELECT Label FROM Boden WHERE ST_Within($1, Boden.geom) LIMIT 1' USING NEW.geom INTO NEW .label_sample; RÜCKGABE NEU; ELSEIF GeometryType(NEW.geom) = 'POLYGON' THEN EXECUTE 'UPDATE label_point SET label_sample = NULL WHERE ST_Within(label_point.geom, $1)' USING NEW.geom; RÜCKGABE NEU; ENDE WENN; ENDE; $body$ SPRACHE plpgsql; CREATE TRIGGER tg_sample_label VOR EINFÜGEN ODER UPDATE ON label_point FÜR JEDE ZEILE AUSFÜHRUNGSVERFAHREN sample_label(); TRIGGER ERSTELLEN tg_sample_label NACH EINFÜGEN ODER AKTUALISIEREN AUF Erde FÜR JEDE ZEILE AUSFÜHRUNGSVERFAHREN sample_label();

Leider leidet diese Lösung unter zwei Problemen.

1.) Beim Löschen eines Boden-Features oder Verschieben eines Boden-Features (ST_Within(label_point.geom, ground.geom) = FALSE) werden die sample_point-Features nicht auf NULL aktualisiert.

2.) Wenn ein Boden-Feature mit dem QGIS 'Split Feature Tool' geteilt und die Beschriftung eines Polygonteils geändert wird, werden die label_point-Features nach dem Speichern der Bearbeitungen nicht korrekt aktualisiert.

Kann mir jemand dabei helfen?


Sie könnten einen Trigger vor dem Löschen schreiben und Ihren Insert- und Update-Trigger wie in meinem folgenden Beispiel ändern. Der Workflow funktioniert soweit, aber der Code könnte noch "bereinigt" und optimiert werden, um Rekursionen zwischen den verschiedenen Triggern zu verhindern... also poste ich meinen Code als "in Arbeit" ;)

https://gist.github.com/neogis-de/a1d08c38d8b9c5d316c7

CREATE TABLE label_point ( gid serial NOT NULL, geom geometrie(point, 3857), label_sample varchar(255), CONSTRAINT label_point_pkey PRIMARY KEY (gid) ); CREATE TABLE boden ( gid serial NOT NULL, geom geometrie(polygon, 3857), label varchar(255), CONSTRAINT boden_pkey PRIMARY KEY (gid) ); ------------------------------------------ -- Trigger für Punkt Layer ERSTELLEN ODER ERSETZEN SIE FUNKTION sample_label_point() RETURNS TRIGGER AS $BODY$ DECLARE BEGIN Hinweis 'Punkt Trigger startet jetzt: %', now(); IF TG_OP = 'INSERT' THEN IF (SELECT COUNT(*) FROM (SELECT t.gid FROM label_point AS t, Boden AS s WHERE st_Within(NEW.geom, s.geom)) AS foo) > 0 THEN SELECT boden.label INTO NEW.label_sample FROM Boden WHERE ST_Intersects(NEW.geom, ground.geom); Hinweis 'Punkt-Trigger endet jetzt: %', now(); RÜCKGABE NEU; ELSE RAISE Hinweis 'keine Kreuzung'; RAISE-Hinweis 'Punkttrigger endet jetzt: %', now(); RÜCKGABE NEU; ENDE WENN; ELSIF TG_OP = 'UPDATE' THEN IF (ST_Equals(NEW.geom , OLD.geom)=FALSE) THEN IF (SELECT COUNT(*) FROM (SELECT t.gid FROM label_point AS t, Boden AS s WHERE st_Within(NEW.geom , s.geom) AND (t.gid <> OLD.gid)) AS foo) > 0 THEN SELECT boden.label INTO NEW.label_sample FROM boden WHERE ST_Intersects(NEW.geom, boden.geom); RAISE Hinweis 'Schnittpunkt gefunden!'; RÜCKGABE NEU; ELSE SELECT NULL INTO NEW.label_sample; RÜCKGABE NEU; Hinweis 'Punkt-Trigger endet jetzt: %', now(); ENDE WENN; ELSE Raise Notice 'Aktualisierung von Attributdaten'; Hinweis 'Punkt-Trigger endet jetzt: %', now(); Zurück NEU; ENDE WENN; ENDE WENN; ENDE; $BODY$ SPRACHE plpgsql; CREATE TRIGGER label_point_trigger BEVOR INSERT ODER UPDATE ON label_point FÜR JEDE ZEILE AUSFÜHRUNGSVERFAHREN sample_label_point(); ------------------------------------------ -- Auslöser für Bodenschicht ERSTELLEN ODER ERSETZEN FUNKTION boden_label() RÜCKGABE TRIGGER ALS $BODY$ DECLARE new_label text := quote_ident(NEW.label); -- bei Deklaration zuweisen BEGIN IF TG_OP = 'INSERT' THEN Hinweis 'Boden-Insert-Trigger startet jetzt: %', now(); IF (SELECT COUNT(*) FROM (SELECT t.gid FROM label_point AS t, Boden AS s WHERE st_Within(t.geom, NEW.geom)) AS foo) > 0 THEN EXECUTE 'UPDATE label_point SET label_sample = $2 WHERE ST_Within( label_point.geom, $1)' USING NEW.geom, NEW.label; -- Hinweis 'Boden-Trigger endet jetzt: %', now(); RÜCKGABE NEU; ELSE RAISE Beachten Sie 'keine Kreuzung'; RÜCKGABE NEU; ENDE WENN; ELSIF TG_OP = 'UPDATE' THEN melde die Meldung 'Bodenaktualisierungstrigger startet jetzt: %', now(); IF (SELECT COUNT(*) FROM (SELECT t.gid FROM label_point AS t, Boden AS s WHERE st_Within(t.geom, NEW.geom) --AND (t.gid <> OLD.gid) ) AS foo) > 0 THEN EXECUTE 'UPDATE label_point SET label_sample = ' || quote_literal(NEW.label) || ' WHERE ST_Within(label_point.geom, $1)' USING NEW.geom; Hinweis 'UPDATE label_point SET label_sample = % WHERE ST_Within(label_point.geom, %)', new_label, NEW.geom; heben notice'Label gefunden: %', NEW.label; RAISE Hinweis 'Schnittpunkt gefunden!'; RÜCKGABE NEU; ELSE EXECUTE 'UPDATE label_point SET label_sample = NULL WHERE ST_Within(label_point.geom, $2)' USING NEW.geom, OLD.geom; RAISE NOTICE 'kein (mehr) Schnittpunkt des Features mit gid=%', NEW.gid; RÜCKGABE NEU; ENDE WENN; ENDE WENN; RAISE NOTICE 'Boden-Trigger endet jetzt: %', now(); ENDE; $BODY$ SPRACHE plpgsql; TRIGGER ERSTELLEN label_soil_trigger BEVOR IN Erde FÜR JEDE ZEILE EINFÜGEN ODER AKTUALISIEREN. -------------------------------------------------- ---- -- Trigger löschen CREATE OR REPLACE FUNCTION public.before_delete_soil() RETURNS trigger AS $BODY$ BEGIN RAISE NOTICE 'Trigger % von Tabelle % ist aktiv % % für Datensatz %', TG_NAME, TG_RELNAME, TG_WHEN, TG_OP, OLD .Etikette; RAISE NOTICE 'Label % wurde für Point mit gid=% gelöscht', OLD.label, OLD.gid; UPDATE label_point SET label_sample = NULL WHERE ST_Within(label_point.geom, OLD.geom); RÜCKKEHR ALT; ENDE; $BODY$ SPRACHE plpgsql FLÜCHTIGE KOSTEN 100; ALTER FUNCTION public.before_delete_soil() OWNER TO postgres; TRIGGER ERSTELLEN trigger_before_delete_soil BEFORE DELETE ON public.soil FÜR JEDE ZEILE AUSFÜHRUNGSVERFAHREN public.before_delete_soil();

EDIT1: Aktualisierter/gereinigter Code:

Ich habe den Code bereinigt und einen Check eingebaut, um eine rekursive Ausführung des Triggers zu verhindern. Jetzt geht es ziemlich schnell:

START; -------------------------------------------------- ----- -- CREATE Testtabellen CREATE TABLE label_point ( gid serial NOT NULL, geom geometrie(point, 3857), label_sample varchar(255), CONSTRAINT label_point_pkey PRIMARY KEY (gid) ); CREATE TABLE boden ( gid serial NOT NULL, geom geometrie(polygon, 3857), label varchar(255), CONSTRAINT boden_pkey PRIMARY KEY (gid) ); -------------------------------------------------- ----- -- Triggerfunktion für label_point-Layer CREATE OR REPLACE FUNCTION sample_label_point() RETURNS TRIGGER AS $BODY$ DECLARE BEGIN IF TG_OP = 'INSERT' THEN IF (SELECT COUNT(*) FROM (SELECT t.gid FROM label_point AS t, Boden AS s WHERE st_Within(NEW.geom, s.geom)) AS foo) > 0 THEN SELECT Boden.label INTO NEW.label_sample FROM Boden WHERE ST_Intersects(NEW.geom, Boden.geom); RÜCKGABE NEU; SONST RÜCKGABE NEU; ENDE WENN; ELSIF TG_OP = 'UPDATE' THEN IF (ST_Equals(NEW.geom , OLD.geom)=FALSE) THEN IF (SELECT COUNT(*) FROM (SELECT t.gid FROM label_point AS t, Boden AS s WHERE st_Within(NEW.geom , s.geom) ) AS foo) > 0 DANN WÄHLE boden.label INTO NEW.label_sample FROM boden WHERE ST_Intersects(NEW.geom, boden.geom); RÜCKGABE NEU; ELSE SELECT NULL INTO NEW.label_sample; RÜCKGABE NEU; ENDE WENN; ELSE Rückgabe NEU; ENDE WENN; ENDE WENN; ENDE; $BODY$ SPRACHE plpgsql; -------------------------------------------------- ----- -- CREATE TRIGGER für label_point-Layer CREATE TRIGGER label_point_trigger_insert BEFORE INSERT ON public.label_point FÜR JEDE ZEILE AUSFÜHRUNGSVERFAHREN public.sample_label_point(); TRIGGER ERSTELLEN label_point_trigger_update VOR AKTUALISIERUNG VON geom AUF public.label_point FÜR JEDE ZEILE WENN (OLD.geom UNTERSCHEIDET VON NEW.geom) AUSFÜHRUNGSVERFAHREN public.sample_label_point(); -------------------------------------------------- ----- -- Triggerfunktion für den Layer ground_label CREATE OR REPLACE FUNCTION ground_label() RETURNS TRIGGER AS $BODY$ DECLARE new_label text := quote_ident(NEW.label); -- Zuweisen bei Deklaration BEGIN IF TG_OP = 'INSERT' THEN IF (SELECT COUNT(*) FROM (SELECT t.gid FROM label_point AS t, Boden AS s WHERE st_Within(t.geom, NEW.geom)) AS foo) > 0 THEN EXECUTE 'UPDATE label_point SET label_sample = $2 WHERE ST_Within(label_point.geom, $1)' USING NEW.geom, NEW.label; RÜCKGABE NEU; SONST RÜCKGABE NEU; ENDE WENN; ELSIF TG_OP = 'UPDATE' THEN IF (SELECT COUNT(*) FROM (SELECT t.gid FROM label_point AS t, Boden AS s WHERE st_Within(t.geom, NEW.geom) ) AS foo) > 0 THEN EXECUTE 'UPDATE label_point SET label_sample = ' || quote_literal(NEW.label) || ' WHERE ST_Within(label_point.geom, $1)' USING NEW.geom; RÜCKGABE NEU; ELSE EXECUTE 'UPDATE label_point SET label_sample = NULL WHERE ST_Within(label_point.geom, $2)' USING NEW.geom, OLD.geom; RÜCKGABE NEU; ENDE WENN; ENDE WENN; ENDE; $BODY$ SPRACHE plpgsql; -------------------------------------------------- ----- -- CREATE TRIGGER für den Bodenlayer CREATE TRIGGER label_soil_trigger_insert BEFORE INSERT ON public.soil FÜR JEDE ZEILE AUSFÜHRUNGSVERFAHREN public.soil_label(); TRIGGER ERSTELLEN label_soil_trigger_update VOR DEM AKTUALISIEREN VON geom AUF public.soil FÜR JEDE ZEILE WENN (OLD.geom UNTERSCHEIDET VON NEW.geom) VERFAHREN AUSFÜHREN public.soil_label(); -------------------------------------------------- ---- -- Trigger löschen FUNKTION ERSTELLEN ODER ERSETZEN public.before_delete_soil() RETURNS trigger AS $BODY$ BEGIN UPDATE label_point SET label_sample = NULL WHERE ST_Within(label_point.geom, OLD.geom); RÜCKKEHR ALT; ENDE; $BODY$ SPRACHE plpgsql FLÜCHTIGE KOSTEN 100; TRIGGER ERSTELLEN trigger_before_delete_soil BEFORE DELETE ON public.soil FÜR JEDE ZEILE AUSFÜHRUNGSVERFAHREN public.before_delete_soil(); VERPFLICHTEN;

https://gist.github.com/neogis-de/27bcf7ee4f36a93fd62e


Eine andere Möglichkeit, dies zu handhaben, besteht darin, eine Ansicht für die Beschriftungen zu verwenden. Je nachdem, wie groß Ihr Dataset ist, wird es natürlich langsamer, wenn Ihre Daten sehr groß werden (10.000+ Funktionen in jeder Tabelle).

Hier ist, was Sie haben könnten:

  1. label_point-Tabelle (bearbeitbar)
  2. Bodentabelle (bearbeitbar)
  3. v_label_soil-Ansicht (könnte bearbeitet werden?)

Die Ansicht kann etwa so aussehen:

select att1, att2, s.att1 l.geom from label_point l Join Böden s on ST_Within(l.geom, s.geom)