[QGIS-DE] Automatische Aktualisierung und Abfragung von Fremdattributen (im Feldrechner)

bmarcus bmarcus at giswana.de
Sa Jan 30 09:19:23 PST 2021


Hallo J1999,

obwohl QGIS mit dem Feldrechner ein mächtiges Werkzeug bereithält, darf für
deine Anliegen bezweifelt werden, dass er das richtige Mittel der Wahl ist.
M.E. ist etwas Programmierarbeit von Nöten.

Da sich das Datenbank basierte Geopackage unter QGIS als Standartformat
etabliert hat, möchte ich in der inzwischen stark pythonisierten GIS-Welt
eine Lanze brechen und stelle zur Lösung geschilderter Probleme einen SQL
Ansatz vor. Mit SQL lassen sich sehr einfach Beziehungen von Datenschichten
abbilden und Funktionsabläufe realisieren, die mit den hauseigenen
Bordmitteln von QGIS nur umständlich bis gar nicht zu realisieren sind.
Darüber hinaus besteht die Möglichkeit, ein und die selben Datenebene zu
manipulieren, ohne immer wieder neue Layer (und den damit einhergehenden
Datenmüll) zu produzieren. Ein weiterer Vorteil von SQL in der
Geodatenmanipulation und -analyse liegt in der Wiederverwendbarkeit von
SQL-Skripten. Sind diese verständlich kommentiert und in chronologischer
Reihenfolge geordnet, bilden sie das Gerüst zur Dokumentation von
GIS-Projekten.

Die nachfolgenden Code-Blöcke sind so kommentiert, dass jede Anweisung,
aufgerufene Funktion etc. auch von einem SQL-Laien nachvollzogen werden
kann. Kommentare werden mit doppeltem Bindestrich eingeleitet.

Starten wir mit den Knotenpunkten. Für das Einfügen sich nicht überlagernder
Start- und Endpunkte eines Linienlayers in einen Punktlayer bietet sich
folgende INSERT Anweisung an:

```sql
-- füge neue Objekte in den Punktlayer ein (nur für das Feld "geom")
insert into points (geom)
-- und zwar aus dem Resultat folgender zusammengefasster Abfragen:
--- 1. wähle alle Startpunkte des "lines" Layers
select st_startpoint(geom) from lines
--- vereinige diese mit Objekten, die nicht schon vorhanden sind
union
--- 2. wähle alle Startpunkte des "lines" Layers
select st_endpoint(geom) from lines
;
```
Das war's auch schon. Mehr Kommentar als Code. Als Ergebnis liegen nun
eindeutige Knotenpunkte aus dem Linienlayer im Layer "points" vor. 

Werden nun einzelne Streckenabschnitte weiter aufgeteilt, muss die INSERT
Anweisung erneut aufgerufen werden. Dabei kommt es dann allerdings auch zu
dem beschriebenen Phänomen sich überlagernder Punkte, da alle zuvor als
eindeutig erkannte Knotenpunkte erneut eingefügt werden.

Auf die Bereinigung doppelter Datensätze soll hier nicht näher eingegangen
werden, sondern wir wählen von vorn herein einen geschicktereren Ansatz.
Hierzu erstellt man zu Anfang eine fiktive Tabelle, welche die Gesamtheit
aller Objekte aus dem Punktlayer aufnimmt (cte1). Über einen räumlichen
Vergleich zwischen dieser Kollektion und den Start- bzw. Endpunkten der
Linien werden die Knotenpunkte ermittelt, die noch nicht im Punktlayer
vorhanden sind (cte2, cte3) und dann in diesen festgeschrieben. Hierbei
spielt es keine Rolle, ob der Punktlayer leer ist oder schon Punktgeometrien
aufweist. (Der SQL-Code ist zwar etwas fortgeschrittener, aber immer noch
leicht verständlich.)

```sql
-- '01_ins_nodes_intscts'
-- Prozessebene Arbeitsspeicher:
with
--- erzeuge drei Tabellen, die aus Abfrageergebnissen Geometrien
zurückgeben:
---- zusammengefasste Kollektion des Punktlayers
cte1 (g_un) as (
    ----- wähle alle Geometrien und fasse sie als ein MULTIPOINT Objekt
zusammen 
	select st_collect(geom) 
	----- vom Punktlayer
	from points
),
---- Startpunkte des Linienlayers
cte2 (node_start) as (
    ----- wähle alle Startpunkte 
	select st_startpoint(geom)
	----- vom Linienlayer, dem der Alias "a", und "cte1", dem der Alias "b"
zugewiesen wird
	from lines as a, cte1 as b     -- in der Abfrage wird "lines" nun über "a" 
                                   -- und "cte1" (zusammengefasste Punkte)
über "b" angesprochen
	--- wo der räumliche Operator st_intersects (schneiden) 
	--- beim Vergleich von zusammengefasster Punktgeometrie und Startpunkten
des Linienlayers nicht WAHR (<> 1, impliziert 0 und NULL) zurück gibt
	---- respektive: wo sich Punktkollektion und Linien-Startpunkte nicht
schneiden
	where st_intersects(b.g_un, st_startpoint(a.geom)) <> 1
)
,
---- Endpunkte des Linienlayers
----- analog zu 'cte2'
cte3 (node_end) as (
	select st_endpoint(geom)
	from lines as a, cte1 as b 
	where st_intersects(b.g_un, st_endpoint(a.geom)) <> 1
)
-- Prozessebene Datenbank (Obacht!!! Einfügen wird ohne Rückfrage
ausgeführt, egal ob Editiermodus ein- oder ausgeschaltet ist!):
--- Einfügen in den Punktlayer (nur für das Feld "geom")
insert into points (geom)
select node_start from cte2
union
select node_end from cte3
;
```

Das Übertragen der Knotenkennungen in den Linienlayer erweist sich in der
SQL-Welt glücklicher Weise als sehr überschaubare und einfache
Angelegenheit. Über eine verschachtelten UPDATE Anweisung werden die
Geometrien des Punktlayers mit den Start- und Endpunkten des Linienlayers
räumlich auf identische Geometrien verglichen und bei Übereinstimmung die
Punktkennung in den Linienlayer festgeschrieben. Als Punktkennung dient der
Primärschlüssel des Punktlayers.

```sql
-- '02_upd_start_end_ids'
-- Prozessebene Datenbank (Obacht!!! Änderung wird ohne Rückfrage
ausgeführt, egal ob Editiermodus ein- oder ausgeschaltet ist!):
--- "lines" Layer aktualisieren
update lines
--- ändere Attribut "start_id" auf Werte der Abfrage 
set start_id = (
    --- wähle Objektkennungen ("fid") von "a"
	select a.fid 
	--- von den Layern "points", dem der Alias "a", und "lines", dem der Alias
"b" zugewiesen wird
	from points as a, lines as b   -- in der Abfrage wird "points" nun über
"a", "lines" über "b" angesprochen
	--- wo der räumliche Operator st_equals (gleich) beim Vergleich von
Punktgeometrie und Startpunkt des Linien Layers WAHR (1) zurück gibt
	---- respektive: wo die Punktgeometrien und die Linien-Startpunkte gleich
sind 
	where st_equals(a.geom, st_startpoint(b.geom)) == 1
        ---- und die Linienkennung der SELECT Anweisung gleich der
Linienkennung des zu aktualisierenden Layers ist
        and b.fid == lines.fid      -- dies ist eine Eigenart von SQLite 
                                    --- bei verschachteltem UPADTE mit
SELECT muss(!) die Objektkennung für eine 1:1 Zuweisung 
                                    --- an den zu aktualisierenden Layer
zurückgegeben werden
                                    --- ansonsten wird nur das erstgefundene
Ergebnis an alle Objekte weitergereicht 
), 
--- ändere Attribut "end_id" auf Werte der Abfrage 
---- analog zu 'set start_id'
end_id = (
	select a.fid 
	from points as a, lines as b
	where st_equals(a.geom, st_endpoint(b.geom)) == 1
	and b.fid == lines.fid
)
;
```

Live und in Farbe können alle Interessierten die Extraktion von
Knotenpunkten und Übertragung von Information zwischen zwei Datenschichten
mittels SQL ausprobieren. Ein QGIS-Projekt mit entsprechenden Daten und
SQL-Skripten ist unter folgendem Link zu finden:
https://www.giswana.de/g/ftp/qgis/extract_unique_nodes.zip
<https://www.giswana.de/g/ftp/qgis/extract_unique_nodes.zip>  

Nach dem Entpacken steht das QGIS-Projekt "node_extract.qgz" im Unterordner
"extract_unique_nodes" bereit.
Bitte beachten, dass die Verbindung zum Geopackage noch erstellt werden
muss. Beide SQL-Anweisungen stehen abgespeichert unter dem SQL-Editor des
Datenbankmanagers zur Verfügung.

Ein kurzes Anschauungsvideo ist zu finden unter: 
https://www.giswana.de/g/media/video/extract_unique_nodes.webm
<https://www.giswana.de/g/media/video/extract_unique_nodes.webm>  




--
Sent from: http://osgeo-org.1560.x6.nabble.com/QGIS-de-f5194137.html


Mehr Informationen über die Mailingliste QGIS-DE