From 2a4e970859d6eabdc5eec95d5248c0464a56d323 Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 12 Jun 2026 13:37:15 +0200 Subject: [PATCH] README auf Demo-Charakter ausgerichtet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Das Repo ist als Beispiel für die Ansteuerung der Karten-API aus QGIS gedacht: README beginnt jetzt mit einem PyQGIS-Minimalbeispiel (Login -> GET -> PUT), benennt die drei Stolpersteine (EPSG:25833, gemischte Geometrietypen, PUT ersetzt alles) und führt das Plugin als Referenz-Implementierung ein. metadata.txt-Beschreibung entsprechend. Co-Authored-By: Claude Fable 5 --- README.md | 209 ++++++++++++++++++++-------------------- vln_karten/metadata.txt | 4 +- 2 files changed, 109 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index ebcfca9..fe07d63 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,69 @@ -# VLN Karten — QGIS-Plugin für die Karten-API des VLN Managers +# API_Karte_QGISDemo — Beispiel: Karten-API des VLN Managers aus QGIS ansteuern -QGIS-Plugin, das die Karten-Layer des [VLN Managers](https://www.vlnsachsen.de) -direkt in QGIS bearbeitbar macht. Es liest und schreibt dieselben -Datenbank-Daten (`KARTE_OBJEKT`), die auch das Web-GIS -(karte.flurneuordnung-sachsen.de) über die `/maps/*`-Endpunkte nutzt. +Dieses Repository zeigt **an einem lauffähigen QGIS-Plugin**, wie die +Karten-API des VLN Managers (`https://api.flurneuordnung-sachsen.de/v2`) +aus QGIS bzw. PyQGIS angesteuert wird: Anmeldung, Laden der +Verfahrens-Layer als editierbare QGIS-Layer und Zurückschreiben der +Änderungen. Es liest und schreibt dieselben Datenbank-Daten +(`KARTE_OBJEKT`), die auch das Web-GIS +(karte.flurneuordnung-sachsen.de) verwendet. -## Funktionen +Der Code ist bewusst als **Referenz-Implementierung** gehalten — wer +eine eigene Integration (Plugin, Skript, Processing-Werkzeug) bauen +will, findet hier alle Bausteine. -- **Einmaliger Login** mit dem VLN-Manager-Konto (E-Mail/Passwort). - Der zurückgelieferte API-Key wird in den QGIS-Einstellungen gespeichert - und bei jedem QGIS-Start automatisch wiederverwendet; erst bei - HTTP 401/403 erscheint der Anmeldedialog erneut. -- **Verfahrens-Auswahl in der Toolbar** — alle Teilnehmergemeinschaften - aus `GET /tgen` als „VKZ — Name“. Die Auswahl wird in der Projektdatei - gespeichert und beim Öffnen des Projekts wiederhergestellt. -- **Vier Layer je Verfahren laden:** +## Die Ansteuerung in Kürze - | Button | API-Layer | DB-Art | - |---|---|---| - | Verfahrensgebiet | `umringe` | `UMRING` | - | Plan 41 (Wege- und Gewässerplan) | `p41` | `P41` | - | Karte alter Stand | `kas` | `KAS` | - | Wertermittlung | `we` | `WE` | +So sieht der minimale Ablauf in PyQGIS aus (vollständige, robuste +Fassung in [vln_karten/api_client.py](vln_karten/api_client.py)): - Geladen wird **vollständig** über den Listen-Endpunkt mit - limit/offset-Paging — auch bei mehr als 2000 Objekten. -- **Gemischte Geometrietypen:** Die API liefert Punkte, Linien und - Flächen in einer FeatureCollection. Beim Laden wird nach - Geometrie-Familie in bis zu drei Memory-Layer gesplittet - (Single-Typen werden zu Multi-Typen befördert); bei nur einem Typ - entsteht ein einzelner Layer. -- **Hochladen:** „Aktiven Layer hochladen“ vereint alle Teil-Layer - desselben Datensatzes (Punkte + Linien + Flächen derselben VKZ) wieder - zu einer FeatureCollection und schreibt sie per `PUT` zurück. - Vorher: Commit offener Bearbeitungssitzungen und Sicherheitsabfrage - mit Auflistung der beteiligten Layer. -- **Schutz vor Duplikaten:** Erneutes Laden eines bereits geladenen - Datensatzes ersetzt die vorhandenen Layer nach Rückfrage, statt sie zu - stapeln. Liefert der Server 0 Objekte, wird das deutlich gemeldet und - ein leerer Layer zum Digitalisieren angelegt. +```python +import json +from qgis.PyQt.QtCore import QUrl +from qgis.PyQt.QtNetwork import QNetworkRequest +from qgis.core import QgsBlockingNetworkRequest -## Voraussetzungen +BASE = "https://api.flurneuordnung-sachsen.de/v2" -- QGIS **3.22 oder neuer**, einschließlich **QGIS 4** (Qt6/PyQt6 — - der Code verwendet durchgehend scoped Enums und `exec()`). -- Ein VLN-Manager-Konto mit Zugriff auf die Karten-API. +def request(method, path, payload=None, api_key=None): + req = QNetworkRequest(QUrl(BASE + path)) + req.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, "application/json") + if api_key: + req.setRawHeader(b"X-API-Key", api_key.encode()) + blk = QgsBlockingNetworkRequest() + body = json.dumps(payload).encode() if payload is not None else b"" + getattr(blk, method)(req, *([body] if method != "get" else [])) + return json.loads(bytes(blk.reply().content()) or b"null") -## Installation +# 1) Login: liefert das userauth-Token = API-Key +login = request("post", "/person/login", + {"mail": "...", "password": "..."}) +api_key = login["data"]["userauth"] -Repository klonen und den Plugin-Ordner in das QGIS-Profil verlinken -(alternativ kopieren), dann QGIS neu starten: +# 2) Layer lesen: GeoJSON FeatureCollection in EPSG:25833 +fc = request("get", "/maps/p41?vkz=22017&limit=2000&offset=0", + api_key=api_key) -```bash -git clone https://entwicklung.flurneuordnung-sachsen.de/VLN_SN/API_Karte_QGISDemo.git -cd API_Karte_QGISDemo - -# macOS, QGIS 4 (bei QGIS 3: "QGIS4" durch "QGIS3" ersetzen): -ln -s "$PWD/vln_karten" \ - ~/Library/Application\ Support/QGIS/QGIS4/profiles/default/python/plugins/vln_karten - -# Linux: -# ~/.local/share/QGIS/QGIS4/profiles/default/python/plugins/ -# Windows: -# %APPDATA%\QGIS\QGIS4\profiles\default\python\plugins\ +# 3) Layer schreiben: PUT ersetzt den kompletten Bestand dieser VKZ! +request("put", "/maps/p41/22017", payload=fc, api_key=api_key) ``` -Anschließend in QGIS unter *Erweiterungen → Erweiterungen verwalten* -„VLN Karten“ aktivieren (experimentelle Erweiterungen zulassen). +Die drei Stolpersteine, die das Beispiel-Plugin jeweils sauber löst: -## Bedienung - -1. **Anmelden …** in der Toolbar „VLN Karten“ (auch unter - *Web → VLN Karten*): E-Mail und Passwort des VLN-Manager-Kontos. - Das Passwort wird nicht gespeichert, nur der API-Key. -2. **Verfahren wählen** in der Auswahlliste. -3. **Layer laden**, in QGIS normal editieren (Memory-Layer, - EPSG:25833), **hochladen**. - -> ⚠️ Der `PUT` der API ersetzt den kompletten Layer-Bestand der -> jeweiligen VKZ (versionierter Snapshot in `KARTE_SPEICHERSTAND`). -> Deshalb lädt das Plugin immer alle Teil-Layer gemeinsam hoch — nur -> einen Teil zu senden, würde die übrigen Geometrietypen serverseitig -> löschen. +1. **CRS:** Die API liefert/erwartet **EPSG:25833** (ETRS89/UTM33), + nicht WGS84 wie in RFC 7946 üblich. GeoJSON-Export aus QGIS daher + mit `QgsJsonExporter.setTransformGeometries(False)`. +2. **Gemischte Geometrietypen:** Eine FeatureCollection kann Punkte, + Linien und Flächen zugleich enthalten — QGIS-Memory-Layer können + das nicht. Das Plugin splittet beim Laden nach Geometrie-Familie + und vereint beim Hochladen wieder ([vln_karten/layer_manager.py](vln_karten/layer_manager.py)). +3. **PUT ersetzt alles:** Der Schreibendpunkt legt einen neuen + Snapshot an und ersetzt den kompletten Layer-Bestand der VKZ. + Immer den vollständigen Datensatz senden — Teilmengen löschen den + Rest serverseitig. ## API-Vertrag -Basis-URL: `https://api.flurneuordnung-sachsen.de/v2` -(fest hinterlegt als `DEFAULT_BASE_URL` in -[vln_karten/api_client.py](vln_karten/api_client.py)) - ``` POST /person/login Body: {"mail": "...", "password": "..."} @@ -97,49 +72,79 @@ POST /person/login GET /tgen Verfahren/TGs (Auth nötig) -GET /maps/?vkz=&limit=&offset= Layer lesen (kas/we: Auth nötig) - Antwort: GeoJSON FeatureCollection in EPSG:25833 (ETRS89/UTM33) +GET /maps/?vkz=&limit=&offset= Layer lesen, mit Paging +GET /maps//{vkz} Layer lesen, ohne Paging + layer: umringe | p41 | st | kas | we + Auth: umringe/p41/st öffentlich, kas/we mit X-API-Key + Antwort: GeoJSON FeatureCollection in EPSG:25833 PUT /maps//{vkz} Layer schreiben (Auth nötig) Body: GeoJSON (ersetzt den Layer dieser VKZ, neuer Snapshot) Antwort: {"data": {"vkz","layer","art","speicher_id"}, "status":"ok"} Fehlerformat: RFC 7807 (application/problem+json) +Leerer vkz-Parameter (?vkz=) wird mit HTTP 400 abgelehnt. ``` -Ein weiterer Layer der API (`st` = Servicetermin) wäre ein zusätzlicher -Eintrag im `DATASETS`-Dict in [vln_karten/plugin.py](vln_karten/plugin.py). +## Das Beispiel-Plugin -## Aufbau +Das Plugin macht den kompletten Arbeitsablauf in der QGIS-Oberfläche +erlebbar: + +- **Einmaliger Login** (E-Mail/Passwort) — der API-Key wird in den + QGIS-Einstellungen gespeichert und über QGIS-Neustarts hinweg + wiederverwendet; bei 401/403 öffnet sich der Anmeldedialog erneut. +- **Verfahrens-Auswahl in der Toolbar** (aus `GET /tgen`), gemerkt je + QGIS-Projektdatei. +- **Buttons je Layer:** Verfahrensgebiet (`umringe`), Plan 41 (`p41`), + Karte alter Stand (`kas`), Wertermittlung (`we`) — vollständig + geladen über den Listen-Endpunkt mit Paging. Weitere Layer (z. B. + `st`) sind je ein Eintrag mehr im `DATASETS`-Dict in + [vln_karten/plugin.py](vln_karten/plugin.py). +- **„Aktiven Layer hochladen“** vereint alle Teil-Layer des Datensatzes + und schreibt per PUT zurück (mit Sicherheitsabfrage). +- Erneutes Laden **ersetzt** vorhandene Layer nach Rückfrage, statt sie + zu stapeln — sonst würde der nächste Upload alles doppelt senden. | Datei | Zweck | |---|---| | [vln_karten/plugin.py](vln_karten/plugin.py) | Toolbar, Aktionen, Verfahrens-Auswahl, `DATASETS`-Registry | -| [vln_karten/api_client.py](vln_karten/api_client.py) | HTTP-Client (Login, Paging-Loader, PUT) über `QgsBlockingNetworkRequest` | -| [vln_karten/layer_manager.py](vln_karten/layer_manager.py) | GeoJSON ↔ Memory-Layer, Geometrie-Splitting, Layer-Zusammenführung | +| [vln_karten/api_client.py](vln_karten/api_client.py) | HTTP-Client: Login, Paging-Loader, PUT, RFC-7807-Fehler | +| [vln_karten/layer_manager.py](vln_karten/layer_manager.py) | GeoJSON ↔ Memory-Layer, Geometrie-Splitting/-Vereinigung | | [vln_karten/login_dialog.py](vln_karten/login_dialog.py) | Anmeldedialog | | [vln_karten/metadata.txt](vln_karten/metadata.txt) | QGIS-Plugin-Metadaten | -## Technische Hinweise +## Installation (zum Ausprobieren) -- **CRS:** Die API liefert und erwartet Koordinaten in **EPSG:25833** - (abweichend von RFC 7946). Import/Export laufen ohne - WGS84-Transformation (`GEOJSON_CRS` in `layer_manager.py`). -- **Geometrien** gehen beim Hochladen als Multi-Typen an die API - (`ST_GeomFromGeoJSON` akzeptiert beides). Features ohne Geometrie - landen in einem Tabellen-Layer „ohne Geometrie“. -- **API-Key-Ablage:** unverschlüsselt in den QGIS-Einstellungen - (QSettings, Gruppe `vln_karten`). Wer das härten will, verlagert ihn - in den QGIS-Authentifizierungsmanager (`QgsApplication.authManager()`). -- **Requests** laufen synchron (blockierend) — für sehr große - Datensätze wäre `QgsNetworkAccessManager` mit Tasks der nächste Schritt. -- Die gewählte VKZ liegt in der Projektdatei (`writeEntry`-Scope - `vln_karten`) — verschiedene Projekte können verschiedene Verfahren - vorausgewählt haben. +Voraussetzung: QGIS **3.22+** oder **QGIS 4** (Qt6/PyQt6 — der Code +nutzt scoped Enums und `exec()`), plus ein VLN-Manager-Konto. -## Entwicklung +```bash +git clone https://entwicklung.flurneuordnung-sachsen.de/VLN_SN/API_Karte_QGISDemo.git +cd API_Karte_QGISDemo -Für schnelles Iterieren empfiehlt sich das Plugin **Plugin Reloader** -aus dem offiziellen QGIS-Repository — Codeänderungen wirken dann ohne -QGIS-Neustart. Der Plugin-Ordner kann dafür per Symlink direkt aus dem -Git-Arbeitsverzeichnis eingebunden bleiben (siehe Installation). +# macOS, QGIS 4 (bei QGIS 3: "QGIS4" durch "QGIS3" ersetzen): +ln -s "$PWD/vln_karten" \ + ~/Library/Application\ Support/QGIS/QGIS4/profiles/default/python/plugins/vln_karten + +# Linux: ~/.local/share/QGIS/QGIS4/profiles/default/python/plugins/ +# Windows: %APPDATA%\QGIS\QGIS4\profiles\default\python\plugins\ +``` + +Dann QGIS starten und unter *Erweiterungen → Erweiterungen verwalten* +„VLN Karten“ aktivieren (experimentelle Erweiterungen zulassen). Beim +Entwickeln hilft das Plugin **Plugin Reloader** — Codeänderungen wirken +dann ohne QGIS-Neustart. + +## Hinweise für eigene Integrationen + +- `QgsBlockingNetworkRequest` statt `requests`/`urllib` verwenden — + damit greifen Proxy- und Zertifikatseinstellungen aus QGIS. +- Der API-Key wird im Beispiel unverschlüsselt in QSettings abgelegt; + für Produktivcode bietet sich der QGIS-Authentifizierungsmanager an + (`QgsApplication.authManager()`). +- Requests laufen im Beispiel synchron (blockierend); für große + Datensätze auf `QgsNetworkAccessManager` mit Tasks wechseln. +- Features ohne Geometrie landen beim Laden in einem Tabellen-Layer + „ohne Geometrie“; Geometrien werden beim Hochladen als Multi-Typen + gesendet (`ST_GeomFromGeoJSON` akzeptiert beides). diff --git a/vln_karten/metadata.txt b/vln_karten/metadata.txt index 7830d3f..ff51596 100644 --- a/vln_karten/metadata.txt +++ b/vln_karten/metadata.txt @@ -3,8 +3,8 @@ name=VLN Karten qgisMinimumVersion=3.22 qgisMaximumVersion=4.99 supportsQt6=True -description=Beispiel-Plugin: Verfahrensumringe, Wege- und Gewässerplan und Karte alter Stamm über die VLN Karten-API laden und zurückschreiben. -about=Demonstriert Login (Benutzername/Passwort -> API-Key), Laden von GeoJSON-Daten in editierbare Memory-Layer und Zurückschreiben der Änderungen an die API. +description=Beispiel: Karten-API des VLN Managers aus QGIS ansteuern — Verfahrensgebiet, Plan 41, Karte alter Stand und Wertermittlung laden und zurückschreiben. +about=Referenz-Implementierung für die VLN-Manager-Karten-API: Login (mail/password -> userauth-API-Key), vollständiges Laden je VKZ mit Paging (EPSG:25833), Splitten gemischter Geometrietypen in Memory-Layer und vereintes Zurückschreiben per PUT. version=0.1.0 author=Erik email=phpwelt@gmail.com