Files
Projektwissen/docs/architektur/schnittstellen.md
T
DanielandClaude Opus 5.5 0a81fd75fa Legenden-Mindestbreite (Basis #96) dokumentiert
- schnittstellen.md Abschnitt 12: Mindestbreite 180 mm (= Kartenschild), Layout-Eigenschaft
  sn_legende_mindestbreite, Listener in sn_basis.main
- fallstricke.md: setMinimumSize wird nicht im Projekt gespeichert
- modulkarte.md, STATUS.md, Testkatalog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:26:51 +02:00

12 KiB
Raw Blame History

Schnittstellen und Verträge

Lebende Referenz der impliziten Verträge zwischen den Komponenten. Wer eine dieser Stellen ändert, ändert eine Schnittstelle und muss alle Nutzer mit anpassen. Stand 02.09.2026.

1. Plugin-Lebenszyklus: sn_basis ↔ Fachplugin

sequenceDiagram
    participant QGIS
    participant B as sn_basis.BasisPlugin
    participant F as Fachplugin (z. B. Verfahrensgebiet)
    QGIS->>B: classFactory(iface) / initGui()
    B->>B: Navigation().init_ui()  (Menü + Toolbar "LNO Sachsen")
    B->>B: setup_verfahrensgebiet_listener()
    loop für jedes Plugin in qgis.utils.plugins mit Namen sn_* (außer sn_basis)
        B->>F: initGui()
        F->>B: basis.ui.add_action(name, run, tooltip, priority)
    end
    B->>B: finalize_menu_and_toolbar()  (sortiert nach priority)
    QGIS->>F: Nutzer klickt Action → run()
    F->>F: DockWidget(...) mit objectName "sn_dock_<name>"
    F->>B: DockManager.show(dock)  (entfernt andere sn_dock_*)
    F->>B: basis.ui.set_active_plugin(action)
    QGIS->>F: unload() → removeDockWidget, basis.ui.remove_action(action)

Vertrag für ein Fachplugin (Muster: Plugin_SN_Widmung/main.py):

  • Paketname beginnt mit sn_ (nur dann ruft sn_basis das initGui() nach).
  • initGui() holt plugins.get("sn_basis"); ohne basis.ui nichts tun (QGIS lädt Plugins in nicht garantierter Reihenfolge; sn_basis ruft später nach).
  • add_action(text, callback, tooltip="", priority=100) → QAction (checkable, in exklusiver Gruppe). Vergebene Prioritäten: Verfahrensgebiet 10, Plan41 20, Widmung 30.
  • Dock-Objektname sn_dock_<pluginname klein>; nur ein sn_dock_* ist gleichzeitig sichtbar.
  • unload() muss Dock entfernen und Action abmelden, sonst bleiben Reste in Menü/Toolbar.

2. DockWidget und Tabs

  • Fachplugin-Dock erbt von sn_basis.ui.base_dockwidget.BaseDockWidget; Klassenattribut tabs = [TabKlasse, ...].
  • Tab-Klasse: QWidget-Unterklasse mit Klassenattribut tab_title; Konstruktor Tab(parent). Optional set_services(pruefmanager=..., data_grabber=...) (wird von Fachplugin-Docks aufgerufen) und cleanup() (wird beim Schließen des Docks aufgerufen – Signale trennen!).
  • Gemeinsame Tabs aus sn_basis: ui.tabs.print_tab.PrintTab (Druck), ui.tabs.settings_tab.SettingsTab (Projekteigenschaften).
  • dock.action = Referenz auf die Toolbar-Action; BaseDockWidget.closeEvent setzt sie zurück.

3. Projektvariablen (gemeinsamer Zustand im QGIS-Projekt)

Zugriff nur über sn_basis.functions.variable_wrapper.get_variable(key, scope="project") / set_variable(...). Der Wrapper setzt das Präfix sn_ selbst; Schlüssel ohne Präfix übergeben.

Schlüssel (ohne sn_) Gesetzt von Gelesen von Bedeutung
amt, behoerde, landkreis_user, sachgebiet SettingsTab Druck (Schriftfeld) benutzerspezifische Angaben
bezeichnung, name, verfahrensnummer, gemeinden, landkreise_proj, landkreis_kennzahl SettingsTab Verfahrensgebiet (ALKIS-Suche), Druck Verfahrensangaben; verfahrensnummer = VKZ
verfahrensgebiet_layer verfahrensgebiet_manager, WorkingTab alle Fachplugins, Druck Layer-ID des aktuellen Verfahrensgebiets
verfahrens_db, tab_a_verfahrens_db Plan41 Tab A Plan41 Pfad des Verfahrens-GeoPackages
linkliste, tab_a_linkliste Plan41 Tab A Plan41 Pfad der Linkliste (XLSX)
tab_a_layer_id, Raumfilter Plan41 Tab A Plan41 Raumfilter-Einstellung
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
verfahrensgebietslayer ⚠ print_logic.py print_logic.py Zweite Schreibweise neben verfahrensgebiet_layer – vermutlich Altlast, beim Refactoring zusammenführen

Benutzer-/Maschinendaten außerhalb des Projekts: QSettings in sn_plan41 (mail, api_key der VLN-API).

4. Prüf- und Entscheidungsmodell

  • pruef_ergebnis(ok: bool, meldung, aktion: PruefAktion, kontext) ist das einheitliche Rückgabeobjekt aller Prüfer (Dateipruefer, Layerpruefer, Linkpruefer, Stilpruefer, Datenbankpruefer) und vieler Fachfunktionen.
  • PruefAktion ist ein Literal mit Codes wie datei_nicht_gefunden, layer_leer, datei_existiert_ueberschreiben, verfahrensgebiet_ergaenzen. Neue Codes dort ergänzen, nicht als freie Strings verwenden.
  • Pruefmanager(ui_modus="qgis"): verarbeite(ergebnis) entscheidet je Aktion (Dialog, Meldung, Standardwert). Mit ui_modus != "qgis" (Tests) werden Standardantworten ohne Dialog geliefert.
  • Fachplugins erhalten eine Pruefmanager- und eine DataGrabber-Instanz aus main.py (Konstruktor) und reichen sie an Tabs weiter.

5. Datenpipeline Plan41 (Fachdaten aus Linkliste)

flowchart LR
    XLSX[assets/Linkliste.xlsx] --> DG[DataGrabber.run]
    DG -->|Excel| LA[sn_plan41.Listenauswerter.validate_rows]
    LA --> DA[Datenabruf.datenabruf]
    VG[(Verfahrensgebiet / Puffer)] --> DA
    DA --> LL[LayerLoader: create_layer, filter_by_layer/extent, apply_style]
    LL --> DS[Datenschreiber.schreibe_Daten → GeoPackage]
    DS --> P[Datenschreiber.lade_Layer + schreibe_log HTML]
  • Erwartetes Spaltenschema der XLSX: ident, gruppe, kartenebene, inhalt, link, provider, stildatei. provider ∈ WFS, REST (ArcGIS), OGR.
  • Das zentrale Repository Linkliste (linkliste.csv: id;gruppe;ebene;inhalt;url;provider;stil) ist nicht angebunden → zwei Wahrheiten, siehe fallstricke.md.
  • Die einzige Rückwärtsabhängigkeit sn_basis → sn_plan41 ist der Lazy-Import von Listenauswerter in DataGrabber._process_excel_source (seit PR #57 abgesichert, damit sn_verfahrensgebiet ohne sn_plan41 lädt).

6. Verfahrensgebiet-Abläufe (Fachplugin)

  • Zentrale Kennung des Verfahrens: Projektvariable verfahrensnummer (VKZ) und/oder landkreis_kennzahl.
  • ALKIS-Quellen: vereinfachtes ALKIS (.../public_alkis/vereinf/wfs) für Gemarkungen/BROR, ALKIS-SF (.../public_alkis/sf/wfs) für Flurstücke und PunktortTA.
  • Layer-Namenskonventionen (String-Konstanten in den Modulen): BROR_LAYER_NAME, FLST_LAYER_NAME, JOINED_LAYER_NAME, „Verfahrensgebiet“, „Neuordnungsgebiet (NoBIS)“, „Neuvermessungsgebiet (NoBIS)“. Andere Module finden Layer über den Namen – Umbenennungen sind Schnittstellenänderungen.
  • Ablaufdokumentation mit Diagrammen: Plugin_SN_Verfahrensgebiet/docs/.

7. VLN-Karten-API (Plan41)

  • Basis-URL https://api.flurneuordnung-sachsen.de/v2; Login per E-Mail/Passwort, danach Header X-API-Key.
  • Laden: Verfahren (VKZ) → Plan41-GeoJSON → Memory-Layer je Geometriefamilie (vln_layer_manager). Upload: alle Teillayer → eine FeatureCollection, PUT ersetzt den Serverbestand der VKZ vollständig.
  • Arbeits-CRS EPSG:25833.

8. Mock-Modus (Tests ohne QGIS)

  • Jeder Wrapper versucht den Import von qgis/PyQt6/PyQt5; schlägt das fehl, definiert er _Mock*-Klassen. Kennzeichen: qgiscore_wrapper.QGIS_AVAILABLE, qt_wrapper.QT_VERSION == 0.
  • Tests importieren Module über den Paketnamen (from sn_basis...), deshalb müssen die Repos unter ihrem Paketnamen erreichbar sein → scripts/run_tests.py erledigt das über ein temporäres Verzeichnis.
  • Tests, die echtes Layer- oder Netzwerkverhalten brauchen, laufen nur in QGIS (tests/test_qgis.bat, Windows).

9. Release-Vertrag

flowchart LR
    T["Tag v26.9.1-u auf Plugin-Repo"] --> W1["Plugin-Workflow release.yaml"]
    W1 -->|"workflow_dispatch + Payload"| W2["Repository, Branch hidden/workflows"]
    W2 --> C["checkout Branch = Kanal<br/>(unstable / testing / stable)"]
    C --> M["metadata.template → metadata.txt:<br/>version, experimental, repository-URL"]
    M --> Z["rsync ohne .git .gitea .gitignore __pycache__ → ZIP_FOLDER.zip"]
    Z --> R["Gitea-Release + Asset"]
    R --> X["plugins-&lt;kanal&gt;.xml aktualisieren"]
  • Tagformat v<JJ.M.N>-u (unstable), -t (testing), ohne Suffix (stable). Version = Jahr.Monat.Laufnummer.
  • Gebaut wird der Kopf des Kanal-Branches, nicht der getaggte Commit. Tag also nur auf dem aktuellen Kopf von unstable bzw. testing setzen.
  • Paketname im ZIP kommt aus der Gitea-Repo-Variable ZIP_FOLDER (z. B. sn_basis).
  • metadata.txt im Repo ist nur ein Abbild des letzten Builds; gepflegt wird .gitea/templates/metadata.template.
  • Release-Body = Block über dem ersten --- in changelog.txt.
  • Neue Workspace-Dateien (AGENTS.md, CLAUDE.md, .agents/, .claude/, docs/ der Repos) landen derzeit im ZIP → Excludes im zentralen Workflow ergänzen (offener Punkt in STATUS.md).

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.

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.

12. Kartenrand im Standardlayout (Basis #93)

print_kartenrand.ordne_kartenrand_an(layout) läuft am Ende von PrintLayout.create_single_page_layout und findet die Elemente über ihre IDs: Hauptkarte, Legende, Legende Basemap, Quellenangabe, Kartenschild, Kartenschild_Linie_1 … _6 und die Schriftfeld-Beschriftungen (Herausgeber … Aktenzeichen, Liste UNTERE_ELEMENTE). Wer im Layoutaufbau eine dieser IDs ändert oder ein neues Element unter die Legenden setzt, muss die Liste anpassen. Die Seitengröße aus print_logic ist danach nur noch eine Mindestgröße: Breite und Höhe können wachsen, die Karte bleibt bei 10 mm / 10 mm mit unveränderter Größe.

Legenden sind mindestens so breit wie das Kartenschild (180 mm, MIN_SPALTENBREITE_MM; die 210 mm in print_logic.STANDARD_LAYOUT_FIXBREITE_MM sind der gesamte Kartenrand mit Abständen). Weil QGIS die Mindestgröße nicht speichert, trägt das Layout die Eigenschaft sn_legende_mindestbreite; sn_basis.main verbindet beim Start setup_mindestbreiten_listener() (readProject) und trennt ihn in unload() (Basis #96).