Basemap-Legende (Basis #88): Umsetzung dokumentiert, ADR 0010, Wissensbasis

- Konzept 10_basemap_legende.md: Abschnitt 10 mit Umsetzung, Abweichungen und offenen Punkten
- ADR 0010: Basemap-Legende als eigenes Legendenelement mit Hilfslayern und Legendenkatalog
- schnittstellen.md: neue Projektvariablen, Abschnitt 11 Basemap-Legende
- modulkarte.md: neue Module, aktualisierte Zeilenzahlen
- fallstricke.md: Legendengröße vor dem ersten Zeichnen, Kartenfilter, Vektorkachel-Zoom,
  QT_VERSION unter QGIS 3, Mock-Lücken, Offscreen-Spike in QGIS
- STATUS.md: Punkt 15; Testkatalog neu erzeugt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 12:03:14 +02:00
co-authored by Claude Opus 5.5
parent bd8b215ddd
commit a1f399b150
9 changed files with 1881 additions and 301 deletions
+18 -11
View File
@@ -17,17 +17,18 @@ Legende: ⚠ = über 600 Zeilen, nie komplett laden, per `grep -n "def "` navigi
| `ui/dockmanager.py` | 86 | `DockManager.show(dock)`: entfernt alle anderen `sn_dock_*`-Docks, zeigt das neue rechts an | | `ui/dockmanager.py` | 86 | `DockManager.show(dock)`: entfernt alle anderen `sn_dock_*`-Docks, zeigt das neue rechts an |
| `ui/base_dockwidget.py` | 126 | `BaseDockWidget`: Titel „LNO Sachsen \| <Untertitel>“, Tabs aus Klassenattribut `tabs`, ruft `cleanup()` der Tabs beim Schließen, setzt Toolbar-Action zurück | | `ui/base_dockwidget.py` | 126 | `BaseDockWidget`: Titel „LNO Sachsen \| <Untertitel>“, Tabs aus Klassenattribut `tabs`, ruft `cleanup()` der Tabs beim Schließen, setzt Toolbar-Action zurück |
| `ui/tabs/settings_tab.py` | 158 | Tab „Projekteigenschaften“: Benutzer- und Projektfelder → Projektvariablen (`SettingsLogic`) | | `ui/tabs/settings_tab.py` | 158 | Tab „Projekteigenschaften“: Benutzer- und Projektfelder → Projektvariablen (`SettingsLogic`) |
| `ui/tabs/print_tab.py` ⚠ | 709 | Tab „Druck“: Druckbereich, Maßstab, Format, Themen, Legende, Atlas; nutzt `print_logic` | | `ui/tabs/print_tab.py` ⚠ | 730 | Tab „Druck“: Druckbereich, Maßstab, Format, Themen, Legende, Atlas, Basemap-Bereich; nutzt `print_logic` |
| `ui/widgets/basemap_bereich.py` | 271 | Einklappbarer Bereich „Basemap“ im Druck-Tab: Auswertebereich, Legende aktualisieren, Stilvorlagen |
### functions/ – Wrapper-Schicht (Qt/QGIS/OS mit Mock-Fallback) ### functions/ – Wrapper-Schicht (Qt/QGIS/OS mit Mock-Fallback)
| Datei | Zeilen | Zweck | | Datei | Zeilen | Zweck |
|---|---:|---| |---|---:|---|
| `functions/__init__.py` | 50 | Re-Export vieler Wrapper (`from sn_basis.functions import QgsProject, info, ...`) | | `functions/__init__.py` | 50 | Re-Export vieler Wrapper (`from sn_basis.functions import QgsProject, info, ...`) |
| `functions/qt_wrapper.py` ⚠ | 796 | Qt-Symbole: PyQt6 primär, PyQt5-Fallback, sonst Mock. `QT_VERSION` (0/5/6), Enum-Aliase (`DockAreaRight`, …), `exec_dialog()` | | `functions/qt_wrapper.py` ⚠ | 832 | Qt-Symbole: PyQt6 primär, PyQt5-Fallback, sonst Mock. `QT_VERSION` (0/5/6), Enum-Aliase (`DockAreaRight`, …), `exec_dialog()` |
| `functions/qgiscore_wrapper.py` ⚠ | 1 122 | `qgis.core`-Symbole und Konstanten; ab Zeile ~255 Mock-Klassen; Helfer `network_head`, `get_layer_extent`, `create_buffer_layer`, `layer_exists_in_gpkg`, `polygon_zu_linie` | | `functions/qgiscore_wrapper.py` ⚠ | 1 382 | `qgis.core`-Symbole und Konstanten; ab Zeile ~255 Mock-Klassen; eigener Block für Vektorkacheln/Ausdrücke/Legende (#88); Helfer `network_head`, `get_layer_extent`, `create_buffer_layer`, `layer_exists_in_gpkg`, `polygon_zu_linie` |
| `functions/qgisui_wrapper.py` | 258 | `iface`-Zugriffe: Hauptfenster, Docks, Menüs, Toolbars, Layoutdesigner; Mock-`iface` | | `functions/qgisui_wrapper.py` | 258 | `iface`-Zugriffe: Hauptfenster, Docks, Menüs, Toolbars, Layoutdesigner; Mock-`iface` |
| `functions/dialog_wrapper.py` | 393 | Dialoge: `ask_yes_no`, `show_info_dialog`, `ask_text`, Überschreiben/Anhängen/Abbrechen, `ProgressDialog`/`create_progress_dialog` | | `functions/dialog_wrapper.py` | 521 | Dialoge: `ask_yes_no`, `show_info_dialog`, `ask_text`, Überschreiben/Anhängen/Abbrechen, `ProgressDialog`/`create_progress_dialog`, `ask_mehrfachauswahl` (Liste mit Häkchen) |
| `functions/message_wrapper.py` | 85 | MessageBar: `info`, `warning`, `error`, `success`, `push_message` | | `functions/message_wrapper.py` | 85 | MessageBar: `info`, `warning`, `error`, `success`, `push_message` |
| `functions/variable_wrapper.py` | 120 | `get_variable(key, scope)`, `set_variable(...)` mit Präfix `sn_`; Mock-Speicher | | `functions/variable_wrapper.py` | 120 | `get_variable(key, scope)`, `set_variable(...)` mit Präfix `sn_`; Mock-Speicher |
| `functions/settings_logic.py` | 50 | `SettingsLogic.VARIABLEN`: Liste der gespeicherten Projektvariablen, `load()/save()` | | `functions/settings_logic.py` | 50 | `SettingsLogic.VARIABLEN`: Liste der gespeicherten Projektvariablen, `load()/save()` |
@@ -55,9 +56,14 @@ Legende: ⚠ = über 600 Zeilen, nie komplett laden, per `grep -n "def "` navigi
| `modules/Datenabruf.py` ⚠ | 636 | `Datenabruf.datenabruf(...)`: WFS, ArcGIS REST, OGR laden; Raumfilter Verfahrensgebiet/Puffer/ohne | | `modules/Datenabruf.py` ⚠ | 636 | `Datenabruf.datenabruf(...)`: WFS, ArcGIS REST, OGR laden; Raumfilter Verfahrensgebiet/Puffer/ohne |
| `modules/LayerLoader.py` | 422 | `create_layer(provider, link, thema)`, `apply_style`, `filter_by_extent`, `filter_by_layer`, `add_to_project` | | `modules/LayerLoader.py` | 422 | `create_layer(provider, link, thema)`, `apply_style`, `filter_by_extent`, `filter_by_layer`, `add_to_project` |
| `modules/Datenschreiber.py` | 448 | `schreibe_Daten(...)` in GeoPackage, `lade_Layer(...)` ins Projekt, `schreibe_log(...)` HTML-Protokoll | | `modules/Datenschreiber.py` | 448 | `schreibe_Daten(...)` in GeoPackage, `lade_Layer(...)` ins Projekt, `schreibe_log(...)` HTML-Protokoll |
| `modules/print_logic.py` ⚠ | 1 321 | `PrintLogic`, `AtlasConfig`, `RectangleCaptureTool`: Druckbereich, Maßstab, Atlas, Layoutaufruf | | `modules/print_logic.py` ⚠ | 1 360 | `PrintLogic`, `AtlasConfig`, `RectangleCaptureTool`: Druckbereich, Maßstab, Atlas, Layoutaufruf |
| `modules/print_layout.py` ⚠ | 1 078 | `PrintLayout`: Layoutaufbau für § 38 / § 41 (Karte, Legende, Gitter, Schriftfeld) | | `modules/print_layout.py` ⚠ | 1 111 | `PrintLayout`: Layoutaufbau für § 38 / § 41 (Karte, Legende, Gitter, Schriftfeld) |
| `modules/print_layout_baufreigabe.py` | 403 | Layout „Anlage zur Baufreigabe“ (feste DIN A3/A4 quer) | | `modules/print_layout_baufreigabe.py` | 403 | Layout „Anlage zur Baufreigabe“ (feste DIN A3/A4 quer) |
| `modules/basemap_legende.py` | 471 | Basemap-Legende (#88): Legendenkatalog laden, Styles → Einträge, Auswahl merken, Basemap-Layer erkennen, Auswertebereich; `BasemapLegende.aktualisiere(layout)` als Einstieg |
| `modules/basemap_kacheln.py` | 192 | Kacheln des Basemap-Layers kachelweise über `selectByGeometry` auswerten, Renderer-Styles lesen, Zoomstufen wie der QGIS-Renderer |
| `modules/basemap_legende_layout.py` | 396 | Legendensymbole aus Projektlayer-Styles zusammensetzen, leere Hilfslayer, Legendenelement „Legende Basemap“ schreiben und platzieren |
| `modules/basemap_katalog.py` | 418 | Erzeugt/aktualisiert `data/basemap_legende.json` aus der basemap.de-Style-JSON (nur Standardbibliothek, eigenständig aufrufbar) |
| `modules/basemap_stilvorlagen.py` | 181 | Lokale Stilvorlagen des Basemap-Layers (QML + JSON im QGIS-Profil), Standard in `QSettings` |
### Sonstiges ### Sonstiges
@@ -66,6 +72,7 @@ Legende: ⚠ = über 600 Zeilen, nie komplett laden, per `grep -n "def "` navigi
| `tests/` | 13 Testdateien, `unittest`, Mock-Modus; `run_tests.py` (farbiger Runner), `test_qgis.bat` (Coverage in OSGeo4W) | | `tests/` | 13 Testdateien, `unittest`, Mock-Modus; `run_tests.py` (farbiger Runner), `test_qgis.bat` (Coverage in OSGeo4W) |
| `styles/` | QML-Stile (Verfahrensgebiet, LSG, Denkmalschutz, Flurstücksbeschriftung) | | `styles/` | QML-Stile (Verfahrensgebiet, LSG, Denkmalschutz, Flurstücksbeschriftung) |
| `data/landkreise.json` | Landkreise mit Kennzahl | | `data/landkreise.json` | Landkreise mit Kennzahl |
| `data/basemap_legende.json` | Legendenkatalog basemap.de: Style-ID → Legendeneintrag (190 Einträge), erzeugt mit `basemap_katalog.py`, fachlich nachbearbeitet |
| `assets/plan-qgis340ExitCrashFix.prompt.md` | KI-Planungsnotiz zum Exit-Crash (historisch) | | `assets/plan-qgis340ExitCrashFix.prompt.md` | KI-Planungsnotiz zum Exit-Crash (historisch) |
| `.gitea/workflows/release.yaml`, `.gitea/templates/metadata.template` | Release-Auslöser, Vorlage für `metadata.txt` | | `.gitea/workflows/release.yaml`, `.gitea/templates/metadata.template` | Release-Auslöser, Vorlage für `metadata.txt` |
@@ -129,12 +136,12 @@ Dient als **minimales Muster** für ein neues Fachplugin (Skill `sn-neues-fachpl
| Datei | Zeilen | | Datei | Zeilen |
|---|---:| |---|---:|
| `sn_verfahrensgebiet/functions/grenzpunkt_alkis.py` | 1 674 | | `sn_verfahrensgebiet/functions/grenzpunkt_alkis.py` | 1 674 |
| `sn_basis/modules/print_logic.py` | 1 321 | | `sn_basis/modules/print_logic.py` | 1 360 |
| `sn_plan41/ui/tab_a_logic.py` | 1 239 | | `sn_plan41/ui/tab_a_logic.py` | 1 239 |
| `sn_basis/functions/qgiscore_wrapper.py` | 1 122 | | `sn_basis/functions/qgiscore_wrapper.py` | 1 382 |
| `sn_basis/modules/print_layout.py` | 1 078 | | `sn_basis/modules/print_layout.py` | 1 111 |
| `sn_verfahrensgebiet/functions/verfahrensgebiet_shape_komplett.py` | 978 | | `sn_verfahrensgebiet/functions/verfahrensgebiet_shape_komplett.py` | 978 |
| `sn_verfahrensgebiet/ui/tabs/working_tab.py` | 873 | | `sn_verfahrensgebiet/ui/tabs/working_tab.py` | 873 |
| `sn_verfahrensgebiet/functions/knickpunkt_pruefung.py` | 810 | | `sn_verfahrensgebiet/functions/knickpunkt_pruefung.py` | 810 |
| `sn_basis/functions/qt_wrapper.py` | 796 | | `sn_basis/functions/qt_wrapper.py` | 832 |
| `sn_basis/ui/tabs/print_tab.py` | 709 | | `sn_basis/ui/tabs/print_tab.py` | 730 |
+25
View File
@@ -55,6 +55,9 @@ Der Wrapper setzt das Präfix `sn_` selbst; Schlüssel **ohne** Präfix übergeb
| `linkliste`, `tab_a_linkliste` | Plan41 Tab A | Plan41 | Pfad der Linkliste (XLSX) | | `linkliste`, `tab_a_linkliste` | Plan41 Tab A | Plan41 | Pfad der Linkliste (XLSX) |
| `tab_a_layer_id`, `Raumfilter` | Plan41 Tab A | Plan41 | Raumfilter-Einstellung | | `tab_a_layer_id`, `Raumfilter` | Plan41 Tab A | Plan41 | Raumfilter-Einstellung |
| `druckrechteck_xmin/xmax/ymin/ymax` | PrintTab/PrintLogic | Druck | gespeicherter Druckbereich | | `druckrechteck_xmin/xmax/ymin/ymax` | PrintTab/PrintLogic | Druck | gespeicherter Druckbereich |
| `basemap_auswertebereich` | Basemap-Bereich im Druck-Tab | `basemap_legende` | `verfahrensgebiet` (Standard) oder `druckbereich` (Basis #88) |
| `basemap_legende_erzeugen` | Basemap-Bereich | `print_logic` | `1`/`0`, Standard `1`: Basemap-Legende beim Erstellen einer §-38/§-41-Vorlage |
| `basemap_legende_auswahl` | `basemap_legende`, Stilvorlage laden | Auswahldialog | JSON `{"ausgeblendet": [...], "eingeblendet": [...]}` mit Eintragsschlüsseln des Legendenkatalogs |
| `sn_abdeckungslayer` ⚠ | PrintTab/PrintLogic | Druck (Atlas) | Atlas-Abdeckungslayer. **Schlüssel enthält bereits `sn_` → tatsächliche Variable `sn_sn_abdeckungslayer`**; funktioniert, ist aber inkonsistent | | `sn_abdeckungslayer` ⚠ | PrintTab/PrintLogic | Druck (Atlas) | Atlas-Abdeckungslayer. **Schlüssel enthält bereits `sn_` → tatsächliche Variable `sn_sn_abdeckungslayer`**; funktioniert, ist aber inkonsistent |
| `verfahrensgebietslayer` ⚠ | `print_logic.py` | `print_logic.py` | **Zweite Schreibweise neben `verfahrensgebiet_layer`** – vermutlich Altlast, beim Refactoring zusammenführen | | `verfahrensgebietslayer` ⚠ | `print_logic.py` | `print_logic.py` | **Zweite Schreibweise neben `verfahrensgebiet_layer`** – vermutlich Altlast, beim Refactoring zusammenführen |
@@ -126,3 +129,25 @@ flowchart LR
## 10. Linkliste-Repository ## 10. Linkliste-Repository
`linkliste.csv` (Semikolon, Spalten `id;gruppe;ebene;inhalt;url;provider;stil`) → Gitea Action → `linkliste.xml/json/yaml` auf `main`. Die Plan41-Laufzeit nutzt stattdessen die mitgelieferte `assets/Linkliste.xlsx` mit abweichenden Spaltennamen. `linkliste.csv` (Semikolon, Spalten `id;gruppe;ebene;inhalt;url;provider;stil`) → Gitea Action → `linkliste.xml/json/yaml` auf `main`. Die Plan41-Laufzeit nutzt stattdessen die mitgelieferte `assets/Linkliste.xlsx` mit abweichenden Spaltennamen.
## 11. Basemap-Legende im Druck (Basis #88)
- **Welche Layouts:** `print_logic.LAYOUTS_MIT_BASEMAP_LEGENDE` (heute §38, §41). Ein Layout nutzt die Funktion,
wenn es die Layout-Eigenschaft `sn_basemap_legende = 1` trägt (`basemap_legende_layout.markiere_layout`);
`PrintLayout.create_single_page_layout(..., basemap_legende=True)` setzt sie und erzeugt die Legende vor dem
Öffnen des Designers. Neue Layouts: in die Konstante aufnehmen bzw. das Kennzeichen setzen.
- **Layout-Elemente nach ID:** `Hauptkarte` (Bezugskarte), `Legende` (Hauptlegende, Spalte und Oberkante),
`Quellenangabe` (Unterkante der Basemap-Legende), `Legende Basemap` (das Basemap-Element selbst).
Umbenennen dieser IDs bricht die Platzierung.
- **Weitere Layout-Eigenschaften:** `sn_basemap_bereich`, `sn_basemap_massstab`, `sn_basemap_eintraege` (JSON),
`sn_basemap_hilfslayer` (IDs der Hilfslayer, werden beim Aktualisieren ersetzt).
- **Hilfslayer:** leere Speicherlayer, nicht im Layerbaum, Eigenschaften `sn_basemap_legende = <Layoutname>` und
`skipMemoryLayersCheck = 1`. Code, der über `QgsProject.mapLayers()` iteriert, sieht sie.
- **Basemap-Layer-Erkennung:** Vektorkachel-Layer, Quelle enthält `gdz_basemapde_vektor`; Name „basemap.de“ nur
als Rückfall.
- **Legendenkatalog:** `sn_basis/data/basemap_legende.json` (Style-ID → Eintrag), erzeugt/aktualisiert mit
`python modules/basemap_katalog.py`; gehört zur Style-Version `bm_web_col` v2.
- **Stilvorlagen:** lokal unter `<QGIS-Profil>/sn_basis/basemap_stile/`, Standard in `QSettings`
`sn_basis/basemap_standardstil`.
- **Neue öffentliche Funktion:** `dialog_wrapper.ask_mehrfachauswahl(titel, hinweis, eintraege, vorauswahl)` –
Liste mit Häkchen, Gruppen, Suche; liefert gewählte Schlüssel oder `None` bei Abbruch.
@@ -0,0 +1,42 @@
# 0010 – Basemap-Legende als eigenes Legendenelement mit Hilfslayern und Legendenkatalog
Status: vorgeschlagen · Datum: 2026-10-02 · Beteiligte: Daniel Helbig, Claude Code · Ticket Plugin_SN_Basis #88
## Kontext
QGIS erzeugt für Vektorkachel-Layer keine Legende (`QgsVectorTileLayer` setzt keine `QgsMapLayerLegend`,
geprüft im Quellcode 3.40, 3.44, master). Die §-38-/§-41-Drucklayouts brauchen aber eine Legende der
basemap.de-Objekte, die im Verfahrensgebiet tatsächlich dargestellt werden, mit der Signatur des Projektlayers.
Die Style-JSON von basemap.de hat 569 Style-Layer mit technischen IDs; ein Legendeneintrag wie „Kreisstraße“
verteilt sich auf bis zu 20 Styles (Kontur, Decker, Brücke, getrennte Fahrbahn …).
Ein Spike in QGIS 3.44.4 (offscreen, echte Kacheln, Gebiet bei Meißen, 619 ha) am 02.10.2026 ergab:
- `QgsVectorTileLayer.selectByGeometry()` holt und dekodiert die Kacheln zur Zoomstufe des Maßstabs selbst
(5,8 s für 10 250 Objekte, entdoppelt 3 990; Style-Zuordnung 0,5 s).
- Hilfslayer in der **Hauptlegende** verschwinden beim Rendern, weil diese nach Karteninhalt filtert
(`setLegendFilterByMapEnabled(True)`) und die Hilfslayer nicht in der Karte liegen. Außerdem müsste die
Hauptlegende dafür ihre automatische Aktualisierung verlieren.
- Ein **eigenes Legendenelement** ohne Kartenfilter zeigt alle Einträge, übersteht Speichern und Neuladen des
Projekts; leere Speicherlayer lösen beim Schließen keine Rückfrage aus.
## Entscheidung
Die Basemap-Legende ist ein **eigenes Legendenelement „Legende Basemap“** ohne Kartenfilter und ohne
automatische Aktualisierung, dessen Einträge aus **leeren, kategorisierten Speicherlayern** stammen (je Gruppe
und Geometrietyp, nicht im Layerbaum, mit dem Projekt gespeichert). Welche Styles zu welchem Eintrag gehören
und welche das Symbol bilden, steht in einem **versionierten Legendenkatalog** `sn_basis/data/basemap_legende.json`,
der aus der Style-JSON erzeugt und fachlich nachbearbeitet wird. Signaturen werden zur Laufzeit aus dem
Projektlayer genommen, nicht aus dem Katalog.
## Folgen
- Hauptlegende bleibt unverändert (Auto-Update, Kartenfilter); der Basemap-Layer selbst erscheint dort nicht,
weil er keine Legendenknoten hat und der Filter ihn ausblendet.
- Lokale Änderungen am Basemap-Stil (Farben, abgeschaltete Styles) wirken automatisch auch in der Legende.
- Die Basemap-Legende ist eine Momentaufnahme; nach Änderungen an Karte oder Stil „Basemap-Legende aktualisieren“.
- Bei einer neuen Basemap-Stilversion muss der Katalog mit `modules/basemap_katalog.py` erneuert werden;
unbekannte Styles erscheinen bis dahin unter „Sonstiges“ (abgewählt).
- Je Layout liegen einige leere Speicherlayer im Projekt (nicht im Layerbaum sichtbar).
## Verworfene Alternativen
- **Hilfslayer in der Hauptlegende:** vom Kartenfilter ausgeblendet (Spike), und Verlust der Auto-Aktualisierung.
- **Eigene `QgsMapLayerLegend` (Python-Unterklasse) am Vektorkachel-Layer:** wird nicht im Projekt gespeichert,
nach Neuladen wäre die Legende weg.
- **Legende direkt aus dem Signaturenkatalog:** zeigt die Standarddarstellung, nicht die im Projekt angepasste.
+1
View File
@@ -34,3 +34,4 @@ Kurz, mit Grund.
| 0007 | Branchmodell: drei Kanäle, `stable` als stabiler Kanal, Rückführungspflicht | vorgeschlagen | | 0007 | Branchmodell: drei Kanäle, `stable` als stabiler Kanal, Rückführungspflicht | vorgeschlagen |
| 0008 | Werkzeugunterstützung auf Claude Code fokussiert | vorgeschlagen | | 0008 | Werkzeugunterstützung auf Claude Code fokussiert | vorgeschlagen |
| 0009 | Tests sind Teil der Definition of Done, und sie werden für Menschen dokumentiert | vorgeschlagen | | 0009 | Tests sind Teil der Definition of Done, und sie werden für Menschen dokumentiert | vorgeschlagen |
| 0010 | Basemap-Legende als eigenes Legendenelement mit Hilfslayern und Legendenkatalog | vorgeschlagen |
+30 -2
View File
@@ -1,6 +1,6 @@
# Basemap-Legende und Basemap-Stilvorlagen im Druck # Basemap-Legende und Basemap-Stilvorlagen im Druck
Stand 02.10.2026. Konzept zur Freigabe, Ticket **Plugin_SN_Basis #88** („Legende für basemap"). Stand 02.10.2026. Konzept freigegeben und umgesetzt (Branch `feature/88-basemap-legende`, Abschnitt 10), Ticket **Plugin_SN_Basis #88** („Legende für basemap").
Betroffen ist ausschließlich **`sn_basis`** (Druck); `sn_plan41` und `sn_verfahrensgebiet` nutzen den Betroffen ist ausschließlich **`sn_basis`** (Druck); `sn_plan41` und `sn_verfahrensgebiet` nutzen den
Druck-Tab aus `sn_basis` und bekommen die Funktion ohne eigene Änderung. Druck-Tab aus `sn_basis` und bekommen die Funktion ohne eigene Änderung.
@@ -160,7 +160,7 @@ einem erneuten Lauf erhalten; neue oder entfallene Style-IDs werden gemeldet.
|---|---|---| |---|---|---|
| Projektvariable | `basemap_auswertebereich` | `verfahrensgebiet` (Standard) oder `druckbereich` | | Projektvariable | `basemap_auswertebereich` | `verfahrensgebiet` (Standard) oder `druckbereich` |
| Projektvariable | `basemap_legende_erzeugen` | `1`/`0`, Standard `1` | | Projektvariable | `basemap_legende_erzeugen` | `1`/`0`, Standard `1` |
| Projektvariable | `basemap_legende_ausgeblendet` | JSON-Liste der zuletzt abgewählten Eintragsschlüssel (Vorbelegung) | | Projektvariable | `basemap_legende_auswahl` | JSON `{"ausgeblendet": [...], "eingeblendet": [...]}` – zuletzt ab-/angewählte Einträge (Vorbelegung) |
| Layout-Eigenschaft | `sn_basemap_legende`, `sn_basemap_bereich`, `sn_basemap_massstab`, `sn_basemap_eintraege` | Kennzeichen und letzte Einstellungen je Layout | | Layout-Eigenschaft | `sn_basemap_legende`, `sn_basemap_bereich`, `sn_basemap_massstab`, `sn_basemap_eintraege` | Kennzeichen und letzte Einstellungen je Layout |
| QSettings | `sn_basis/basemap_standardstil` | Name der Standard-Stilvorlage | | QSettings | `sn_basis/basemap_standardstil` | Name der Standard-Stilvorlage |
| Öffentliche Funktion | `dialog_wrapper.ask_mehrfachauswahl(...)` | neuer allgemeiner Dialog | | Öffentliche Funktion | `dialog_wrapper.ask_mehrfachauswahl(...)` | neuer allgemeiner Dialog |
@@ -233,3 +233,31 @@ Ein Feature-Branch `feature/88-basemap-legende` in `Plugin_SN_Basis` von `unstab
„kommt vor" unerheblich. „kommt vor" unerheblich.
- **Umfang:** Stilvorlagen sind fachlich eigenständig. Werden sie zu groß für einen PR, als eigenes Ticket - **Umfang:** Stilvorlagen sind fachlich eigenständig. Werden sie zu groß für einen PR, als eigenes Ticket
abtrennen (Schritte 1–6 bleiben #88). abtrennen (Schritte 1–6 bleiben #88).
## 10. Umsetzung und Abweichungen (02.10.2026)
Umgesetzt in `Plugin_SN_Basis`, Branch `feature/88-basemap-legende` (drei Commits). Spike-Ergebnisse und
Begründung der Legendenvariante: ADR 0010.
| Thema | Umsetzung | Abweichung vom Konzept |
|---|---|---|
| Legendendarstellung | Variante (b): eigenes Element „Legende Basemap“, Titel „Kartengrundlage (basemap.de)“ | Spike entschieden |
| Platzierung | unten an der Quellenangabe verankert, wächst nach oben, 1–3 Spalten (nie über den Seitenrand), sonst Hinweis | statt „unter der Hauptlegende“: deren Höhe steht erst nach dem Rendern fest |
| Mehrere Basemap-Layer | ausgewertet wird der erste sichtbare, Hinweis an den Nutzer | statt „je Layer eine Gruppe“ (vereinfacht) |
| Katalog-Generator | `sn_basis/modules/basemap_katalog.py` (nur Standardbibliothek, eigenständig aufrufbar) | statt Skript im Projektwissen – so mit der sn_basis-Suite getestet |
| Auswahl im Projekt | `basemap_legende_auswahl` (ab- und angewählte Einträge) | statt nur `…_ausgeblendet`, damit angewählte „Sonstiges“-Einträge gemerkt werden |
| Module | `basemap_katalog`, `basemap_kacheln`, `basemap_legende`, `basemap_legende_layout`, `basemap_stilvorlagen`, `ui/widgets/basemap_bereich` | Kachelauswertung ausgelagert (Dateigröße) |
| Einklappen | `QToolButton`-Muster wie Atlas-Bereich | `QgsCollapsibleGroupBox` nicht nötig |
| Stilvorlagen | QML (rund 3,7 MB je Vorlage) + JSON mit Legendenauswahl, im QGIS-Profil | wie geplant |
**Katalog:** 434 darstellende Styles in 190 Einträgen; Beschriftungen (`Name_*`, Labeling), Nummernschilder
(`Nummer_*`), Hintergrund, Mittellinien und kleinmaßstäbige Ortspunkte ausgeschlossen. Erster fachlicher
Durchgang der Bezeichnungen ist gemacht; **die fachliche Abnahme steht aus.**
**Bekannte Punkte für die Durchsicht:**
- „Platz“ (Verkehr, Siedlung) hat im Basemap-Stil eine weiße Füllung ohne Rand und ist in der Legende kaum zu sehen.
- „Durchlass, Düker“ und „Baumreihe“ zeigen in der Legende kein sichtbares Symbol (Ursache noch nicht geklärt).
- Gewässerbreiten sind zu „Fließgewässer (Linie)“ zusammengefasst; Symbol ist die 6-m-Linie.
**Nicht geprüft:** QGIS 4 (Qt6) – lokal nicht verfügbar. QGIS 3.44.4 (Qt5) offscreen geprüft: Auswertung,
Dialog (echte Widgets), Legende im §-41-Layout, Stilvorlage speichern/laden am echten Layer, Basemap-Bereich.
+6 -2
View File
@@ -1,7 +1,7 @@
# Status des Projekts # Status des Projekts
Kurz und aktuell. Wird bei jedem Merge und jeder Übergabe gepflegt (Skill `sn-uebergabe`). Älteres wandert nach unten Kurz und aktuell. Wird bei jedem Merge und jeder Übergabe gepflegt (Skill `sn-uebergabe`). Älteres wandert nach unten
in „Erledigt" oder wird gelöscht. **Stand: 04.09.2026.** in „Erledigt" oder wird gelöscht. **Stand: 02.10.2026.**
## Aktuelle Phase ## Aktuelle Phase
@@ -13,7 +13,7 @@ spätere Phase und beginnt erst, wenn die Test-Baseline grün ist und das Projek
| Person / Behörde | Schwerpunkt | Stand | | Person / Behörde | Schwerpunkt | Stand |
|---|---|---| |---|---|---|
| Daniel Helbig (LK Meißen) | Fachfunktionen Verfahrensgebiet, Plan41, VLN-API | aktiv | | Daniel Helbig (LK Meißen) | Fachfunktionen Verfahrensgebiet, Plan41, VLN-API; Basemap-Legende im Druck (Basis #88, PR vorbereitet) | aktiv |
| Michael Otto (LK Mittelsachsen) | Release-Infrastruktur, Linkliste, zuletzt Verfahrensgebiet (Shape-Laden, Beteiligung) | aktiv | | Michael Otto (LK Mittelsachsen) | Release-Infrastruktur, Linkliste, zuletzt Verfahrensgebiet (Shape-Laden, Beteiligung) | aktiv |
| 22munzke | Druck/Layout-Anforderungen (Issues Basis #37–#44) | Issues | | 22munzke | Druck/Layout-Anforderungen (Issues Basis #37–#44) | Issues |
| weitere Behörden | Einstieg über dieses Repository | ab 09/2026 | | weitere Behörden | Einstieg über dieses Repository | ab 09/2026 |
@@ -63,6 +63,10 @@ spätere Phase und beginnt erst, wenn die Test-Baseline grün ist und das Projek
(fest verdrahteter NAS-Namespace `gid/6.0` in `grenzpunkt_alkis.py`, abgeflachte WFS-Feldnamen in (fest verdrahteter NAS-Namespace `gid/6.0` in `grenzpunkt_alkis.py`, abgeflachte WFS-Feldnamen in
`sql=`-Filtern). Vergleich 6↔7 und Anpassungsliste: `docs/konzept/09_alkis7_umstellung.md`. `sql=`-Filtern). Vergleich 6↔7 und Anpassungsliste: `docs/konzept/09_alkis7_umstellung.md`.
Nächster Schritt: Gitea-Issue in `Plugin_SN_Verfahrensgebiet`. Nächster Schritt: Gitea-Issue in `Plugin_SN_Verfahrensgebiet`.
15. **Basemap-Legende im Druck (Basis #88).** Umgesetzt auf `Plugin_SN_Basis:feature/88-basemap-legende`
(Konzept `docs/konzept/10_basemap_legende.md`, ADR 0010), PR steht aus. Offen: **fachliche Abnahme des
Legendenkatalogs** `sn_basis/data/basemap_legende.json` (Bezeichnungen, Gruppen, Zusammenfassungen; drei
Einträge ohne gut sichtbares Symbol, siehe Konzept Abschnitt 10) und Test unter QGIS 4.
**Hinweis zum lokalen Stand (04.09.2026):** Die Klone `Plugin_SN_Verfahrensgebiet` (4 Commits: PR #43, PR #45) **Hinweis zum lokalen Stand (04.09.2026):** Die Klone `Plugin_SN_Verfahrensgebiet` (4 Commits: PR #43, PR #45)
und `Repository` (Release 26.8.4-unstable) liegen hinter `origin`. Aktualisieren mit `python scripts/hallo.py`. und `Repository` (Release 26.8.4-unstable) liegen hinter `origin`. Aktualisieren mit `python scripts/hallo.py`.
File diff suppressed because one or more lines are too long
+741 -236
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -5,6 +5,13 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik
## Umgebung und Werkzeuge ## Umgebung und Werkzeuge
- **`qt_wrapper` meldet unter QGIS 3.44 `QT_VERSION == 6`, obwohl Qt5 läuft.** Der „PyQt6“-Zweig importiert
`qgis.PyQt`, das unter QGIS 3 PyQt5 ist; die Qt6-Schreibweise der Enums (`Qt.ItemFlag.ItemIsUserCheckable`)
funktioniert mit PyQt 5.15 trotzdem. `QT_VERSION` also nicht als Qt-Hauptversion verstehen, nur als
„echtes Qt (≠ 0)“. (02.10.2026, QGIS 3.44.4 geprüft, Basis #88)
- **Lücken im Mock-Modus:** `QgsGeometry` hat im Mock kein `fromRect`, `QgsLayoutPoint`/`QgsLayoutSize` haben
`x`/`width` als Attribute statt als Methoden; `QgsRectangle` hatte bis Basis #88 gar keinen Mock. Tests patchen
solche Stellen gezielt, statt die Produktionsfunktion umzubauen. (02.10.2026)
- **QGIS 3.40 ist seit Januar 2026 End of Life.** Aktueller LTR ist 3.44, Stable 4.2 (wird 25.09.2026 LTR). `workspace.toml` und alle `metadata.template` stehen noch auf `3.40` – offene Produktentscheidung, siehe `docs/konzept/07_qgis_tests_plan.md` Abschnitt 2. (04.09.2026, QGIS-Release-Fahrplan) - **QGIS 3.40 ist seit Januar 2026 End of Life.** Aktueller LTR ist 3.44, Stable 4.2 (wird 25.09.2026 LTR). `workspace.toml` und alle `metadata.template` stehen noch auf `3.40` – offene Produktentscheidung, siehe `docs/konzept/07_qgis_tests_plan.md` Abschnitt 2. (04.09.2026, QGIS-Release-Fahrplan)
- **Zwei Qt-Welten.** QGIS 3.40/3.44 laufen mit Qt5/PyQt5, QGIS 4 mit Qt6/PyQt6. `qt_wrapper` lädt PyQt6 zuerst, dann PyQt5, sonst Mock. Enum-Zugriffe unterscheiden sich (`Qt.AlignmentFlag.AlignTop` statt `Qt.AlignTop`), deshalb nur Wrapper-Aliase verwenden. (09/2026, Code) - **Zwei Qt-Welten.** QGIS 3.40/3.44 laufen mit Qt5/PyQt5, QGIS 4 mit Qt6/PyQt6. `qt_wrapper` lädt PyQt6 zuerst, dann PyQt5, sonst Mock. Enum-Zugriffe unterscheiden sich (`Qt.AlignmentFlag.AlignTop` statt `Qt.AlignTop`), deshalb nur Wrapper-Aliase verwenden. (09/2026, Code)
- **Mock-Modus ist kein QGIS.** `python scripts/run_tests.py` läuft in Sekunden, prüft aber nur Logik gegen Mock-Klassen. Geometrieoperationen, WFS, Layouts und Signale müssen in QGIS geprüft werden (`tests/test_qgis.bat` unter Windows). Manuelle QGIS-Tests im PR dokumentieren. (09/2026) - **Mock-Modus ist kein QGIS.** `python scripts/run_tests.py` läuft in Sekunden, prüft aber nur Logik gegen Mock-Klassen. Geometrieoperationen, WFS, Layouts und Signale müssen in QGIS geprüft werden (`tests/test_qgis.bat` unter Windows). Manuelle QGIS-Tests im PR dokumentieren. (09/2026)
@@ -19,6 +26,12 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik
## QGIS-Tests (geplant, siehe `docs/konzept/07_qgis_tests_plan.md`) ## QGIS-Tests (geplant, siehe `docs/konzept/07_qgis_tests_plan.md`)
- **Spike/Abnahme ohne QGIS-Oberfläche:** Skript mit `QT_QPA_PLATFORM=offscreen` über `python-qgis.bat` starten,
das Plugin-Repo per Junction (`mklink /J <tmp>\sn_basis <Plugin_SN_Basis>`) unter dem Paketnamen in `sys.path`
legen. Für Widgets und Dialoge `QgsApplication([], True)` (mit GUI) – ohne GUI stürzt z. B.
`QgsSymbolLayerUtils.symbolPreviewIcon` ab. Modale Dialoge: `exec_dialog` patchen und im Ersatz die Widgets
prüfen. Layout-Ergebnis mit `QgsLayoutExporter.renderRegionToImage` als PNG ansehen; ohne installierte Schriften
erscheinen Texte als Kästchen. `exportToImage` liefert bei Erfolg `0`. (02.10.2026, Basis #88)
- **`QT_QPA_PLATFORM=offscreen` muss gesetzt sein, bevor die `QgsApplication` erzeugt wird.** Danach wirkt es nicht mehr. (04.09.2026, Recherche) - **`QT_QPA_PLATFORM=offscreen` muss gesetzt sein, bevor die `QgsApplication` erzeugt wird.** Danach wirkt es nicht mehr. (04.09.2026, Recherche)
- **`qgis.utils.iface` ist nach `start_app()` `None`.** Entweder `pytest-qgis` verwenden (patcht automatisch) oder selbst `qgis.utils.iface = get_iface()` setzen – und keine Module zu früh importieren. (04.09.2026) - **`qgis.utils.iface` ist nach `start_app()` `None`.** Entweder `pytest-qgis` verwenden (patcht automatisch) oder selbst `qgis.utils.iface = get_iface()` setzen – und keine Module zu früh importieren. (04.09.2026)
- **`QgsProject.instance()` ist global.** Ohne Aufräumen zwischen den Tests beeinflussen sie sich gegenseitig. (04.09.2026) - **`QgsProject.instance()` ist global.** Ohne Aufräumen zwischen den Tests beeinflussen sie sich gegenseitig. (04.09.2026)
@@ -40,6 +53,15 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik
## Daten und Dienste ## Daten und Dienste
- **basemap.de-Vektorkacheln (Basis #88):** QGIS zeigt für Vektorkachel-Layer keine Legende. Objekte im Gebiet
liefert `QgsVectorTileLayer.selectByGeometry()` (lädt die Kacheln selbst; `getRawTile` ist in Python gesperrt).
Kacheln enden bei Zoom 15. Der Renderer prüft Styles mit `scaleToZoomLevel(maßstab, False)` und setzt
`@vector_tile_zoom = scaleToZoom(maßstab)` – diese Variable fehlt in der Legende, zoomabhängige Strichmuster
sind dort sonst unsichtbar. Beim Import der Style-JSON werden Beschriftungen zu Labeling (Hausnummern =
Labeling-Style `Hauskoordinate`), `Nummer_*`-Schilder zu Renderer-Symbolen, `fill-extrusion` (3D) entfällt.
Der Legendenkatalog `sn_basis/data/basemap_legende.json` gehört zur Style-Version `bm_web_col` v2; nach einer
neuen Version `python modules/basemap_katalog.py` laufen lassen und Meldungen prüfen. Eine QML-Stilvorlage des
Layers ist rund 3,7 MB groß. (02.10.2026)
- **WFS-Beschränkungen der sächsischen ALKIS-Dienste:** POST/GML-Filter werden blockiert, URL-Länge ist begrenzt, serverseitige Feature-Limits. Deshalb arbeiten Flurstücks- und Grenzpunktabrufe mit BBOX, gemarkungsweisen Batches und Nutzerdialogen. Nicht „vereinfachen“, ohne das gegen den Dienst zu testen. Vollständig mit erprobten Anfragen: `docs/wissen/wfs_dienste.md`, Objektarten/Attribute: `docs/wissen/alkis_objektarten.md`. (Verfahrensgebiet, Code) - **WFS-Beschränkungen der sächsischen ALKIS-Dienste:** POST/GML-Filter werden blockiert, URL-Länge ist begrenzt, serverseitige Feature-Limits. Deshalb arbeiten Flurstücks- und Grenzpunktabrufe mit BBOX, gemarkungsweisen Batches und Nutzerdialogen. Nicht „vereinfachen“, ohne das gegen den Dienst zu testen. Vollständig mit erprobten Anfragen: `docs/wissen/wfs_dienste.md`, Objektarten/Attribute: `docs/wissen/alkis_objektarten.md`. (Verfahrensgebiet, Code)
- **Zwei Linklisten.** Zentrale `Linkliste/linkliste.csv` (Spalten `id;gruppe;ebene;inhalt;url;provider;stil`) und die vom Plugin genutzte `Plugin_SN_Plan41/assets/Linkliste.xlsx` (`ident, gruppe, kartenebene, inhalt, link, provider, stildatei`) sind nicht verbunden. Änderungen an Diensten in **beiden** nachziehen, bis die Zusammenführung entschieden ist. (09/2026) - **Zwei Linklisten.** Zentrale `Linkliste/linkliste.csv` (Spalten `id;gruppe;ebene;inhalt;url;provider;stil`) und die vom Plugin genutzte `Plugin_SN_Plan41/assets/Linkliste.xlsx` (`ident, gruppe, kartenebene, inhalt, link, provider, stildatei`) sind nicht verbunden. Änderungen an Diensten in **beiden** nachziehen, bis die Zusammenführung entschieden ist. (09/2026)
- **ArcGIS-REST-Layer nicht nach Nummer deuten.** Gleiche Layer-URLs werden in der Linkliste und im Alternativansatz unterschiedlich benannt; Bedeutung und Geometrie immer gegen die Dienstmetadaten prüfen. (Vergleich Alternativansatz 09/2026) - **ArcGIS-REST-Layer nicht nach Nummer deuten.** Gleiche Layer-URLs werden in der Linkliste und im Alternativansatz unterschiedlich benannt; Bedeutung und Geometrie immer gegen die Dienstmetadaten prüfen. (Vergleich Alternativansatz 09/2026)
@@ -50,6 +72,12 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik
## Code-Konventionen, die man kennen muss ## Code-Konventionen, die man kennen muss
- **Legendenelemente im Layout (QGIS 3.44):** `QgsLayoutItemLegend.adjustBoxSize()` wirkt erst, nachdem das
Element einmal gezeichnet wurde – beim Anlegen per Code ändert es nichts. Platzbedarf vorher über
`QgsLegendRenderer(legende.model(), einstellungen).minimumSize(QgsLayoutUtils.createRenderContextForLayout(layout, None))`
berechnen; für mehrere Spalten zusätzlich `setSplitLayer(True)`, sonst bleibt ein Layer in einer Spalte.
Eine Legende mit `setLegendFilterByMapEnabled(True)` blendet Layer aus, die nicht in der Karte liegen, und ihre
Höhe steht erst nach dem Rendern fest. (02.10.2026, Spike Basis #88)
- **Projektvariablen-Schlüssel ohne `sn_` übergeben.** `set_variable("sn_abdeckungslayer", ...)` erzeugt `sn_sn_abdeckungslayer` (Altlast in Druckmodulen; funktioniert nur, weil Lesen und Schreiben denselben Fehler machen). - **Projektvariablen-Schlüssel ohne `sn_` übergeben.** `set_variable("sn_abdeckungslayer", ...)` erzeugt `sn_sn_abdeckungslayer` (Altlast in Druckmodulen; funktioniert nur, weil Lesen und Schreiben denselben Fehler machen).
- **Zwei Schreibweisen für den Verfahrensgebiet-Layer:** `verfahrensgebiet_layer` (Manager, Fachplugins) und `verfahrensgebietslayer` (`print_logic.py`). Bis zur Bereinigung nicht auf Gleichheit vertrauen. - **Zwei Schreibweisen für den Verfahrensgebiet-Layer:** `verfahrensgebiet_layer` (Manager, Fachplugins) und `verfahrensgebietslayer` (`print_logic.py`). Bis zur Bereinigung nicht auf Gleichheit vertrauen.
- **Layer werden über Anzeigenamen gefunden** (`BROR_LAYER_NAME` usw.). Umbenennungen in der UI brechen Abläufe. - **Layer werden über Anzeigenamen gefunden** (`BROR_LAYER_NAME` usw.). Umbenennungen in der UI brechen Abläufe.