Linkliste: Kartenwahl hakt die passenden Dienste vor

Erik, 06.10.2026: neben dem Thema eine zweite Gliederung — für welche Karten ein Dienst gedacht
ist (Wege- und Gewässerplan, Neugestaltungsgrundsätze, Wertermittlung; mehrfach). Der Manager
liefert dafür je Eintrag `karten` und den Katalog unter GET /linkliste/karten.

Über der Dienstliste steht jetzt je Karte ein Häkchen (Katalog vom Server, Rückfall auf die drei
Karten bei älterem Server). Eine oder mehrere Karten gewählt: angehakt sind die Dienste mit
mindestens einer passenden Karte (linkliste.vorhaken), ohne Wahl alle wie bisher; von Hand
nachjustieren bleibt möglich, eine neu geholte Liste übernimmt die Wahl. Karten im Tooltip.
Fassung bleibt 0.7.0 (noch nicht ausgeliefert). Tests ohne Netz erweitert, beide grün.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
KAI
2026-10-06 23:19:47 +02:00
co-authored by Claude Opus 5.5
parent a56bf7c397
commit c3386dc454
7 changed files with 139 additions and 3 deletions
+4
View File
@@ -272,6 +272,10 @@ mit den Häkchen (Erik, 06.10.2026).
- **Ausgewählte laden** hängt die angehakten Dienste schrittweise unter „Geodienste <vkz> <Name>“
ein: Gruppe › Untergruppe › Layer (`linkliste.zielgruppe`); ohne Untergruppe direkt unter die
Gruppe. Ein hinterlegter Stil kommt über `GET /linkliste/{id}/style.qml`.
- **Für Karte** (seit 0.7.0): über der Liste je Karte ein Häkchen — Wege- und Gewässerplan,
Neugestaltungsgrundsätze, Wertermittlung (Katalog vom Server, `GET /linkliste/karten`). Eine
oder mehrere Karten gewählt: angehakt sind die Dienste, die der Landkreis bzw. die landesweite
Pflege dieser Karte zugeordnet hat (`linkliste.vorhaken`); nachjustieren von Hand bleibt möglich.
- Anbieter: WMS (`QgsRasterLayer`), WFS, ArcGIS REST (FeatureServer) und GeoJSON/OGR.
- **Nur lesen:** eine oFB sieht die Verfahren ihres Landkreises; deren OGC-Sammlungen tragen
`vln:schreibbar: false` und werden schreibgeschützt geladen (`layer.setReadOnly(True)`).
+20
View File
@@ -91,6 +91,26 @@ pruefe(zweiter.name() == "Biotope" and len(oben.children()) == 2 and len(natur.c
"zweiter Ladelauf legt nichts doppelt an")
pruefe(sonst.name() == "Sonstiges" and not sonst.children(), "ohne Untergruppe direkt in die Gruppe")
print("== Karten: Katalog und Vorhaken ==")
from vln_karten.linkliste import KARTEN_VORGABE, karten_laden, vorhaken
d = [{"name": "A", "karten": ["p41"]}, {"name": "B", "karten": ["ngg", "we"]}, {"name": "C"}]
pruefe(vorhaken(d, []) == [True, True, True], "ohne Kartenwahl alles angehakt")
pruefe(vorhaken(d, ["p41"]) == [True, False, False], "Wege- und Gewässerplan: nur A")
pruefe(vorhaken(d, ["p41", "we"]) == [True, True, False], "zwei Karten: A und B (mehrfach)")
k = ac.KartenApiClient(api_key="pruef")
k._request = lambda m, p, **kw: {"karten": [{"schluessel": "p41", "name": "Wege- und Gewässerplan"},
{"schluessel": "kas", "name": "Karte alter Stand"}]}
pruefe([x["schluessel"] for x in karten_laden(k)] == ["p41", "kas"],
"Katalog vom Server (neue Karte ohne neue Plugin-Fassung)")
def kein_katalog(m, p, **kw):
raise ac.ApiError("GET /linkliste/karten lieferte HTTP 404", 404)
k._request = kein_katalog
pruefe(karten_laden(k) == KARTEN_VORGABE, "aelterer Server: Vorgabe der drei Karten")
print("== Ergebnis: %d Fehler ==" % len(FEHLER))
for f in FEHLER:
print(" -", f)
+21
View File
@@ -88,6 +88,27 @@ panel.set_dienste([{"name": "Denkmal Punkte", "gruppe_name": "Kultur", "untergru
pruefe(panel.dienste_liste.item(0).text().startswith("Denkmal Punkte · Kultur › Denkmal · "),
"Untergruppe in der Dienstliste: %r" % panel.dienste_liste.item(0).text())
pruefe(panel.dienste_liste.item(1).text().startswith("Wald · Natur · "), "ohne Untergruppe wie bisher")
print("== Karten: Wahl hakt vor ==")
from vln_karten.linkliste import KARTEN_VORGABE
panel.set_karten(KARTEN_VORGABE)
pruefe(list(panel.karten_haken) == ["p41", "ngg", "we"] and panel.karten_beschriftung.isVisibleTo(panel),
"je Karte ein Haekchen")
panel.set_dienste([{"name": "Biotope", "gruppe_name": "Natur", "karten": ["ngg"]},
{"name": "Wege", "gruppe_name": "Verkehr", "karten": ["p41", "ngg"]},
{"name": "Bodenwert", "gruppe_name": "Boden", "karten": ["we"]}])
an = lambda: [panel.dienste_liste.item(i).checkState() == Qt.CheckState.Checked
for i in range(panel.dienste_liste.count())]
pruefe(an() == [True, True, True], "ohne Wahl alles angehakt")
panel.karten_haken["p41"].setChecked(True)
pruefe(an() == [False, True, False], "Wege- und Gewaesserplan: nur passende: %r" % an())
panel.karten_haken["we"].setChecked(True)
pruefe(an() == [False, True, True], "zwei Karten zugleich: %r" % an())
panel.set_dienste([{"name": "Neu", "karten": ["we"]}, {"name": "Ohne"}])
pruefe(an() == [True, False], "neue Liste uebernimmt die Kartenwahl: %r" % an())
pruefe("Karten: Wertermittlung" in panel.dienste_liste.item(0).toolTip(), "Karten im Tooltip")
for haken in panel.karten_haken.values():
haken.setChecked(False)
panel.set_angemeldet(False)
GERUFEN.clear()
+40
View File
@@ -47,6 +47,46 @@ WMS_FORMAT = "image/png"
WMS_CRS = "EPSG:25833"
#: Rückfall, wenn der Server den Kartenkatalog noch nicht kennt (Manager vor dem 06.10.2026).
KARTEN_VORGABE = [{"schluessel": "p41", "name": "Wege- und Gewässerplan"},
{"schluessel": "ngg", "name": "Neugestaltungsgrundsätze"},
{"schluessel": "we", "name": "Wertermittlung"}]
def karten_laden(client):
"""``GET /linkliste/karten`` -> ``[{schluessel, name}]`` — für welche Karten ein Dienst
gedacht sein kann (Erik, 06.10.2026). Ein älterer Server kennt die Route nicht: dann die
Vorgabe; kommt dort eine Karte dazu, braucht das Plugin keine neue Fassung.
"""
try:
daten = client._request("GET", "/linkliste/karten")
except ApiError as exc:
if getattr(exc, "status", None) in (400, 404, 405):
return list(KARTEN_VORGABE)
raise
zeilen = daten.get("karten") if isinstance(daten, dict) else None
if not isinstance(zeilen, list):
return list(KARTEN_VORGABE)
return [{"schluessel": str(z.get("schluessel")), "name": str(z.get("name") or z.get("schluessel"))}
for z in zeilen if isinstance(z, dict) and z.get("schluessel")]
def vorhaken(dienste, gewaehlt):
"""Welche Dienste für die gewählten Karten angehakt werden.
Ohne Kartenwahl alle (wie bisher); sonst jeder Dienst, der mindestens einer gewählten Karte
zugeordnet ist. Nachjustieren von Hand bleibt möglich — das hier ist nur die Vorbelegung.
:param dienste: Einträge der Liste (je ``karten``: Liste von Schlüsseln).
:param gewaehlt: Schlüssel der gewählten Karten.
:returns: Liste ``bool`` in der Reihenfolge von ``dienste``.
"""
gewaehlt = set(gewaehlt or ())
if not gewaehlt:
return [True] * len(dienste)
return [bool(gewaehlt & set(d.get("karten") or ())) for d in dienste]
def dienste_laden(client, vkz):
"""``GET /linkliste?vkz=…`` -> Liste der aufgelösten Einträge.
+1 -1
View File
@@ -15,6 +15,6 @@ category=Web
icon=
experimental=True
deprecated=False
changelog=0.7.0 Linkliste mit Untergruppen (Gruppe › Untergruppe › Layer) und für den ganzen Landkreis; Auswahl über /tgen/auswahl: als oFB die Verfahren des Landkreises „nur lesen“, Layer dort schreibgeschützt; ein 403 meldet nicht mehr ab.
changelog=0.7.0 Linkliste mit Untergruppen (Gruppe › Untergruppe › Layer), Kartenwahl (Wege- und Gewässerplan, Neugestaltungsgrundsätze, Wertermittlung) hakt passende Dienste vor, Linkliste auch für den ganzen Landkreis; Auswahl über /tgen/auswahl: als oFB die Verfahren des Landkreises „nur lesen“, Layer dort schreibgeschützt; ein 403 meldet nicht mehr ab.
0.6.0 Widmungen und Verkehrszeichen: eigene Layergruppe, Schildgrafiken vom Server (lokal abgelegt), Palette mit Suche, Werkzeuge „Schild setzen" und „Zusatzzeichen"; Seitenleiste rollt statt das QGIS-Fenster zu strecken (Issue #1).
0.5.0 Geodienste aus der Linkliste, Stile speichern, Seitenleiste statt Werkzeugleiste.
+47 -2
View File
@@ -260,6 +260,16 @@ class VlnPanel(QDockWidget):
lage.setSpacing(6)
lage.addWidget(_klein(QLabel("WMS-, WFS- und ArcGIS-Dienste für dieses Verfahren "
"oder den gewählten Landkreis.")))
# Kartenwahl (Erik, 06.10.2026): hakt vor, was für die gewählte Karte gedacht ist.
# Die Häkchen entstehen erst mit dem Katalog vom Server (set_karten).
self.karten_haken = {}
self._karten_namen = {}
self.karten_zeile = QHBoxLayout()
self.karten_zeile.setSpacing(10)
self.karten_beschriftung = _klein(QLabel("Für Karte:"))
self.karten_beschriftung.setVisible(False)
self.karten_zeile.addWidget(self.karten_beschriftung)
lage.addLayout(self.karten_zeile)
self.dienste_liste = QListWidget()
# Bewusst niedrig: die Mindesthoehe aller Kaesten zusammen ist die Mindesthoehe des
# Docks (s. Rollflaeche im Konstruktor). 140 px hier machten die Seitenleiste auf
@@ -289,6 +299,37 @@ class VlnPanel(QDockWidget):
for i in range(self.dienste_liste.count()):
self.dienste_liste.item(i).setCheckState(ziel)
def set_karten(self, katalog):
"""Die Kartenwahl aufbauen: je Karte ein Häkchen; eine Wahl hakt die passenden Dienste vor.
:param katalog: ``[{schluessel, name}]`` vom Server (``GET /linkliste/karten``).
"""
for haken in self.karten_haken.values():
self.karten_zeile.removeWidget(haken)
haken.deleteLater()
self.karten_haken = {}
self._karten_namen = {}
for k in katalog or []:
haken = QCheckBox(str(k.get("name") or k.get("schluessel")))
haken.setToolTip("Dienste für diese Karte vorhaken")
haken.stateChanged.connect(lambda _zustand: self._karten_anwenden())
self.karten_zeile.addWidget(haken)
self.karten_haken[str(k.get("schluessel"))] = haken
self._karten_namen[str(k.get("schluessel"))] = str(k.get("name") or k.get("schluessel"))
self.karten_beschriftung.setVisible(bool(self.karten_haken))
def gewaehlte_karten(self):
"""Schlüssel der angehakten Karten."""
return [k for k, haken in self.karten_haken.items() if haken.isChecked()]
def _karten_anwenden(self):
"""Die Dienste nach der Kartenwahl vorhaken (ohne Wahl: alle)."""
from .linkliste import vorhaken
eintraege = [self.dienste_liste.item(i) for i in range(self.dienste_liste.count())]
daten = [e.data(Qt.ItemDataRole.UserRole) or {} for e in eintraege]
for eintrag, an in zip(eintraege, vorhaken(daten, self.gewaehlte_karten())):
eintrag.setCheckState(Qt.CheckState.Checked if an else Qt.CheckState.Unchecked)
def set_dienste(self, dienste):
"""Die geholte Liste anzeigen: Name, Gruppe › Untergruppe und Herkunft, alle zunächst
angehakt."""
@@ -304,8 +345,12 @@ class VlnPanel(QDockWidget):
eintrag.setFlags(eintrag.flags() | Qt.ItemFlag.ItemIsUserCheckable)
eintrag.setCheckState(Qt.CheckState.Checked)
eintrag.setData(Qt.ItemDataRole.UserRole, d)
eintrag.setToolTip("%s\n%s" % (d.get("url"), d.get("beschreibung") or ""))
karten = ", ".join(self._karten_namen.get(k, k) for k in (d.get("karten") or []))
eintrag.setToolTip("%s\n%s%s" % (d.get("url"), d.get("beschreibung") or "",
("\nKarten: " + karten) if karten else ""))
self.dienste_liste.addItem(eintrag)
if self.gewaehlte_karten():
self._karten_anwenden()
def gewaehlte_dienste(self):
"""Die angehakten Einträge als Liste der Server-Datensätze."""
@@ -350,7 +395,7 @@ class VlnPanel(QDockWidget):
self.schild_knopf, self.zusatz_knopf, self.palette, self.blatt_combo,
self.stil_knopf, self.vorlage_haken, self.dienste_holen_knopf,
self.dienste_laden_knopf, self.dienste_alle, self.dienste_liste,
*self.gruppen_haken.values()):
*self.gruppen_haken.values(), *self.karten_haken.values()):
widget.setEnabled(angemeldet)
if not angemeldet:
self._stumm = True
+6
View File
@@ -397,6 +397,12 @@ class VlnKartenPlugin:
except ApiError as exc:
self._show_error("Linkliste", str(exc))
return
if not self.panel.karten_haken:
# Kartenkatalog einmal je Sitzung; ohne ihn bleibt es beim Anhaken von Hand.
try:
self.panel.set_karten(linkliste.karten_laden(self.client))
except ApiError:
self.panel.set_karten(linkliste.KARTEN_VORGABE)
self.panel.set_dienste(dienste)
herkunft = {}
for d in dienste: