# API_Karte_QGISDemo — Beispiel: Karten-API des VLN Managers aus QGIS ansteuern 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 Daten, die auch das Web-GIS (karte.flurneuordnung-sachsen.de) verwendet. Der Code ist bewusst als **Referenz-Implementierung** gehalten — wer eine eigene Integration (Plugin, Skript, Processing-Werkzeug) bauen will, findet hier alle Bausteine. ## Die Ansteuerung in Kürze So sieht der minimale Ablauf in PyQGIS aus (vollständige, robuste Fassung in [vln_karten/api_client.py](vln_karten/api_client.py)): ```python import json from qgis.PyQt.QtCore import QUrl from qgis.PyQt.QtNetwork import QNetworkRequest from qgis.core import QgsBlockingNetworkRequest BASE = "https://api.flurneuordnung-sachsen.de/v2" 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") # 1) Login: liefert das userauth-Token = API-Key login = request("post", "/person/login", {"mail": "...", "password": "..."}) api_key = login["data"]["userauth"] # 2) Layer lesen: GeoJSON FeatureCollection in EPSG:25833 fc = request("get", "/maps/p41?vkz=22017&limit=2000&offset=0", api_key=api_key) # 3) Layer schreiben: PUT ersetzt den kompletten Bestand dieser VKZ! request("put", "/maps/p41/22017", payload=fc, api_key=api_key) ``` Die drei Stolpersteine, die das Beispiel-Plugin jeweils sauber löst: 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-Kommunikation ``` POST /person/login Body: {"mail": "...", "password": "..."} Antwort: {"data": {"userauth": "", "id": ...}} userauth = API-Key, danach Header "X-API-Key: " GET /tgen Verfahren/TGs (Auth nötig) 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. ``` ## Das Beispiel-Plugin 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, 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 | ## Installation (zum Ausprobieren) Voraussetzung: QGIS **3.22+** oder **QGIS 4** (Qt6/PyQt6 — der Code nutzt scoped Enums und `exec()`), plus ein VLN-Manager-Konto. ```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\ ``` 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).