Schneller laden: Antworten vorwaermen, Gruppen auswaehlen

Der OAPIF-Provider fragt je Sammlung nacheinander Sammlung, Schema, Stil,
OPTIONS und die erste Seite ab — bei 27 Sammlungen dauert das Anlegen 20,5 s
(gemessen 08.09.2026, Berbisdorf).

- ogc_layers.vorwaermen(): holt Landing, OpenAPI, Conformance, Sammlung, Schema
  und Stil vorab und gleichzeitig ueber Qts Ereignisschleife (unter 1,5 s fuer
  84 Antworten). layer_hinzufuegen ruft es vor dem ersten Layer auf. Ergebnis
  14,2 s statt 20,5 s, und die Zeit schwankt deutlich weniger.
- Seitenleiste: drei Haken (Verfahren / Vorerhebung / Massnahmenblaetter) waehlen
  aus, was geladen wird. Nur Verfahren 11,1 s (21 Layer), nur Vorerhebung oder
  nur Massnahmenblaetter je 1,6 s (3 Layer).

Bewusst NICHT parallel: ein in einem Python-Thread gebauter OAPIF-Layer laesst
sich zwar in 0,9 s erzeugen, stuerzt aber beim ersten getFeatures() ab
(Segfault, gemessen) — der Provider gehoert dem Thread, der ihn gebaut hat.

tools/headless_panel_test.py prueft die neue Auswahl mit. metadata.txt 0.4.0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
erik
2026-09-08 09:32:56 +02:00
co-authored by Claude Fable 5.1
parent b97f066b49
commit 1680b4ede3
6 changed files with 116 additions and 8 deletions
+10 -1
View File
@@ -122,7 +122,7 @@ Verbandsmitarbeiter alle (`?vkz=` in `/ogc/collections` grenzt ein).
### Mit dem Plugin
Knopf **„Alle Layer laden"** in der Seitenleiste: legt nach dem Login eine
Knopf **„Layer laden"** in der Seitenleiste: legt nach dem Login eine
QGIS-Authentifizierungskonfiguration *API-Header* (`X-API-Key`) an — das Passwort
bleibt draußen —, holt `GET /ogc/collections?vkz=…` und hängt jede Sammlung als
`OAPIF`-Layer in eine Gruppe „VLN <Verfahren>" (Untergruppen Vorerhebung /
@@ -231,6 +231,15 @@ erlebbar:
Anmeldung, Verfahrensliste aus `GET /tgen` mit Suchfeld, Lade- und Speicherknöpfe,
Statuszeile. Wieder einblenden über *Web → VLN Karten → Seitenleiste anzeigen*.
- **Verfahrens-Auswahl** gemerkt je QGIS-Projektdatei.
- **Ladezeit:** jede Sammlung kostet beim Anlegen rund eine halbe Sekunde — der
OAPIF-Provider fragt Sammlung, Schema, Stil, `OPTIONS` und die erste Seite ab, nacheinander
und blockierend. Zwei Gegenmittel: `vorwaermen()` holt vorab alle diese Antworten
gleichzeitig (unter 1,5 s), und die Häkchen in der Seitenleiste laden nur die gewünschten
Gruppen. Gemessen am 08.09.2026 (Berbisdorf, 27 Sammlungen): **20,5 s ohne, 14,2 s mit**
Vorwärmen; nur Verfahren 11,1 s (21 Layer), nur Vorerhebung oder nur Maßnahmenblätter je
1,6 s (3 Layer). **Nicht parallel:** ein in einem Python-Thread gebauter OAPIF-Layer stürzt
beim ersten `getFeatures()` ab (Segfault, gemessen) — der Provider gehört dem Thread, der
ihn erzeugt hat.
- **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.
+6
View File
@@ -85,6 +85,12 @@ for erwartet in ("load_ogc", "save_style", "upload", "load_dataset"):
pruefe(erwartet in namen, "Rueckruf %s" % erwartet)
ds = [a for n, a in GERUFEN if n == "load_dataset"]
pruefe(ds and ds[0][0] == list(DATASETS)[0], "Datensatz-Knopf uebergibt Schluessel: %s" % (ds[0] if ds else "-"))
pruefe(panel.gewaehlte_gruppen() == {"Verfahren", "Vorerhebung", "Maßnahmenblätter"},
"alle Gruppen zunaechst angehakt: %s" % sorted(panel.gewaehlte_gruppen()))
panel.gruppen_haken["Verfahren"].setChecked(False)
pruefe(panel.gewaehlte_gruppen() == {"Vorerhebung", "Maßnahmenblätter"},
"Abwaehlen wirkt: %s" % sorted(panel.gewaehlte_gruppen()))
panel.gruppen_haken["Verfahren"].setChecked(True)
pruefe(not panel.als_vorlage(), "Vorlage-Haken standardmaessig aus")
panel.vorlage_haken.setChecked(True)
pruefe(panel.als_vorlage(), "Vorlage-Haken wirkt")
+1 -1
View File
@@ -5,7 +5,7 @@ qgisMaximumVersion=4.99
supportsQt6=True
description=Beispiel: Karten-API des VLN Managers aus QGIS ansteuern — OGC API Features (alle Layer editierbar, Vorerhebung bis Maßnahmenblatt) und der klassische GeoJSON-Weg.
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.3.0
version=0.4.0
author=Erik
email=phpwelt@gmail.com
tracker=https://entwicklung.flurneuordnung-sachsen.de/VLN_SN/API_Karte_QGISDemo/issues
+66 -2
View File
@@ -27,7 +27,7 @@ Blatt-Spalten als ``blatt_*``-Attribute; neue Features brauchen ``blatt_uuid`` o
import os
import tempfile
from qgis.PyQt.QtCore import QSettings, QUrl, Qt, QTimer
from qgis.PyQt.QtCore import QEventLoop, QSettings, QUrl, Qt, QTimer
from qgis.PyQt.QtNetwork import QNetworkRequest
from qgis.PyQt.QtWidgets import QProgressBar
from qgis.core import (
@@ -35,6 +35,7 @@ from qgis.core import (
QgsApplication,
QgsAuthMethodConfig,
QgsDataSourceUri,
QgsNetworkAccessManager,
QgsProject,
QgsVectorLayer,
)
@@ -204,6 +205,61 @@ def layer_uri(base_url, cid, authcfg=None, storage_crs=None, api_key=None):
return uri.uri(False)
def vorwaermen(base_url, collections, api_key=None, authcfg=None, gleichzeitig=8):
"""Die Antworten, die der Provider gleich braucht, vorab und **gleichzeitig** holen.
Der OAPIF-Provider fragt je Layer nacheinander Landing Page, OpenAPI, Conformance,
Sammlung, Schema und (über das Plugin) den Stil ab — bei 27 Sammlungen über 160 Anfragen,
jede einzeln und blockierend. Alle diese Antworten tragen serverseitig ``Cache-Control``,
liegen also nach einem Vorablauf im Plattencache von QGIS und kosten den Provider danach
kein Netz mehr. Gemessen 08.09.2026 (Berbisdorf, 27 Sammlungen): 30,7 s ohne, 14,9 s mit
Vorwärmen; das Vorwärmen selbst dauert unter einer Sekunde.
Bewusst **kein** paralleles Anlegen der Layer: ein in einem Python-Thread erzeugter
OAPIF-Layer stürzt beim ersten ``getFeatures()`` ab (Segfault, gemessen) — der Provider
gehört dem Thread, der ihn gebaut hat. Hier laufen nur reine Netzanfragen nebeneinander,
über Qts eigene Ereignisschleife im selben Thread.
:returns: Anzahl der vorgeladenen Antworten.
"""
ziele = [ogc_url(base_url), ogc_url(base_url) + "/api", ogc_url(base_url) + "/conformance"]
for c in collections or []:
cid = str((c or {}).get("id") or "")
if cid:
ziele.append("%s/collections/%s" % (ogc_url(base_url), cid))
ziele.append("%s/collections/%s/schema" % (ogc_url(base_url), cid))
ziele.append(stil_url(base_url, cid))
verwalter = QgsNetworkAccessManager.instance()
geholt = 0
for anfang in range(0, len(ziele), gleichzeitig):
haufen = ziele[anfang:anfang + gleichzeitig]
schleife = QEventLoop()
offen = {"n": len(haufen)}
def fertig(_antwort=None):
offen["n"] -= 1
if offen["n"] <= 0:
schleife.quit()
antworten = []
for url in haufen:
req = QNetworkRequest(QUrl(url))
if api_key:
req.setRawHeader(b"X-API-Key", api_key.encode("utf-8"))
if authcfg:
QgsApplication.authManager().updateNetworkRequest(req, authcfg)
req.setAttribute(QNetworkRequest.Attribute.CacheSaveControlAttribute, True)
antwort = verwalter.get(req)
antwort.finished.connect(fertig)
antworten.append(antwort)
schleife.exec()
for antwort in antworten:
antwort.readAll() # Rumpf lesen, damit er im Cache landet
antwort.deleteLater()
geholt += 1
return geholt
def layer_hinzufuegen(iface, base_url, vkz, vkz_name, collections, authcfg=None, api_key=None):
"""Alle Sammlungen als OAPIF-Layer in die Gruppe „VLN <Verfahren>" hängen — **schrittweise**.
@@ -225,10 +281,18 @@ def layer_hinzufuegen(iface, base_url, vkz, vkz_name, collections, authcfg=None,
balken.setMaximum(len(collections))
balken.setValue(0)
balken.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
meldung = iface.messageBar().createMessage("VLN Karten", "Layer werden geladen …")
meldung = iface.messageBar().createMessage("VLN Karten", "Layer werden vorbereitet …")
meldung.layout().addWidget(balken)
iface.messageBar().pushWidget(meldung, Qgis.Info)
# Erst die Antworten gleichzeitig in den Plattencache holen (unter einer Sekunde), dann
# bauen — das halbiert die Ladezeit, weil der Provider danach kaum noch ans Netz muss.
try:
vorwaermen(base_url, collections, api_key=api_key, authcfg=authcfg)
except Exception: # noqa: BLE001
pass # Vorwärmen ist Beiwerk
meldung.setText("Layer werden geladen …")
zustand = {"i": 0, "geladen": 0, "fehler": []}
def schritt():
+21 -4
View File
@@ -108,9 +108,21 @@ class VlnPanel(QDockWidget):
kasten = QGroupBox("OGC API (empfohlen)")
lage = QVBoxLayout(kasten)
lage.setSpacing(6)
self.ogc_knopf = QPushButton("Alle Layer laden")
self.ogc_knopf.setToolTip("Verfahrenslayer, Vorerhebung und Maßnahmenblätter als "
"editierbare OAPIF-Layer; QGIS schreibt Änderungen direkt zurück")
# Jede Sammlung kostet beim Anlegen rund eine halbe Sekunde (der Provider fragt
# Sammlung, Schema, Stil, OPTIONS und die erste Seite ab). Wer nur einen Teil braucht,
# ist mit der Auswahl deutlich schneller: 27 Layer ~16 s, die Vorerhebung allein ~2 s.
lage.addWidget(_klein(QLabel("Was geladen wird:")))
self.gruppen_haken = {}
for schluessel, beschriftung in (("Verfahren", "Verfahren (Umring, Plan 41, KAS …)"),
("Vorerhebung", "Vorerhebung"),
("Maßnahmenblätter", "Maßnahmenblätter")):
haken = QCheckBox(beschriftung)
haken.setChecked(True)
lage.addWidget(haken)
self.gruppen_haken[schluessel] = haken
self.ogc_knopf = QPushButton("Layer laden")
self.ogc_knopf.setToolTip("Die angehakten Sammlungen als editierbare OAPIF-Layer; "
"QGIS schreibt Änderungen direkt zurück")
self.ogc_knopf.clicked.connect(lambda: self._ruf("load_ogc"))
self.stil_knopf = QPushButton("Stil des aktiven Layers speichern")
self.stil_knopf.setToolTip("Braucht die Gruppe MODUL_KARTENSTIL (Verbandsmitarbeiter "
@@ -160,7 +172,8 @@ class VlnPanel(QDockWidget):
self.login_knopf.setVisible(not angemeldet)
self.logout_knopf.setVisible(angemeldet)
for widget in (self.filter_feld, self.combo, self.ogc_knopf, self.stil_knopf,
self.vorlage_haken, self.upload_knopf, *self.datensatz_knoepfe):
self.vorlage_haken, self.upload_knopf, *self.gruppen_haken.values(),
*self.datensatz_knoepfe):
widget.setEnabled(angemeldet)
if not angemeldet:
self._stumm = True
@@ -190,6 +203,10 @@ class VlnPanel(QDockWidget):
def als_vorlage(self):
return self.vorlage_haken.isChecked()
def gewaehlte_gruppen(self):
"""Namen der angehakten Layer-Gruppen (wie in ``ogc_layers.GRUPPEN``)."""
return {name for name, haken in self.gruppen_haken.items() if haken.isChecked()}
def waehle_vkz(self, vkz):
"""Setzt die Auswahl ohne Rückruf (Wiederherstellen aus der Projektdatei)."""
self._stumm = True
+12
View File
@@ -421,6 +421,18 @@ class VlnKartenPlugin:
self._show_error("OGC API", "Für dieses Verfahren gibt es keine Sammlungen "
"(oder keine Berechtigung).")
return
# Nur die in der Seitenleiste angehakten Gruppen laden — jede Sammlung kostet beim
# Anlegen rund eine halbe Sekunde, und selten braucht man alle 27 gleichzeitig.
gewaehlt = self.panel.gewaehlte_gruppen()
gruppe_von = dict(ogc_layers.GRUPPEN)
collections = [c for c in collections
if gruppe_von.get(str(c.get("id") or "").split("-", 1)[0], "Weitere")
in gewaehlt]
if not collections:
self._show_error("Nichts ausgewählt",
"Bitte oben mindestens eine Gruppe anhaken (Verfahren, "
"Vorerhebung oder Maßnahmenblätter).")
return
# Schrittweise: je Ereignisschleifen-Durchlauf ein Layer, Fortschritt in der
# Meldungsleiste. Fehler kommen dort am Ende gesammelt, nicht als Modal-Dialog.
self.panel.set_status("Lade %d Sammlungen …" % len(collections))