Files
API_Karte_QGISDemo/README.md
T
erik 2eff228217 README: API-Kommunikation statt API-Vertrag, DB-Interna entfernt
Tabellennamen der Server-Datenbank gehen API-Nutzer nichts an —
beschrieben wird nur noch die Schnittstelle selbst.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:39:28 +02:00

6.6 KiB

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):

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).
  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": "<url_token>", "id": ...}}
  userauth = API-Key, danach Header "X-API-Key: <userauth>"

GET  /tgen                              Verfahren/TGs (Auth nötig)

GET  /maps/<layer>?vkz=&limit=&offset=  Layer lesen, mit Paging
GET  /maps/<layer>/{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/<layer>/{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.
  • „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 Toolbar, Aktionen, Verfahrens-Auswahl, DATASETS-Registry
vln_karten/api_client.py HTTP-Client: Login, Paging-Loader, PUT, RFC-7807-Fehler
vln_karten/layer_manager.py GeoJSON ↔ Memory-Layer, Geometrie-Splitting/-Vereinigung
vln_karten/login_dialog.py Anmeldedialog
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.

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).