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:
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user