Compare commits

..
Author SHA1 Message Date
Daniel 5004a8d5bf Merge pull request 'Zoom auf das Verfahrensgebiet ins Projekt-KBS umrechnen (Basis #99)' (#55) from bugfix/99-zoom-kbs into unstable
Release Plugin / release (push) Successful in 4s
Reviewed-on: #55
2026-10-05 20:24:29 +00:00
DanielandClaude Opus 5.5 3bde666d96 Zoom auf das Verfahrensgebiet ins Projekt-KBS umrechnen (Basis #99)
Nach dem Laden aus ALKIS oder Shape wurde mit layer.extent() im
Layer-KBS gezoomt; bei anderem Projekt-KBS sprang die Karte an die
falsche Stelle. Beide Stellen nutzen jetzt zoom_auf_layer aus sn_basis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:15:34 +02:00
Daniel e0660aba86 Merge pull request 'Grenzpunkte aus ALKIS werden im ALKIS-Stil angezeigt (#53)' (#54) from feature/53-grenzpunkt-stil-ergaenzen into unstable
Reviewed-on: #54
2026-09-17 14:31:55 +00:00
DanielandClaude Sonnet 5 84e03d208c Grenzpunkte aus ALKIS werden im ALKIS-Stil angezeigt (#53)
grenzpunkte_gemarkungsweise() ruft nach project.addMapLayer(joined_layer)
jetzt apply_style(joined_layer, "alkis_grenzpunkte.qml") auf - analog zum
bereits etablierten Muster bei Flurstücken/Verfahrensgebiet/BROR in
working_tab.py bzw. verfahrensgebiet_alkis_komplett.py.

Neuer Test test_stil_wird_nach_dem_laden_auf_den_ergebnis_layer_angewendet
mockt die komplette interne WFS-Kette (Extents, Hits, GML-Parsing,
Grenzpunkt-Batch, Join) und prüft, dass apply_style() mit dem Ergebnis-
Layer und dem richtigen Dateinamen aufgerufen wird.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 16:29:19 +02:00
Daniel 341b9ab1c4 Merge pull request 'Bugfix/16 flurstueck alkis testabgleich' (#51) from bugfix/16-flurstueck-alkis-testabgleich into unstable
Reviewed-on: #51
2026-09-17 13:57:27 +00:00
Daniel f7661a5d25 Merge branch 'unstable' into bugfix/16-flurstueck-alkis-testabgleich 2026-09-17 13:57:13 +00:00
Daniel 1aea40d028 Merge pull request 'Stildatei gemarkungen_beschriftet.qml konsequent kleingeschrieben (#50)' (#52) from bugfix/50-qml-dateiname-gross-klein into unstable
Reviewed-on: #52
2026-09-17 13:56:45 +00:00
DanielandClaude Sonnet 5 45a8ce37de Stildatei gemarkungen_beschriftet.qml konsequent kleingeschrieben (#50)
apply_style(gem_layer, "gemarkungen_beschriftet.qml") in working_tab.py
ruft die Datei klein geschrieben auf, im Repo lag sie als
Gemarkungen_beschriftet.qml. Auf Windows (case-insensitive) unsichtbar,
auf einem case-sensitiven Dateisystem (Linux/OSGeo4W) hätte das die
Datei nie gefunden - zusätzlich zum Pfadfehler in get_plugin_root()
(siehe Kommentar zu #50).

Umbenennung über zwei git mv-Schritte, da Windows/core.ignorecase eine
reine Case-Änderung sonst nicht als Rename erkennt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 15:49:58 +02:00
DanielandClaude Sonnet 5 951b9075fe changelog.txt: Testabgleich TestFlurstueckAlkis ergänzt (#16)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 12:12:38 +02:00
DanielandClaude Sonnet 5 c260ccc2d6 TestFlurstueckAlkis an aktuelle Ladelogik angeglichen, bbox-Doku korrigiert (#16)
Test-Audit (Projektwissen #16) deckte auf: tests/test_flurstueck_alkis.py
wurde im selben Commit (f8bdcde) angelegt, in dem flurstueck_alkis() von
einer einzelnen QgsVectorLayer-Abfrage auf lade_flurstuecke_per_gemarkung()
(eine WFS-Anfrage je Gemarkung, Merge in Memory-Layer) umgestellt wurde -
die Tests mockten aber weiterhin die alte Ein-Query-Variante und liefen
seither ins Leere (8 von 12 Testfaellen rot in der Baseline).

- Tests mocken jetzt lade_flurstuecke_per_gemarkung()/_check_existing_
  flurstueck_layer() statt QgsVectorLayer direkt, passend zum seit #47
  bzw. 944fc82 aktuellen Drei-Knopf-Dialog (Ueberschreiben/Anhaengen/
  Abbrechen) und dem Pro-Gemarkung-Ladevorgang.
- test_existing_layer_keep_no -> test_existing_layer_abbrechen_returns_none:
  LoadStatus.KEEP wird von _check_existing_flurstueck_layer() nicht mehr
  zurueckgegeben, der verbleibende Nicht-Aendern-Pfad ist "Abbrechen".
- test_bbox_embedded_in_wfs_uri entfernt: bbox wird von keinem Aufrufer
  mehr uebergeben und im Code nicht mehr ausgewertet; die vermeintliche
  URI-Einbettung gab es schon lange nicht mehr.
- Modul-Docstring in flurstueck_alkis.py korrigiert: bbox ist reine
  Rueckwaertskompatibilitaet in der Signatur (Flurstueck-Geometrie kommt
  aus dem WFS je Gemarkung, keine BBox-Filterung), Memory-Layer statt
  Direktladung dokumentiert.
- Alle Docstrings von TestFlurstueckAlkis auf "Prueft, dass ..." gebracht
  (Vorgabe docs/leitfaden/06_tests.md) und inhaltlich an die tatsaechlich
  getestete Logik angepasst; Datei-Docstring ebenfalls korrigiert.
- Totes _DummyLayer.extent() entfernt (wird von keiner Aufrufkette mehr
  erreicht).

Kein Verhaltensunterschied in flurstueck_alkis() selbst - nur Tests und
Dokumentation an eine bereits bestehende Implementierung angeglichen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 12:11:13 +02:00
Daniel 49d30eea22 Merge pull request 'Drei getrennte Flurstuecke-Ladelogiken auf eine konsolidiert (#47)' (#49) from bugfix/47-flurstuecke-ladelogik-konsolidieren into unstable
Reviewed-on: #49
2026-09-15 08:31:25 +00:00
DanielandClaude Sonnet 5 0ac0757e42 Drei getrennte Flurstuecke-Ladelogiken auf eine konsolidiert (#47)
handle_flurst_aus_gemarkungen() hatte eine eigene Kopie des
"Layer bereits vorhanden"-Dialogs und rief lade_flurstuecke_per_gemarkung
direkt auf, statt die bereits vorhandene, getestete Logik in
flurstueck_alkis() wiederzuverwenden. Grund war, dass ohne_dialog=True
zwei unabhaengige Dinge zugleich abschaltete: die Schluessel-Abfrage per
ask_text UND den Existenz-Dialog samt Anhaengen-Dedup.

Neuer Parameter ohne_schluessel_abfrage trennt beides: er ueberspringt
nur die Schluessel-Abfrage (Schluessel kommen bereits aus der
Gemarkungen-Layer-Auswahl), der Existenz-Dialog mit Dedup bleibt aktiv.
ohne_dialog bleibt fuer die automatischen Pipelines unveraendert.

handle_flurst_alkis() und handle_flurst_aus_gemarkungen() teilen sich
jetzt zusaetzlich die Nachbehandlung des Ladeergebnisses
(_uebernimm_flurstuecke_ergebnis). lade_flurstuecke_per_gemarkung() wird
wieder nur noch von einer Stelle aus aufgerufen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 10:26:25 +02:00
Daniel e267d51607 Merge pull request 'Bugfix/28 doppelte flurstuecke' (#48) from bugfix/28-doppelte-flurstuecke into unstable
Reviewed-on: #48
2026-09-15 08:06:56 +00:00
DanielandClaude Sonnet 5 003ad1957b Zweite Anhaengen-Pipeline (Gemarkungen-Button) auf Dedup umgestellt (#28)
handle_flurst_aus_gemarkungen in working_tab.py hatte eine eigene,
von flurstueck_alkis() unabhaengige Anhaengen-Logik, die
lade_flurstuecke_per_gemarkung direkt aufrief und ungefiltert
anhaengte. Nutzt jetzt dieselbe flurstueckskennzeichen-Pruefung wie
der Fix im vorigen Commit; dafuer vorhandene_kennzeichen() in
flurstueck_alkis.py oeffentlich gemacht.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 09:17:30 +02:00
DanielandClaude Sonnet 5 821bb0640e Doppeltes Laden von Flurstuecken beim Anhaengen verhindert (#28)
Beim Anhaengen an einen bestehenden Flurstueck-Layer wurden alle neu
geladenen Objekte unabhaengig davon hinzugefuegt, ob sie ueber
flurstueckskennzeichen bereits im Layer vorhanden waren. Jetzt werden
nur noch neue Kennzeichen angehaengt; fehlt das Feld, bleibt das
bisherige Verhalten als Rueckfall erhalten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 08:48:14 +02:00
Daniel 04f2a49c13 Merge pull request 'Fehlenden Umlaut in Docstring behoben (Plugin_SN_Basis #42)' (#46) from bugfix/42-umlaute-sonderzeichen into unstable
Reviewed-on: #46
2026-09-15 05:59:46 +00:00
DanielandClaude Sonnet 5 eac3b44470 Fehlenden Umlaut in Docstring behoben (Plugin_SN_Basis #42)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MneDjzrjmbpQt8udFYxN9e
2026-09-14 12:19:57 +02:00
10 changed files with 1528 additions and 184 deletions
+5
View File
@@ -1,3 +1,8 @@
- Bugfix: Nach dem Laden des Verfahrensgebiets (ALKIS oder Shape) sprang die Karte an die falsche Stelle, wenn ein anderes Projekt-KBS als EPSG:25833 eingestellt war. Der Zoom rechnet die Ausdehnung jetzt in das KBS der Kartenansicht um (`zoom_auf_layer` aus sn_basis, setzt sn_basis mit Basis #99 voraus) (Basis #99)
- Neu: Grenzpunkte aus ALKIS werden nach dem Laden im ALKIS-Stil dargestellt (styles/alkis_grenzpunkte.qml), statt ungestylt zu bleiben (#53)
- Intern: Tests von TestFlurstueckAlkis an die seit längerem geänderte Ladelogik angeglichen (Mocks zeigten noch auf die alte Ein-Query-/Zwei-Knopf-Variante), bbox-Dokumentation in flurstueck_alkis.py korrigiert (bbox wird nicht mehr ausgewertet) – keine Verhaltensänderung (Test-Audit #16)
- Intern: Der Button "aus gewählten Gemarkungen laden" nutzt jetzt dieselbe Ladelogik wie "Flurstücke laden" (flurstueck_alkis) statt einer eigenen, parallelen Implementierung – konsolidiert drei bisher getrennte Code-Pfade zum Flurstücke-Laden auf einen (#47)
- Bugfix: Flurstücke aus ALKIS an bestehenden Layer anhängen überspringt jetzt bereits vorhandene Flurstücke (Abgleich über flurstueckskennzeichen), statt sie doppelt zu laden – betrifft sowohl den Dialog in "Flurstücke laden" als auch den Button "aus gewählten Gemarkungen laden" (#28)
- Fortschrittsdialog beim Laden aus ALKIS schließt jetzt zuverlässig bei jedem Abbruch (Bugfix) - Fortschrittsdialog beim Laden aus ALKIS schließt jetzt zuverlässig bei jedem Abbruch (Bugfix)
- Fortschrittsanzeige beim Laden aus ALKIS in zwei Dialoge aufgeteilt (Suche / eigentlicher Ladevorgang), inkl. eigenem Suchschritt - Fortschrittsanzeige beim Laden aus ALKIS in zwei Dialoge aufgeteilt (Suche / eigentlicher Ladevorgang), inkl. eigenem Suchschritt
- Mehrfachauswahl bei mehreren BauRaumOderBodenordnungsrecht-Treffern zur Verfahrensnummer (gemeinsames Laden/Verschmelzen mehrerer Objekte) - Mehrfachauswahl bei mehreren BauRaumOderBodenordnungsrecht-Treffern zur Verfahrensnummer (gemeinsames Laden/Verschmelzen mehrerer Objekte)
+61 -14
View File
@@ -3,8 +3,9 @@
Lädt Flurstücke aus dem ALKIS-WFS-Dienst (``adv:AX_Flurstueck``) anhand von Lädt Flurstücke aus dem ALKIS-WFS-Dienst (``adv:AX_Flurstueck``) anhand von
Gemarkungsschlüsseln, die der Nutzer interaktiv eingibt. Gemarkungsschlüsseln, die der Nutzer interaktiv eingibt.
Der WFS-Layer wird direkt (nicht als Memory-Layer) ins Projekt geladen, Für jede Gemarkungsnummer wird eine eigene WFS-Anfrage gestellt (umgeht das
da die einzelnen Features für die spätere Auswahl benötigt werden. WFS-Feature-Limit einer Gesamtabfrage); alle Ergebnisse werden anschließend in
einem gemeinsamen Memory-Layer zusammengeführt.
**Erweiterung gegenüber dem Original:** **Erweiterung gegenüber dem Original:**
@@ -16,10 +17,11 @@ da die einzelnen Features für die spätere Auswahl benötigt werden.
- ``vorgabe_namen``: Zugehörige Bezeichnungen, die im Dialog als Hinweis - ``vorgabe_namen``: Zugehörige Bezeichnungen, die im Dialog als Hinweis
angezeigt werden (kein eigenständiges Feld, nur Anzeigetext). angezeigt werden (kein eigenständiges Feld, nur Anzeigetext).
- ``bbox``: Optionaler BBox-Parameter, der – sofern übergeben – als - ``bbox``: Nicht mehr verwendet. Die Flurstück-Geometrie kommt ausschließlich
zusätzlicher serverseitiger Filter in die WFS-URI eingebettet wird. So aus dem ALKIS-WFS je Gemarkung; eine BBox filtert höchstens vorgelagert die
werden ausschließlich Flurstücke geladen, die den Verfahrensgebiets-Rahmen Gemarkungsauswahl (siehe ``gemarkung_alkis.py``), nie die Flurstücke selbst.
überlappen. Der Parameter bleibt nur aus Gründen der Signatur-Rückwärtskompatibilität
erhalten.
""" """
from __future__ import annotations from __future__ import annotations
@@ -37,6 +39,7 @@ from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus
ALKIS_SF_URL = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs" ALKIS_SF_URL = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs"
FLST_TYPENAME = "adv:AX_Flurstueck" FLST_TYPENAME = "adv:AX_Flurstueck"
FLST_LAYER_NAME = "Flurstücke" FLST_LAYER_NAME = "Flurstücke"
FELD_FLURSTUECKSKENNZEICHEN = "flurstueckskennzeichen"
def _check_existing_flurstueck_layer(tab_widget): def _check_existing_flurstueck_layer(tab_widget):
@@ -74,6 +77,24 @@ def _check_existing_flurstueck_layer(tab_widget):
return None, LoadStatus.NONE return None, LoadStatus.NONE
def _vorhandene_kennzeichen(layer) -> set:
"""Liefert die ``flurstueckskennzeichen``-Werte, die ``layer`` bereits enthält.
Fehlt das Feld, wird eine leere Menge zurückgegeben (kein Abgleich möglich).
"""
kennzeichen: set = set()
if layer.fields().indexFromName(FELD_FLURSTUECKSKENNZEICHEN) < 0:
return kennzeichen
for feat in layer.getFeatures():
try:
kz = str(feat[FELD_FLURSTUECKSKENNZEICHEN] or "").strip()
except Exception:
kz = ""
if kz:
kennzeichen.add(kz)
return kennzeichen
def lade_flurstuecke_per_gemarkung(schluessel_liste: List[str]): def lade_flurstuecke_per_gemarkung(schluessel_liste: List[str]):
"""Lädt Flurstücke für mehrere Gemarkungen, eine WFS-Anfrage pro Schlüssel. """Lädt Flurstücke für mehrere Gemarkungen, eine WFS-Anfrage pro Schlüssel.
@@ -152,6 +173,7 @@ def flurstueck_alkis(
vorgabe_namen: Optional[List[str]] = None, vorgabe_namen: Optional[List[str]] = None,
bbox=None, bbox=None,
ohne_dialog: bool = False, ohne_dialog: bool = False,
ohne_schluessel_abfrage: bool = False,
) -> Tuple[Optional[QgsVectorLayer], LoadStatus]: ) -> Tuple[Optional[QgsVectorLayer], LoadStatus]:
"""Lädt Flurstücke aus dem ALKIS-WFS anhand von Gemarkungsschlüsseln. """Lädt Flurstücke aus dem ALKIS-WFS anhand von Gemarkungsschlüsseln.
@@ -165,17 +187,27 @@ def flurstueck_alkis(
Eltern-Widget für Dialoge. Eltern-Widget für Dialoge.
vorgabe_schluessel : list of str, optional vorgabe_schluessel : list of str, optional
Vorausgefüllte Gemarkungsnummern (4-stellig). Im Dialog als Standard Vorausgefüllte Gemarkungsnummern (4-stellig). Im Dialog als Standard
angezeigt; bei ``ohne_dialog=True`` direkt verwendet. angezeigt; bei ``ohne_dialog=True`` oder ``ohne_schluessel_abfrage=True``
direkt verwendet.
vorgabe_namen : list of str, optional vorgabe_namen : list of str, optional
Menschenlesbare Gemarkungsbezeichnungen für den Dialog-Hinweistext. Menschenlesbare Gemarkungsbezeichnungen für den Dialog-Hinweistext.
bbox : bbox :
Nicht mehr verwendet (WFS unterstützt keinen BBox-Filter). Bleibt Nicht mehr verwendet (WFS unterstützt keinen BBox-Filter). Bleibt
für Rückwärtskompatibilität in der Signatur erhalten. für Rückwärtskompatibilität in der Signatur erhalten.
ohne_dialog : bool, optional ohne_dialog : bool, optional
``True``: Überspringt die Gemarkungsschlüssel-Nutzerabfrage und verwendet ``True``: Überspringt sowohl die Gemarkungsschlüssel-Nutzerabfrage (wie
``vorgabe_schluessel`` direkt. Vorhandene Layer werden ohne Rückfrage ``ohne_schluessel_abfrage``) als auch die Rückfrage bei einem bereits
ersetzt. Gedacht für die automatische Pipeline vorhandenen Layer – dieser wird dann immer ersetzt. Gedacht für die
(``verfahrensgebiet_alkis_komplett``). Standard: ``False``. automatischen Pipelines (``verfahrensgebiet_alkis_komplett``,
``verfahrensgebiet_shape_komplett``). Standard: ``False``.
ohne_schluessel_abfrage : bool, optional
``True``: Überspringt nur die Gemarkungsschlüssel-Nutzerabfrage und
verwendet ``vorgabe_schluessel`` direkt; die Rückfrage bei einem
bereits vorhandenen Layer (Überschreiben/Anhängen/Abbrechen, inkl.
Dedup beim Anhängen) bleibt aktiv. Für Aufrufer, die die Schlüssel
bereits anderweitig ermittelt haben (z. B. aus einer Layer-Auswahl),
aber trotzdem interaktiv mit einem bestehenden Layer umgehen wollen.
Wirkungslos, wenn ``ohne_dialog=True`` gesetzt ist. Standard: ``False``.
Returns Returns
------- -------
@@ -201,8 +233,8 @@ def flurstueck_alkis(
append_target = existing append_target = existing
# --- Schritt 2: Gemarkungsschlüssel bestimmen ------------------------- # --- Schritt 2: Gemarkungsschlüssel bestimmen -------------------------
if ohne_dialog: if ohne_dialog or ohne_schluessel_abfrage:
# Pipeline-Modus: vorgabe_schluessel direkt verwenden, kein Dialog # vorgabe_schluessel direkt verwenden, kein ask_text-Dialog
if not vorgabe_schluessel: if not vorgabe_schluessel:
return None, LoadStatus.NONE return None, LoadStatus.NONE
schluessel_liste = [s.strip() for s in vorgabe_schluessel if s.strip()] schluessel_liste = [s.strip() for s in vorgabe_schluessel if s.strip()]
@@ -250,7 +282,22 @@ def flurstueck_alkis(
# --- Anhängen an vorhandenen Layer ------------------------------------ # --- Anhängen an vorhandenen Layer ------------------------------------
if status == LoadStatus.APPEND and append_target is not None: if status == LoadStatus.APPEND and append_target is not None:
append_target.dataProvider().addFeatures(list(layer.getFeatures())) alle_feats = list(layer.getFeatures())
neue_feats = alle_feats
if layer.fields().indexFromName(FELD_FLURSTUECKSKENNZEICHEN) >= 0:
vorhandene = _vorhandene_kennzeichen(append_target)
neue_feats = [
f for f in alle_feats
if str(f[FELD_FLURSTUECKSKENNZEICHEN] or "").strip() not in vorhandene
]
uebersprungen = len(alle_feats) - len(neue_feats)
if uebersprungen:
_info(
"Flurstücke",
f"{uebersprungen} bereits vorhandene Flurstücke übersprungen.",
duration=3,
)
append_target.dataProvider().addFeatures(neue_feats)
append_target.updateExtents() append_target.updateExtents()
append_target.triggerRepaint() append_target.triggerRepaint()
return append_target, LoadStatus.APPEND return append_target, LoadStatus.APPEND
+2
View File
@@ -34,6 +34,7 @@ from sn_basis.functions import QgsVectorLayer, QgsProject
from sn_basis.functions import QMessageBox from sn_basis.functions import QMessageBox
from sn_basis.functions.message_wrapper import info, warning from sn_basis.functions.message_wrapper import info, warning
from sn_basis.functions.dialog_wrapper import create_progress_dialog from sn_basis.functions.dialog_wrapper import create_progress_dialog
from sn_basis.functions.ly_style_wrapper import apply_style
ALKIS_SF_URL = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs" ALKIS_SF_URL = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs"
PUNKTORT_TA_TYPENAME = "adv:AX_PunktortTA" PUNKTORT_TA_TYPENAME = "adv:AX_PunktortTA"
@@ -1652,6 +1653,7 @@ def grenzpunkte_gemarkungsweise(
return None return None
project.addMapLayer(joined_layer) project.addMapLayer(joined_layer)
apply_style(joined_layer, "alkis_grenzpunkte.qml")
prog.set_value(prog_gesamt) prog.set_value(prog_gesamt)
prog.close() prog.close()
+913
View File
@@ -0,0 +1,913 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis autoRefreshMode="Disabled" autoRefreshTime="0" hasScaleBasedVisibilityFlag="0" labelsEnabled="0" layerType="Vector" maxScale="0" minScale="100000000" readOnly="0" simplifyAlgorithm="0" simplifyDrawingHints="0" simplifyDrawingTol="1" simplifyLocal="1" simplifyMaxScale="1" styleCategories="AllStyleCategories" symbologyReferenceScale="-1" version="4.2.0-Belém do Pará">
<flags>
<Identifiable>1</Identifiable>
<Removable>1</Removable>
<Searchable>1</Searchable>
<Private>0</Private>
</flags>
<temporal accumulate="0" durationField="" durationUnit="min" enabled="0" endExpression="" endField="" fixedDuration="0" limitMode="0" mode="0" startExpression="" startField="">
<fixedRange>
<start></start>
<end></end>
</fixedRange>
</temporal>
<elevation binding="Centroid" clamping="Terrain" customToleranceEnabled="0" extrusion="0" extrusionEnabled="0" respectLayerSymbol="1" showMarkerSymbolInSurfacePlots="0" symbology="Line" type="IndividualFeatures" zoffset="0" zscale="1">
<data-defined-properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data-defined-properties>
<profileLineSymbol>
<symbol alpha="1" clip_to_extent="1" force_rhr="0" frame_rate="10" is_animated="0" name="" type="line">
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
<layer class="SimpleLine" enabled="1" id="{524b77c5-d547-434b-918a-eaa8d67032a3}" locked="0" pass="0">
<Option type="Map">
<Option name="align_dash_pattern" type="QString" value="0"/>
<Option name="capstyle" type="QString" value="square"/>
<Option name="customdash" type="QString" value="5;2"/>
<Option name="customdash_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="customdash_unit" type="QString" value="MM"/>
<Option name="dash_pattern_offset" type="QString" value="0"/>
<Option name="dash_pattern_offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="dash_pattern_offset_unit" type="QString" value="MM"/>
<Option name="draw_inside_polygon" type="QString" value="0"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="line_color" type="QString" value="152,125,183,255,rgb:0.5960785,0.4901961,0.7176471,1"/>
<Option name="line_style" type="QString" value="solid"/>
<Option name="line_width" type="QString" value="0.6"/>
<Option name="line_width_unit" type="QString" value="MM"/>
<Option name="offset" type="QString" value="0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="MM"/>
<Option name="ring_filter" type="QString" value="0"/>
<Option name="trim_distance_end" type="QString" value="0"/>
<Option name="trim_distance_end_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="trim_distance_end_unit" type="QString" value="MM"/>
<Option name="trim_distance_start" type="QString" value="0"/>
<Option name="trim_distance_start_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="trim_distance_start_unit" type="QString" value="MM"/>
<Option name="tweak_dash_pattern_on_corners" type="QString" value="0"/>
<Option name="use_custom_dash" type="QString" value="0"/>
<Option name="width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</profileLineSymbol>
<profileFillSymbol>
<symbol alpha="1" clip_to_extent="1" force_rhr="0" frame_rate="10" is_animated="0" name="" type="fill">
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
<layer class="SimpleFill" enabled="1" id="{a950bd48-1020-4bd8-ba91-53a188c9d8c0}" locked="0" pass="0">
<Option type="Map">
<Option name="border_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="color" type="QString" value="152,125,183,255,rgb:0.5960785,0.4901961,0.7176471,1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="offset" type="QString" value="0,0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="MM"/>
<Option name="outline_color" type="QString" value="109,89,131,255,rgb:0.4257572,0.3501335,0.5125963,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="0.2"/>
<Option name="outline_width_unit" type="QString" value="MM"/>
<Option name="style" type="QString" value="solid"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</profileFillSymbol>
<profileMarkerSymbol>
<symbol alpha="1" clip_to_extent="1" force_rhr="0" frame_rate="10" is_animated="0" name="" type="marker">
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
<layer class="SimpleMarker" enabled="1" id="{1432e3c3-aaae-4f1c-8df7-441e77bdd112}" locked="0" pass="0">
<Option type="Map">
<Option name="angle" type="QString" value="0"/>
<Option name="cap_style" type="QString" value="square"/>
<Option name="color" type="QString" value="152,125,183,255,rgb:0.5960785,0.4901961,0.7176471,1"/>
<Option name="horizontal_anchor_point" type="QString" value="1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="name" type="QString" value="diamond"/>
<Option name="offset" type="QString" value="0,0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="MM"/>
<Option name="outline_color" type="QString" value="109,89,131,255,rgb:0.4257572,0.3501335,0.5125963,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="0.2"/>
<Option name="outline_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="outline_width_unit" type="QString" value="MM"/>
<Option name="scale_method" type="QString" value="diameter"/>
<Option name="size" type="QString" value="3"/>
<Option name="size_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="size_unit" type="QString" value="MM"/>
<Option name="vertical_anchor_point" type="QString" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</profileMarkerSymbol>
</elevation>
<renderer-v2 enableorderby="0" forceraster="0" referencescale="-1" symbollevels="0" type="RuleRenderer">
<rules key="{b5ed05de-e0db-4029-a532-cc0c11943124}">
<rule filter=" &quot;gp_abmarkung_Marke&quot; = '1000' " key="{ba76030e-eb1e-4a5a-b10c-b8614af46b1a}" label="Abgemarkt" symbol="0">
<rule filter="&quot;gp_festgestellterGrenzpunkt&quot; = 'true'" key="{0f910cf3-1e11-45f4-b4a5-5b8b11f4e2f3}" label="FGP" symbol="1"/>
</rule>
<rule filter=" &quot;gp_abmarkung_Marke&quot; IN ('9500')" key="{f530f073-c1a2-426e-9394-eb137d98b1fd}" label="Nicht abgemarkt" symbol="2">
<rule filter="&quot;gp_festgestellterGrenzpunkt&quot; = 'true'" key="{c1a0a382-cbf0-4981-b461-33c42c64701e}" label="FGP" symbol="3"/>
</rule>
<rule filter=" &quot;gp_abmarkung_Marke&quot; IN ('9600')" key="{f9d78633-b6a7-40df-88b4-d2d5bd51d1ab}" label="zeitweilig ausgesetzt" symbol="4">
<rule filter="&quot;gp_festgestellterGrenzpunkt&quot; = 'true'" key="{66a1f910-5127-4e2c-8c55-bb48f290be47}" label="FGP" symbol="5"/>
</rule>
</rules>
<symbols>
<symbol alpha="1" clip_to_extent="1" force_rhr="0" frame_rate="10" is_animated="0" name="0" type="marker">
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
<layer class="SimpleMarker" enabled="1" id="{4b2b5770-c712-4a17-a7e7-e8d833a5ef55}" locked="0" pass="0">
<Option type="Map">
<Option name="angle" type="QString" value="0"/>
<Option name="cap_style" type="QString" value="square"/>
<Option name="color" type="QString" value="255,255,255,255,hsv:0,0,1,1"/>
<Option name="horizontal_anchor_point" type="QString" value="1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="name" type="QString" value="circle"/>
<Option name="offset" type="QString" value="0,0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="Point"/>
<Option name="outline_color" type="QString" value="35,35,35,255,rgb:0.1372549,0.1372549,0.1372549,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="1"/>
<Option name="outline_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="outline_width_unit" type="QString" value="Point"/>
<Option name="scale_method" type="QString" value="diameter"/>
<Option name="size" type="QString" value="6"/>
<Option name="size_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="size_unit" type="QString" value="Point"/>
<Option name="vertical_anchor_point" type="QString" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
<symbol alpha="1" clip_to_extent="1" force_rhr="0" frame_rate="10" is_animated="0" name="1" type="marker">
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
<layer class="SimpleMarker" enabled="1" id="{e3e8ca93-babc-4c67-803f-73b091b1dcbf}" locked="0" pass="0">
<Option type="Map">
<Option name="angle" type="QString" value="0"/>
<Option name="cap_style" type="QString" value="square"/>
<Option name="color" type="QString" value="255,255,255,0,hsv:0,0,1,0"/>
<Option name="horizontal_anchor_point" type="QString" value="1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="name" type="QString" value="circle"/>
<Option name="offset" type="QString" value="0,0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="Point"/>
<Option name="outline_color" type="QString" value="227,26,28,255,rgb:0.8901961,0.1019608,0.1098039,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="1"/>
<Option name="outline_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="outline_width_unit" type="QString" value="Point"/>
<Option name="scale_method" type="QString" value="diameter"/>
<Option name="size" type="QString" value="8"/>
<Option name="size_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="size_unit" type="QString" value="Point"/>
<Option name="vertical_anchor_point" type="QString" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
<symbol alpha="1" clip_to_extent="1" force_rhr="0" frame_rate="10" is_animated="0" name="2" type="marker">
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
<layer class="SimpleMarker" enabled="1" id="{3b684a01-3cd1-4ec2-a3b3-5dd097cac078}" locked="0" pass="0">
<Option type="Map">
<Option name="angle" type="QString" value="0"/>
<Option name="cap_style" type="QString" value="square"/>
<Option name="color" type="QString" value="255,255,255,255,hsv:0,0,1,1"/>
<Option name="horizontal_anchor_point" type="QString" value="1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="name" type="QString" value="circle"/>
<Option name="offset" type="QString" value="0,0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="MM"/>
<Option name="outline_color" type="QString" value="35,35,35,255,rgb:0.1372549,0.1372549,0.1372549,1"/>
<Option name="outline_style" type="QString" value="no"/>
<Option name="outline_width" type="QString" value="0"/>
<Option name="outline_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="outline_width_unit" type="QString" value="MM"/>
<Option name="scale_method" type="QString" value="diameter"/>
<Option name="size" type="QString" value="6"/>
<Option name="size_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="size_unit" type="QString" value="Point"/>
<Option name="vertical_anchor_point" type="QString" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
<layer class="SimpleMarker" enabled="1" id="{7cf47a18-2311-467c-835d-8a5ba1dcbbf9}" locked="0" pass="0">
<Option type="Map">
<Option name="angle" type="QString" value="0"/>
<Option name="cap_style" type="QString" value="square"/>
<Option name="color" type="QString" value="0,0,0,255,hsv:0.99550002813339233,0.60656136274337769,0,1"/>
<Option name="horizontal_anchor_point" type="QString" value="1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="name" type="QString" value="circle"/>
<Option name="offset" type="QString" value="0,0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="MM"/>
<Option name="outline_color" type="QString" value="35,35,35,255,rgb:0.1372549,0.1372549,0.1372549,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="0"/>
<Option name="outline_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="outline_width_unit" type="QString" value="MM"/>
<Option name="scale_method" type="QString" value="diameter"/>
<Option name="size" type="QString" value="2"/>
<Option name="size_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="size_unit" type="QString" value="Point"/>
<Option name="vertical_anchor_point" type="QString" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
<symbol alpha="1" clip_to_extent="1" force_rhr="0" frame_rate="10" is_animated="0" name="3" type="marker">
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
<layer class="SimpleMarker" enabled="1" id="{e3e8ca93-babc-4c67-803f-73b091b1dcbf}" locked="0" pass="0">
<Option type="Map">
<Option name="angle" type="QString" value="0"/>
<Option name="cap_style" type="QString" value="square"/>
<Option name="color" type="QString" value="255,255,255,0,hsv:0,0,1,0"/>
<Option name="horizontal_anchor_point" type="QString" value="1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="name" type="QString" value="circle"/>
<Option name="offset" type="QString" value="0,0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="Point"/>
<Option name="outline_color" type="QString" value="227,26,28,255,rgb:0.8901961,0.1019608,0.1098039,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="1"/>
<Option name="outline_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="outline_width_unit" type="QString" value="Point"/>
<Option name="scale_method" type="QString" value="diameter"/>
<Option name="size" type="QString" value="8"/>
<Option name="size_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="size_unit" type="QString" value="Point"/>
<Option name="vertical_anchor_point" type="QString" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
<symbol alpha="1" clip_to_extent="1" force_rhr="0" frame_rate="10" is_animated="0" name="4" type="marker">
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
<layer class="SimpleMarker" enabled="1" id="{3b684a01-3cd1-4ec2-a3b3-5dd097cac078}" locked="0" pass="0">
<Option type="Map">
<Option name="angle" type="QString" value="0"/>
<Option name="cap_style" type="QString" value="square"/>
<Option name="color" type="QString" value="255,255,255,255,hsv:0,0,1,1"/>
<Option name="horizontal_anchor_point" type="QString" value="1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="name" type="QString" value="circle"/>
<Option name="offset" type="QString" value="0,0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="MM"/>
<Option name="outline_color" type="QString" value="35,35,35,255,rgb:0.1372549,0.1372549,0.1372549,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="1"/>
<Option name="outline_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="outline_width_unit" type="QString" value="MM"/>
<Option name="scale_method" type="QString" value="diameter"/>
<Option name="size" type="QString" value="6"/>
<Option name="size_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="size_unit" type="QString" value="Point"/>
<Option name="vertical_anchor_point" type="QString" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
<layer class="SimpleMarker" enabled="1" id="{7cf47a18-2311-467c-835d-8a5ba1dcbbf9}" locked="0" pass="0">
<Option type="Map">
<Option name="angle" type="QString" value="0"/>
<Option name="cap_style" type="QString" value="square"/>
<Option name="color" type="QString" value="0,0,0,255,hsv:0.99550002813339233,0.60656136274337769,0,1"/>
<Option name="horizontal_anchor_point" type="QString" value="1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="name" type="QString" value="circle"/>
<Option name="offset" type="QString" value="0,0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="MM"/>
<Option name="outline_color" type="QString" value="35,35,35,255,rgb:0.1372549,0.1372549,0.1372549,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="0"/>
<Option name="outline_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="outline_width_unit" type="QString" value="MM"/>
<Option name="scale_method" type="QString" value="diameter"/>
<Option name="size" type="QString" value="2"/>
<Option name="size_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="size_unit" type="QString" value="Point"/>
<Option name="vertical_anchor_point" type="QString" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
<symbol alpha="1" clip_to_extent="1" force_rhr="0" frame_rate="10" is_animated="0" name="5" type="marker">
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
<layer class="SimpleMarker" enabled="1" id="{e3e8ca93-babc-4c67-803f-73b091b1dcbf}" locked="0" pass="0">
<Option type="Map">
<Option name="angle" type="QString" value="0"/>
<Option name="cap_style" type="QString" value="square"/>
<Option name="color" type="QString" value="255,255,255,0,hsv:0,0,1,0"/>
<Option name="horizontal_anchor_point" type="QString" value="1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="name" type="QString" value="circle"/>
<Option name="offset" type="QString" value="0,0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="Point"/>
<Option name="outline_color" type="QString" value="227,26,28,255,rgb:0.8901961,0.1019608,0.1098039,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="1"/>
<Option name="outline_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="outline_width_unit" type="QString" value="Point"/>
<Option name="scale_method" type="QString" value="diameter"/>
<Option name="size" type="QString" value="8"/>
<Option name="size_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="size_unit" type="QString" value="Point"/>
<Option name="vertical_anchor_point" type="QString" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</symbols>
<data-defined-properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data-defined-properties>
</renderer-v2>
<selection mode="Default">
<selectionColor invalid="1"/>
<selectionSymbol>
<symbol alpha="1" clip_to_extent="1" force_rhr="0" frame_rate="10" is_animated="0" name="" type="marker">
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
<layer class="SimpleMarker" enabled="1" id="{4e7caead-e027-4f3e-a2ff-5a600d1c387c}" locked="0" pass="0">
<Option type="Map">
<Option name="angle" type="QString" value="0"/>
<Option name="cap_style" type="QString" value="square"/>
<Option name="color" type="QString" value="255,0,0,255,rgb:1,0,0,1"/>
<Option name="horizontal_anchor_point" type="QString" value="1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="name" type="QString" value="circle"/>
<Option name="offset" type="QString" value="0,0"/>
<Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offset_unit" type="QString" value="MM"/>
<Option name="outline_color" type="QString" value="35,35,35,255,rgb:0.1372549,0.1372549,0.1372549,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="0"/>
<Option name="outline_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="outline_width_unit" type="QString" value="MM"/>
<Option name="scale_method" type="QString" value="diameter"/>
<Option name="size" type="QString" value="2"/>
<Option name="size_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="size_unit" type="QString" value="MM"/>
<Option name="vertical_anchor_point" type="QString" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</selectionSymbol>
</selection>
<customproperties>
<Option type="Map">
<Option name="dualview/previewExpressions" type="List">
<Option type="QString" value="COALESCE( &quot;gml_id&quot;, '&lt;NULL>' )"/>
</Option>
<Option name="embeddedWidgets/count" type="int" value="0"/>
<Option name="variableNames"/>
<Option name="variableValues"/>
</Option>
</customproperties>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>1</layerOpacity>
<geometryOptions geometryPrecision="0" removeDuplicateNodes="0">
<activeChecks/>
<checkConfiguration/>
</geometryOptions>
<legend showLabelLegend="0" type="default-vector"/>
<referencedLayers/>
<referencingLayers/>
<fieldConfiguration>
<field configurationFlags="0" name="gml_id">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="identifier">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="lebenszeitintervall">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="modellart">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="anlass">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="zeigtAufExternes">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="istTeilVon">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="kartendarstellung">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="koordinatenstatus">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_gml_id">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_identifier">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_lebenszeitintervall">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_modellart">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_anlass">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_zeigtAufExternes">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_bestehtAus">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_punktkennung">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_abmarkung_Marke">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_festgestellterGrenzpunkt">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_inversZu_zeigtAuf">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="0" name="gp_sonstigeEigenschaft">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
</fieldConfiguration>
<aliases>
<alias field="gml_id" index="0" name=""/>
<alias field="identifier" index="1" name=""/>
<alias field="lebenszeitintervall" index="2" name=""/>
<alias field="modellart" index="3" name=""/>
<alias field="anlass" index="4" name=""/>
<alias field="zeigtAufExternes" index="5" name=""/>
<alias field="istTeilVon" index="6" name=""/>
<alias field="kartendarstellung" index="7" name=""/>
<alias field="koordinatenstatus" index="8" name=""/>
<alias field="qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe" index="9" name=""/>
<alias field="gp_gml_id" index="10" name=""/>
<alias field="gp_identifier" index="11" name=""/>
<alias field="gp_lebenszeitintervall" index="12" name=""/>
<alias field="gp_modellart" index="13" name=""/>
<alias field="gp_anlass" index="14" name=""/>
<alias field="gp_zeigtAufExternes" index="15" name=""/>
<alias field="gp_bestehtAus" index="16" name=""/>
<alias field="gp_punktkennung" index="17" name=""/>
<alias field="gp_abmarkung_Marke" index="18" name=""/>
<alias field="gp_festgestellterGrenzpunkt" index="19" name=""/>
<alias field="gp_inversZu_zeigtAuf" index="20" name=""/>
<alias field="gp_sonstigeEigenschaft" index="21" name=""/>
</aliases>
<defaults>
<default applyOnUpdate="0" expression="" field="gml_id"/>
<default applyOnUpdate="0" expression="" field="identifier"/>
<default applyOnUpdate="0" expression="" field="lebenszeitintervall"/>
<default applyOnUpdate="0" expression="" field="modellart"/>
<default applyOnUpdate="0" expression="" field="anlass"/>
<default applyOnUpdate="0" expression="" field="zeigtAufExternes"/>
<default applyOnUpdate="0" expression="" field="istTeilVon"/>
<default applyOnUpdate="0" expression="" field="kartendarstellung"/>
<default applyOnUpdate="0" expression="" field="koordinatenstatus"/>
<default applyOnUpdate="0" expression="" field="qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe"/>
<default applyOnUpdate="0" expression="" field="gp_gml_id"/>
<default applyOnUpdate="0" expression="" field="gp_identifier"/>
<default applyOnUpdate="0" expression="" field="gp_lebenszeitintervall"/>
<default applyOnUpdate="0" expression="" field="gp_modellart"/>
<default applyOnUpdate="0" expression="" field="gp_anlass"/>
<default applyOnUpdate="0" expression="" field="gp_zeigtAufExternes"/>
<default applyOnUpdate="0" expression="" field="gp_bestehtAus"/>
<default applyOnUpdate="0" expression="" field="gp_punktkennung"/>
<default applyOnUpdate="0" expression="" field="gp_abmarkung_Marke"/>
<default applyOnUpdate="0" expression="" field="gp_festgestellterGrenzpunkt"/>
<default applyOnUpdate="0" expression="" field="gp_inversZu_zeigtAuf"/>
<default applyOnUpdate="0" expression="" field="gp_sonstigeEigenschaft"/>
</defaults>
<constraints>
<constraint constraints="0" exp_strength="0" field="gml_id" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="identifier" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="lebenszeitintervall" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="modellart" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="anlass" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="zeigtAufExternes" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="istTeilVon" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="kartendarstellung" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="koordinatenstatus" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_gml_id" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_identifier" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_lebenszeitintervall" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_modellart" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_anlass" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_zeigtAufExternes" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_bestehtAus" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_punktkennung" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_abmarkung_Marke" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_festgestellterGrenzpunkt" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_inversZu_zeigtAuf" notnull_strength="0" unique_strength="0"/>
<constraint constraints="0" exp_strength="0" field="gp_sonstigeEigenschaft" notnull_strength="0" unique_strength="0"/>
</constraints>
<constraintExpressions>
<constraint desc="" exp="" field="gml_id"/>
<constraint desc="" exp="" field="identifier"/>
<constraint desc="" exp="" field="lebenszeitintervall"/>
<constraint desc="" exp="" field="modellart"/>
<constraint desc="" exp="" field="anlass"/>
<constraint desc="" exp="" field="zeigtAufExternes"/>
<constraint desc="" exp="" field="istTeilVon"/>
<constraint desc="" exp="" field="kartendarstellung"/>
<constraint desc="" exp="" field="koordinatenstatus"/>
<constraint desc="" exp="" field="qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe"/>
<constraint desc="" exp="" field="gp_gml_id"/>
<constraint desc="" exp="" field="gp_identifier"/>
<constraint desc="" exp="" field="gp_lebenszeitintervall"/>
<constraint desc="" exp="" field="gp_modellart"/>
<constraint desc="" exp="" field="gp_anlass"/>
<constraint desc="" exp="" field="gp_zeigtAufExternes"/>
<constraint desc="" exp="" field="gp_bestehtAus"/>
<constraint desc="" exp="" field="gp_punktkennung"/>
<constraint desc="" exp="" field="gp_abmarkung_Marke"/>
<constraint desc="" exp="" field="gp_festgestellterGrenzpunkt"/>
<constraint desc="" exp="" field="gp_inversZu_zeigtAuf"/>
<constraint desc="" exp="" field="gp_sonstigeEigenschaft"/>
</constraintExpressions>
<expressionfields/>
<attributeactions>
<defaultAction key="Canvas" value="{00000000-0000-0000-0000-000000000000}"/>
</attributeactions>
<attributetableconfig actionWidgetStyle="dropDown" sortExpression="" sortOrder="0">
<columns>
<column hidden="0" name="gml_id" type="field" width="-1"/>
<column hidden="0" name="identifier" type="field" width="-1"/>
<column hidden="0" name="lebenszeitintervall" type="field" width="-1"/>
<column hidden="0" name="modellart" type="field" width="-1"/>
<column hidden="0" name="anlass" type="field" width="-1"/>
<column hidden="0" name="zeigtAufExternes" type="field" width="-1"/>
<column hidden="0" name="istTeilVon" type="field" width="-1"/>
<column hidden="0" name="kartendarstellung" type="field" width="-1"/>
<column hidden="0" name="koordinatenstatus" type="field" width="-1"/>
<column hidden="0" name="qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe" type="field" width="-1"/>
<column hidden="0" name="gp_gml_id" type="field" width="-1"/>
<column hidden="0" name="gp_identifier" type="field" width="-1"/>
<column hidden="0" name="gp_lebenszeitintervall" type="field" width="-1"/>
<column hidden="0" name="gp_modellart" type="field" width="-1"/>
<column hidden="0" name="gp_anlass" type="field" width="-1"/>
<column hidden="0" name="gp_zeigtAufExternes" type="field" width="-1"/>
<column hidden="0" name="gp_bestehtAus" type="field" width="-1"/>
<column hidden="0" name="gp_punktkennung" type="field" width="-1"/>
<column hidden="0" name="gp_abmarkung_Marke" type="field" width="-1"/>
<column hidden="0" name="gp_festgestellterGrenzpunkt" type="field" width="-1"/>
<column hidden="0" name="gp_inversZu_zeigtAuf" type="field" width="-1"/>
<column hidden="0" name="gp_sonstigeEigenschaft" type="field" width="-1"/>
<column hidden="1" type="actions" width="-1"/>
</columns>
</attributetableconfig>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
<storedexpressions/>
<editform tolerant="1"></editform>
<editforminit/>
<editforminitcodesource>0</editforminitcodesource>
<editforminitfilepath></editforminitfilepath>
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
"""
QGIS-Formulare können eine Python-Funktion haben,, die aufgerufen wird, wenn sich das Formular öffnet
Diese Funktion kann verwendet werden um dem Formular Extralogik hinzuzufügen.
Der Name der Funktion wird im Feld "Python Init-Function" angegeben
Ein Beispiel folgt:
"""
from qgis.PyQt.QtWidgets import QWidget
def my_form_open(dialog, layer, feature):
geom = feature.geometry()
control = dialog.findChild(QWidget, "MyLineEdit")
]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<editorlayout>generatedlayout</editorlayout>
<editable>
<field editable="1" name="anlass"/>
<field editable="1" name="gml_id"/>
<field editable="1" name="gp_abmarkung_Marke"/>
<field editable="1" name="gp_anlass"/>
<field editable="1" name="gp_bestehtAus"/>
<field editable="1" name="gp_festgestellterGrenzpunkt"/>
<field editable="1" name="gp_gml_id"/>
<field editable="1" name="gp_identifier"/>
<field editable="1" name="gp_inversZu_zeigtAuf"/>
<field editable="1" name="gp_lebenszeitintervall"/>
<field editable="1" name="gp_modellart"/>
<field editable="1" name="gp_punktkennung"/>
<field editable="1" name="gp_sonstigeEigenschaft"/>
<field editable="1" name="gp_zeigtAufExternes"/>
<field editable="1" name="identifier"/>
<field editable="1" name="istTeilVon"/>
<field editable="1" name="kartendarstellung"/>
<field editable="1" name="koordinatenstatus"/>
<field editable="1" name="lebenszeitintervall"/>
<field editable="1" name="modellart"/>
<field editable="1" name="qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe"/>
<field editable="1" name="zeigtAufExternes"/>
</editable>
<labelOnTop>
<field labelOnTop="0" name="anlass"/>
<field labelOnTop="0" name="gml_id"/>
<field labelOnTop="0" name="gp_abmarkung_Marke"/>
<field labelOnTop="0" name="gp_anlass"/>
<field labelOnTop="0" name="gp_bestehtAus"/>
<field labelOnTop="0" name="gp_festgestellterGrenzpunkt"/>
<field labelOnTop="0" name="gp_gml_id"/>
<field labelOnTop="0" name="gp_identifier"/>
<field labelOnTop="0" name="gp_inversZu_zeigtAuf"/>
<field labelOnTop="0" name="gp_lebenszeitintervall"/>
<field labelOnTop="0" name="gp_modellart"/>
<field labelOnTop="0" name="gp_punktkennung"/>
<field labelOnTop="0" name="gp_sonstigeEigenschaft"/>
<field labelOnTop="0" name="gp_zeigtAufExternes"/>
<field labelOnTop="0" name="identifier"/>
<field labelOnTop="0" name="istTeilVon"/>
<field labelOnTop="0" name="kartendarstellung"/>
<field labelOnTop="0" name="koordinatenstatus"/>
<field labelOnTop="0" name="lebenszeitintervall"/>
<field labelOnTop="0" name="modellart"/>
<field labelOnTop="0" name="qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe"/>
<field labelOnTop="0" name="zeigtAufExternes"/>
</labelOnTop>
<reuseLastValuePolicy>
<field name="anlass" reuseLastValuePolicy="NotAllowed"/>
<field name="gml_id" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_abmarkung_Marke" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_anlass" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_bestehtAus" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_festgestellterGrenzpunkt" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_gml_id" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_identifier" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_inversZu_zeigtAuf" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_lebenszeitintervall" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_modellart" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_punktkennung" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_sonstigeEigenschaft" reuseLastValuePolicy="NotAllowed"/>
<field name="gp_zeigtAufExternes" reuseLastValuePolicy="NotAllowed"/>
<field name="identifier" reuseLastValuePolicy="NotAllowed"/>
<field name="istTeilVon" reuseLastValuePolicy="NotAllowed"/>
<field name="kartendarstellung" reuseLastValuePolicy="NotAllowed"/>
<field name="koordinatenstatus" reuseLastValuePolicy="NotAllowed"/>
<field name="lebenszeitintervall" reuseLastValuePolicy="NotAllowed"/>
<field name="modellart" reuseLastValuePolicy="NotAllowed"/>
<field name="qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe" reuseLastValuePolicy="NotAllowed"/>
<field name="zeigtAufExternes" reuseLastValuePolicy="NotAllowed"/>
</reuseLastValuePolicy>
<dataDefinedFieldProperties/>
<widgets/>
<previewExpression>COALESCE( "gml_id", '&lt;NULL>' )</previewExpression>
<mapTip enabled="1"></mapTip>
<layerGeometryType>0</layerGeometryType>
</qgis>
+221 -86
View File
@@ -6,8 +6,11 @@ Getestet werden:
- Nutzerabbruch bei ask_text → (None, LoadStatus.NONE) - Nutzerabbruch bei ask_text → (None, LoadStatus.NONE)
- Ungültiger WFS-Layer → (None, LoadStatus.NONE) - Ungültiger WFS-Layer → (None, LoadStatus.NONE)
- Erfolgreicher Ladevorgang → gültiger Layer + LoadStatus.FIRST - Erfolgreicher Ladevorgang → gültiger Layer + LoadStatus.FIRST
- Bestehender Layer: Nutzer wählt "Neu laden" → LoadStatus.RELOAD - Bestehender Layer, Drei-Knopf-Dialog: "Überschreiben" → LoadStatus.RELOAD,
- Bestehender Layer: Nutzer wählt "Behalten" → LoadStatus.KEEP "Abbrechen" → (None, LoadStatus.NONE), "Anhängen" siehe
TestFlurstueckAlkisAnhaengenDedup
- vorgabe_schluessel/vorgabe_namen füllen den Eingabedialog vor
- ohne_schluessel_abfrage=True überspringt die ask_text-Abfrage
""" """
import sys import sys
@@ -30,7 +33,12 @@ if "qgis" not in sys.modules:
sys.modules["qgis.core"] = qgis_core sys.modules["qgis.core"] = qgis_core
sys.modules["qgis.utils"] = qgis_utils sys.modules["qgis.utils"] = qgis_utils
from sn_verfahrensgebiet.functions.flurstueck_alkis import flurstueck_alkis, FLST_LAYER_NAME from sn_verfahrensgebiet.functions.flurstueck_alkis import (
flurstueck_alkis,
lade_flurstuecke_per_gemarkung,
FLST_LAYER_NAME,
FELD_FLURSTUECKSKENNZEICHEN,
)
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus
@@ -49,8 +57,8 @@ class _DummyLayer:
def id(self): def id(self):
return self._id return self._id
def extent(self): def featureCount(self):
return object() return 1
class _DummyProject: class _DummyProject:
@@ -78,7 +86,7 @@ class TestFlurstueckAlkis(unittest.TestCase):
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_user_cancel_returns_none(self, mock_ask_text, mock_project, mock_msgbox): def test_user_cancel_returns_none(self, mock_ask_text, mock_project, mock_msgbox):
"""ask_text → ok=False, text="" → (None, NONE).""" """Prüft, dass ein Abbruch im Eingabedialog (ok=False) (None, LoadStatus.NONE) liefert."""
mock_ask_text.return_value = ("", False) mock_ask_text.return_value = ("", False)
mock_project.return_value = _DummyProject() mock_project.return_value = _DummyProject()
@@ -91,7 +99,7 @@ class TestFlurstueckAlkis(unittest.TestCase):
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_empty_text_after_confirmation_returns_none(self, mock_ask_text, mock_project, mock_msgbox): def test_empty_text_after_confirmation_returns_none(self, mock_ask_text, mock_project, mock_msgbox):
"""ask_text → ok=True, aber Text leer → (None, NONE).""" """Prüft, dass ein bestätigter, aber leerer Eingabetext ebenfalls (None, LoadStatus.NONE) liefert."""
mock_ask_text.return_value = (" ", True) mock_ask_text.return_value = (" ", True)
mock_project.return_value = _DummyProject() mock_project.return_value = _DummyProject()
@@ -110,7 +118,7 @@ class TestFlurstueckAlkis(unittest.TestCase):
def test_invalid_wfs_layer_returns_none( def test_invalid_wfs_layer_returns_none(
self, mock_ask_text, mock_project, mock_wfs_layer_cls, mock_msgbox self, mock_ask_text, mock_project, mock_wfs_layer_cls, mock_msgbox
): ):
"""WFS-Layer nicht valide → (None, NONE).""" """Prüft, dass ein ungültiger WFS-Layer (None, LoadStatus.NONE) liefert."""
mock_ask_text.return_value = ("1234", True) mock_ask_text.return_value = ("1234", True)
mock_project.return_value = _DummyProject() mock_project.return_value = _DummyProject()
mock_wfs_layer_cls.return_value = _DummyLayer(valid=False) mock_wfs_layer_cls.return_value = _DummyLayer(valid=False)
@@ -123,67 +131,64 @@ class TestFlurstueckAlkis(unittest.TestCase):
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
# 3. Erfolgreicher Erstladevorgang # # 3. Erfolgreicher Erstladevorgang #
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_success_returns_layer_and_first_status( def test_success_returns_layer_and_first_status(
self, mock_ask_text, mock_project, mock_wfs_layer_cls self, mock_ask_text, mock_project, mock_lade
): ):
"""Kein bestehender Layer, WFS valide → (layer, FIRST).""" """Prüft, dass ohne bestehenden Layer der von lade_flurstuecke_per_gemarkung gelieferte Layer mit LoadStatus.FIRST zurückgegeben wird."""
mock_ask_text.return_value = ("1234, 5678", True) mock_ask_text.return_value = ("1234, 5678", True)
mock_project.return_value = _DummyProject() mock_project.return_value = _DummyProject()
valid_layer = _DummyLayer(valid=True, layer_id="wfs_ok") valid_layer = _DummyLayer(valid=True, layer_id="wfs_ok")
mock_wfs_layer_cls.return_value = valid_layer mock_lade.return_value = valid_layer
layer, status = flurstueck_alkis(None) layer, status = flurstueck_alkis(None)
self.assertIs(layer, valid_layer) self.assertIs(layer, valid_layer)
self.assertEqual(status, LoadStatus.FIRST) self.assertEqual(status, LoadStatus.FIRST)
mock_lade.assert_called_once_with(["1234", "5678"])
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance") def test_sql_filter_contains_all_schluessel(self, mock_wfs_layer_cls):
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text") """Prüft, dass lade_flurstuecke_per_gemarkung für jeden Gemarkungsschlüssel eine eigene WFS-Anfrage mit dessen Schlüssel stellt."""
def test_sql_filter_contains_all_schluessel( # Ungültig genügt: nur die aufgebaute URI je Aufruf wird geprüft, das
self, mock_ask_text, mock_project, mock_wfs_layer_cls # tatsächliche Zusammenführen der Features ist nicht Teil dieses Tests.
): mock_wfs_layer_cls.return_value = _DummyLayer(valid=False)
"""Der WFS-URI enthält alle eingegebenen Gemarkungsschlüssel."""
mock_ask_text.return_value = ("1111, 2222, 3333", True)
mock_project.return_value = _DummyProject()
mock_wfs_layer_cls.return_value = _DummyLayer(valid=True)
flurstueck_alkis(None) lade_flurstuecke_per_gemarkung(["1111", "2222", "3333"])
# Erster Positionsarg von QgsVectorLayer ist der URI-String uris = [call.args[0] for call in mock_wfs_layer_cls.call_args_list]
call_args = mock_wfs_layer_cls.call_args self.assertEqual(len(uris), 3)
uri_str = call_args[0][0] if call_args[0] else call_args[1].get("path", "") self.assertIn("1111", uris[0])
self.assertIn("1111", uri_str) self.assertIn("2222", uris[1])
self.assertIn("2222", uri_str) self.assertIn("3333", uris[2])
self.assertIn("3333", uri_str)
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
# 4. Bestehender Layer – Nutzer wählt "Neu laden" # # 4. Bestehender Layer – Nutzer klickt "Überschreiben" #
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QMessageBox") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QMessageBox")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_existing_layer_reload_yes( def test_existing_layer_reload_yes(
self, mock_ask_text, mock_project, mock_msgbox_cls, mock_wfs_layer_cls self, mock_ask_text, mock_project, mock_msgbox_cls, mock_lade
): ):
"""Bestehender Layer + Nutzer: 'Ja, neu laden' → LoadStatus.RELOAD.""" """Prüft, dass bei bestehendem Layer und Klick auf 'Überschreiben' der alte Layer entfernt und LoadStatus.RELOAD geliefert wird."""
existing = _DummyLayer(valid=True, layer_id="old_flst") existing = _DummyLayer(valid=True, layer_id="old_flst")
project = _DummyProject(named_layers=[existing]) project = _DummyProject(named_layers=[existing])
mock_ask_text.return_value = ("1234", True) mock_ask_text.return_value = ("1234", True)
mock_project.return_value = project mock_project.return_value = project
# StandardButton als int, damit | Operator funktioniert # Drei-Knopf-Dialog: addButton() liefert je Aufruf einen eigenen
mock_msgbox_cls.StandardButton.Yes = 0x4000 # Sentinel, clickedButton() simuliert den Klick auf "Überschreiben".
mock_msgbox_cls.StandardButton.No = 0x10000 btn_ueberschreiben, btn_anhaengen, btn_abbrechen = object(), object(), object()
# QMessageBox.question → Yes msg_instance = mock_msgbox_cls.return_value
mock_msgbox_cls.question.return_value = 0x4000 msg_instance.addButton.side_effect = [btn_ueberschreiben, btn_anhaengen, btn_abbrechen]
msg_instance.clickedButton.return_value = btn_ueberschreiben
new_layer = _DummyLayer(valid=True, layer_id="new_flst") new_layer = _DummyLayer(valid=True, layer_id="new_flst")
mock_wfs_layer_cls.return_value = new_layer mock_lade.return_value = new_layer
layer, status = flurstueck_alkis(None) layer, status = flurstueck_alkis(None)
@@ -192,46 +197,46 @@ class TestFlurstueckAlkis(unittest.TestCase):
self.assertEqual(status, LoadStatus.RELOAD) self.assertEqual(status, LoadStatus.RELOAD)
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
# 5. Bestehender Layer – Nutzer wählt "Behalten" # # 5. Bestehender Layer – Nutzer klickt "Abbrechen" #
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QMessageBox") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QMessageBox")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_existing_layer_keep_no( def test_existing_layer_abbrechen_returns_none(
self, mock_ask_text, mock_project, mock_msgbox_cls self, mock_ask_text, mock_project, mock_msgbox_cls
): ):
"""Bestehender Layer + Nutzer: 'Nein, behalten' → LoadStatus.KEEP.""" """Prüft, dass bei bestehendem Layer und Klick auf 'Abbrechen' nichts verändert wird und (None, LoadStatus.NONE) geliefert wird."""
existing = _DummyLayer(valid=True, layer_id="keep_flst") existing = _DummyLayer(valid=True, layer_id="keep_flst")
project = _DummyProject(named_layers=[existing]) project = _DummyProject(named_layers=[existing])
mock_ask_text.return_value = ("1234", True) mock_ask_text.return_value = ("1234", True)
mock_project.return_value = project mock_project.return_value = project
# StandardButton als int, damit | Operator funktioniert btn_ueberschreiben, btn_anhaengen, btn_abbrechen = object(), object(), object()
mock_msgbox_cls.StandardButton.Yes = 0x4000 msg_instance = mock_msgbox_cls.return_value
mock_msgbox_cls.StandardButton.No = 0x10000 msg_instance.addButton.side_effect = [btn_ueberschreiben, btn_anhaengen, btn_abbrechen]
# QMessageBox.question → No msg_instance.clickedButton.return_value = btn_abbrechen
mock_msgbox_cls.question.return_value = 0x10000
layer, status = flurstueck_alkis(None) layer, status = flurstueck_alkis(None)
self.assertIs(layer, existing) self.assertIsNone(layer)
self.assertEqual(status, LoadStatus.KEEP) self.assertEqual(status, LoadStatus.NONE)
self.assertEqual(project.removed, []) # nichts gelöscht self.assertEqual(project.removed, []) # nichts gelöscht
mock_ask_text.assert_not_called() # Abbruch schon vor der Schlüsselabfrage
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
# 6. vorgabe_schluessel füllt ask_text-Default vor # # 6. vorgabe_schluessel füllt ask_text-Default vor #
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_vorgabe_schluessel_prefills_default_text( def test_vorgabe_schluessel_prefills_default_text(
self, mock_ask_text, mock_project, mock_wfs_layer_cls self, mock_ask_text, mock_project, mock_lade
): ):
"""vorgabe_schluessel=['1234','5678'] → ask_text erhält '1234, 5678' als default_text.""" """Prüft, dass vorgabe_schluessel als vorbelegter default_text im Eingabedialog erscheint."""
mock_ask_text.return_value = ("1234, 5678", True) mock_ask_text.return_value = ("1234, 5678", True)
mock_project.return_value = _DummyProject() mock_project.return_value = _DummyProject()
mock_wfs_layer_cls.return_value = _DummyLayer(valid=True) mock_lade.return_value = _DummyLayer(valid=True)
flurstueck_alkis(None, vorgabe_schluessel=["1234", "5678"]) flurstueck_alkis(None, vorgabe_schluessel=["1234", "5678"])
@@ -245,16 +250,16 @@ class TestFlurstueckAlkis(unittest.TestCase):
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
# 7. vorgabe_namen erscheint im Label-Text # # 7. vorgabe_namen erscheint im Label-Text #
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_vorgabe_namen_appears_in_label( def test_vorgabe_namen_appears_in_label(
self, mock_ask_text, mock_project, mock_wfs_layer_cls self, mock_ask_text, mock_project, mock_lade
): ):
"""vorgabe_namen=['Musterstadt'] → ask_text-label enthält den Namen.""" """Prüft, dass vorgabe_namen im Beschriftungstext des Eingabedialogs erscheint."""
mock_ask_text.return_value = ("9999", True) mock_ask_text.return_value = ("9999", True)
mock_project.return_value = _DummyProject() mock_project.return_value = _DummyProject()
mock_wfs_layer_cls.return_value = _DummyLayer(valid=True) mock_lade.return_value = _DummyLayer(valid=True)
flurstueck_alkis(None, vorgabe_namen=["Musterstadt"]) flurstueck_alkis(None, vorgabe_namen=["Musterstadt"])
@@ -264,49 +269,179 @@ class TestFlurstueckAlkis(unittest.TestCase):
self.assertIn("Musterstadt", label_arg) self.assertIn("Musterstadt", label_arg)
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
# 8. bbox wird in WFS-URI eingebettet # # 8. Rückwärtskompatibilität: alle neuen Params optional #
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_bbox_embedded_in_wfs_uri(
self, mock_ask_text, mock_project, mock_wfs_layer_cls
):
"""bbox-Parameter → WFS-URI enthält 'bbox=' Substring."""
mock_ask_text.return_value = ("1234", True)
mock_project.return_value = _DummyProject()
mock_wfs_layer_cls.return_value = _DummyLayer(valid=True)
class _Rect:
def xMinimum(self): return 414000.0
def yMinimum(self): return 5657000.0
def xMaximum(self): return 415000.0
def yMaximum(self): return 5658000.0
flurstueck_alkis(None, bbox=_Rect())
uri_str = mock_wfs_layer_cls.call_args[0][0]
self.assertIn("bbox=", uri_str.lower())
# ------------------------------------------------------------------ #
# 9. Rückwärtskompatibilität: alle neuen Params optional #
# ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text") @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_backwards_compatible_no_new_params( def test_backwards_compatible_no_new_params(
self, mock_ask_text, mock_project, mock_wfs_layer_cls self, mock_ask_text, mock_project, mock_lade
): ):
"""flurstueck_alkis(tab) ohne neue Parameter → kein Fehler.""" """Prüft, dass flurstueck_alkis() ohne die neuen optionalen Parameter weiterhin fehlerfrei einen Layer mit LoadStatus.FIRST liefert."""
mock_ask_text.return_value = ("0000", True) mock_ask_text.return_value = ("0000", True)
mock_project.return_value = _DummyProject() mock_project.return_value = _DummyProject()
mock_wfs_layer_cls.return_value = _DummyLayer(valid=True) mock_lade.return_value = _DummyLayer(valid=True)
layer, status = flurstueck_alkis(None) layer, status = flurstueck_alkis(None)
self.assertIsNotNone(layer) self.assertIsNotNone(layer)
self.assertEqual(status, LoadStatus.FIRST) self.assertEqual(status, LoadStatus.FIRST)
# ------------------------------------------------------------------ #
# 9. ohne_schluessel_abfrage: vorgabe_schluessel direkt, kein ask_text (#47)
# ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_ohne_schluessel_abfrage_ueberspringt_ask_text(
self, mock_ask_text, mock_project, mock_lade
):
"""Prüft, dass ohne_schluessel_abfrage=True vorgabe_schluessel direkt verwendet und ask_text nicht aufgerufen wird."""
mock_project.return_value = _DummyProject()
geladen = _DummyFlstLayer([_DummyFlstFeature("110001_0001__00001")])
mock_lade.return_value = geladen
layer, status = flurstueck_alkis(
None, vorgabe_schluessel=["1234", "5678"], ohne_schluessel_abfrage=True
)
mock_ask_text.assert_not_called()
self.assertIs(layer, geladen)
self.assertEqual(status, LoadStatus.FIRST)
mock_lade.assert_called_once_with(["1234", "5678"])
# ---------------------------------------------------------------------------
# Dummies für den Anhängen-Deduplizierungstest (#28)
# ---------------------------------------------------------------------------
class _DummyFlstFeature:
def __init__(self, kennzeichen):
self._kennzeichen = kennzeichen
def __getitem__(self, key):
if key == FELD_FLURSTUECKSKENNZEICHEN:
return self._kennzeichen
return None
class _DummyFlstFields:
def __init__(self, hat_kennzeichen_feld: bool):
self._hat_feld = hat_kennzeichen_feld
def indexFromName(self, name):
if name == FELD_FLURSTUECKSKENNZEICHEN and self._hat_feld:
return 0
return -1
class _DummyFlstLayer:
"""Flurstück-Layer-Dummy mit Feature-Liste und aufzeichnendem ``dataProvider``."""
def __init__(self, features, hat_kennzeichen_feld: bool = True):
self._features = list(features)
self._hat_feld = hat_kennzeichen_feld
self.added = None
def getFeatures(self):
return list(self._features)
def fields(self):
return _DummyFlstFields(self._hat_feld)
def dataProvider(self):
layer = self
class _Provider:
def addFeatures(self, feats):
layer.added = list(feats)
layer._features.extend(feats)
return True
return _Provider()
def updateExtents(self):
pass
def triggerRepaint(self):
pass
def isValid(self):
return True
def featureCount(self):
return len(self._features)
class TestFlurstueckAlkisAnhaengenDedup(unittest.TestCase):
"""Anhängen an einen bestehenden Flurstück-Layer (Issue #28)."""
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis._check_existing_flurstueck_layer")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_append_ueberspringt_bereits_vorhandene_flurstuecke(
self, mock_ask_text, mock_check, mock_lade
):
"""Prüft, dass beim Anhängen bereits vorhandene flurstueckskennzeichen nicht erneut hinzugefügt werden."""
mock_ask_text.return_value = ("0001, 0002", True)
bestehend = _DummyFlstLayer([_DummyFlstFeature("110001_0001__00001")])
neu_geladen = _DummyFlstLayer([
_DummyFlstFeature("110001_0001__00001"), # bereits im Layer vorhanden
_DummyFlstFeature("110001_0002__00007"), # noch nicht vorhanden
])
mock_check.return_value = (bestehend, LoadStatus.APPEND)
mock_lade.return_value = neu_geladen
layer, status = flurstueck_alkis(None)
self.assertEqual(status, LoadStatus.APPEND)
self.assertIs(layer, bestehend)
self.assertEqual(len(bestehend.added), 1)
self.assertEqual(bestehend.added[0][FELD_FLURSTUECKSKENNZEICHEN], "110001_0002__00007")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis._check_existing_flurstueck_layer")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_append_ohne_kennzeichen_feld_haengt_alles_an(
self, mock_ask_text, mock_check, mock_lade
):
"""Prüft, dass ohne flurstueckskennzeichen-Feld weiterhin alle Features angehängt werden (Rückfallverhalten)."""
mock_ask_text.return_value = ("0001", True)
bestehend = _DummyFlstLayer([_DummyFlstFeature("110001_0001__00001")], hat_kennzeichen_feld=False)
neu_geladen = _DummyFlstLayer(
[_DummyFlstFeature("110001_0001__00001"), _DummyFlstFeature("110001_0002__00007")],
hat_kennzeichen_feld=False,
)
mock_check.return_value = (bestehend, LoadStatus.APPEND)
mock_lade.return_value = neu_geladen
layer, status = flurstueck_alkis(None)
self.assertEqual(status, LoadStatus.APPEND)
self.assertEqual(len(bestehend.added), 2)
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis._check_existing_flurstueck_layer")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_ohne_schluessel_abfrage_append_dedup(self, mock_ask_text, mock_check, mock_lade):
"""ohne_schluessel_abfrage=True: Anhängen dedupliziert trotzdem, ohne nach Schlüsseln zu fragen (#47)."""
bestehend = _DummyFlstLayer([_DummyFlstFeature("110001_0001__00001")])
neu_geladen = _DummyFlstLayer([
_DummyFlstFeature("110001_0001__00001"), # bereits vorhanden
_DummyFlstFeature("110001_0002__00007"), # neu
])
mock_check.return_value = (bestehend, LoadStatus.APPEND)
mock_lade.return_value = neu_geladen
layer, status = flurstueck_alkis(
None, vorgabe_schluessel=["0001", "0002"], ohne_schluessel_abfrage=True
)
mock_ask_text.assert_not_called()
self.assertEqual(status, LoadStatus.APPEND)
self.assertEqual(len(bestehend.added), 1)
self.assertEqual(bestehend.added[0][FELD_FLURSTUECKSKENNZEICHEN], "110001_0002__00007")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+130
View File
@@ -43,6 +43,7 @@ if "qgis" not in sys.modules:
from sn_verfahrensgebiet.functions.grenzpunkt_alkis import ( from sn_verfahrensgebiet.functions.grenzpunkt_alkis import (
grenzpunkt_alkis, grenzpunkt_alkis,
grenzpunkte_gemarkungsweise,
_extrahiere_gml_ids, _extrahiere_gml_ids,
) )
@@ -297,5 +298,134 @@ class TestGrenzpunktAlkis(unittest.TestCase):
self.assertIs(gp, gp_layer) self.assertIs(gp, gp_layer)
# ---------------------------------------------------------------------------
# Tests: grenzpunkte_gemarkungsweise – Stilanbringung (#53)
# ---------------------------------------------------------------------------
_G_PATCH_EXTENTS = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._lade_gemarkung_extents"
_G_PATCH_HITS = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._wfs_hits_count"
_G_PATCH_GET_GML = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._wfs_get_gml"
_G_PATCH_PARSE_GML = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._parse_alkis_gml_memory_layer"
_G_PATCH_ERKENNE_FELD = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._erkenne_gehoert_zu_feld"
_G_PATCH_EXTRAHIERE_ID = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._extrahiere_feature_gml_id"
_G_PATCH_LADE_GP_DICT = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._lade_grenzpunkte_als_dict"
_G_PATCH_ERSTELLE_JOINED = "sn_verfahrensgebiet.functions.grenzpunkt_alkis._erstelle_joined_layer"
_G_PATCH_WFS_LAYER = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.QgsVectorLayer"
_G_PATCH_PROJECT = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.QgsProject"
_G_PATCH_PROGRESS = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.create_progress_dialog"
_G_PATCH_APPLY_STIL = "sn_verfahrensgebiet.functions.grenzpunkt_alkis.apply_style"
# QgsField/QgsFeature werden innerhalb der Funktion per lokalem Import geholt
# (from sn_basis.functions import QgsField/QgsFeature) - Patch-Ziel ist daher
# die Quelle, nicht der Aufrufort.
_G_PATCH_QGSFIELD = "sn_basis.functions.QgsField"
_G_PATCH_QGSFEATURE = "sn_basis.functions.QgsFeature"
class _DummyProgress:
def set_value(self, wert): pass
def set_label(self, text): pass
def set_total(self, wert): pass
def is_canceled(self): return False
def close(self): pass
class _DummyBbox:
def xMinimum(self): return 414000.0
def yMinimum(self): return 5657000.0
def xMaximum(self): return 415000.0
def yMaximum(self): return 5658000.0
class _DummyGmlFeld:
def __init__(self, name):
self._name = name
def name(self):
return self._name
class _DummyGmlFeature:
def __init__(self, gehoert_zu_wert):
self._wert = gehoert_zu_wert
def geometry(self):
return object()
def __getitem__(self, key):
if key == "gehoertZu":
return self._wert
raise KeyError(key)
class _DummyGmlLayer:
"""Minimaler Ersatz für das Ergebnis von _parse_alkis_gml_memory_layer()."""
def __init__(self, feature):
self._feature = feature
def fields(self):
return [_DummyGmlFeld("gehoertZu")]
def getFeatures(self):
return [self._feature]
class TestGrenzpunkteGemarkungsweiseStil(unittest.TestCase):
@patch(_G_PATCH_APPLY_STIL)
@patch(_G_PATCH_QGSFEATURE)
@patch(_G_PATCH_QGSFIELD)
@patch(_G_PATCH_PROGRESS)
@patch(_G_PATCH_PROJECT)
@patch(_G_PATCH_WFS_LAYER)
@patch(_G_PATCH_ERSTELLE_JOINED)
@patch(_G_PATCH_LADE_GP_DICT)
@patch(_G_PATCH_EXTRAHIERE_ID)
@patch(_G_PATCH_ERKENNE_FELD)
@patch(_G_PATCH_PARSE_GML)
@patch(_G_PATCH_GET_GML)
@patch(_G_PATCH_HITS)
@patch(_G_PATCH_EXTENTS)
def test_stil_wird_nach_dem_laden_auf_den_ergebnis_layer_angewendet(
self,
mock_extents,
mock_hits,
mock_get_gml,
mock_parse_gml,
mock_erkenne_feld,
mock_extrahiere_id,
mock_lade_gp_dict,
mock_erstelle_joined,
mock_wfs_layer_cls,
mock_project_cls,
mock_progress,
mock_qgsfield,
mock_qgsfeature,
mock_apply_style,
):
"""Prüft, dass grenzpunkte_gemarkungsweise() nach dem Laden apply_style() mit dem neuen ALKIS-Grenzpunkt-Stil auf den fertigen Join-Layer anwendet (#53)."""
mock_extents.return_value = {"1234": _DummyBbox()}
mock_hits.return_value = 1
mock_get_gml.return_value = b"<fake/>"
mock_parse_gml.return_value = _DummyGmlLayer(_DummyGmlFeature("urn:adv:oid:GP1"))
mock_erkenne_feld.return_value = "gehoertZu"
mock_extrahiere_id.return_value = "PTA1"
mock_lade_gp_dict.return_value = ({"GP1": object()}, ["gp_feld"])
joined_layer = MagicMock()
mock_erstelle_joined.return_value = joined_layer
mock_wfs_layer_cls.return_value = MagicMock()
project = MagicMock()
mock_project_cls.instance.return_value = project
mock_progress.return_value = _DummyProgress()
ergebnis = grenzpunkte_gemarkungsweise(None, ["1234"])
self.assertIs(ergebnis, joined_layer)
project.addMapLayer.assert_called_once_with(joined_layer)
mock_apply_style.assert_called_once_with(joined_layer, "alkis_grenzpunkte.qml")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+164
View File
@@ -81,6 +81,30 @@ class _DummyCombo:
self.layer = layer self.layer = layer
class _DummyGemFeature:
def __init__(self, schluessel, name):
self._schluessel = schluessel
self._name = name
def __getitem__(self, key):
if key == "schluessel":
return self._schluessel
if key == "ave_name":
return self._name
return None
class _DummyGemarkungenLayer:
def __init__(self, selected):
self._selected = list(selected)
def selectedFeatureCount(self):
return len(self._selected)
def selectedFeatures(self):
return list(self._selected)
class TestWorkingTab(unittest.TestCase): class TestWorkingTab(unittest.TestCase):
def _make_tab(self): def _make_tab(self):
@@ -96,6 +120,7 @@ class TestWorkingTab(unittest.TestCase):
tab.haken_flurst = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None) tab.haken_flurst = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None)
tab.haken_gem = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None) tab.haken_gem = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None)
tab.haken_grenz = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None) tab.haken_grenz = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None)
tab.btn_flurst_aus_gemarkungen = _DummyButton()
tab.setze_haken = lambda *_args, **_kwargs: None tab.setze_haken = lambda *_args, **_kwargs: None
# Pruefmanager-Mock: verarbeite() wird im Test ggf. überschrieben # Pruefmanager-Mock: verarbeite() wird im Test ggf. überschrieben
tab.pruefmanager = types.SimpleNamespace( tab.pruefmanager = types.SimpleNamespace(
@@ -175,6 +200,83 @@ class TestWorkingTab(unittest.TestCase):
mock_set_layer_id.assert_called_once_with("") mock_set_layer_id.assert_called_once_with("")
# ------------------------------------------------------------------
# handle_flurst_alkis / handle_flurst_aus_gemarkungen (#47: eine gemeinsame
# Ladelogik in flurstueck_alkis(), hier wird nur noch die Anbindung geprüft)
# ------------------------------------------------------------------
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.success")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_flurst_alkis_first_load_uebernimmt_layer(
self, mock_iface, mock_project_instance, mock_flurstueck_alkis, mock_apply_style, mock_success
):
"""Erstmaliges Laden über 'Flurstücke laden' fügt den Layer dem Projekt hinzu."""
tab = self._make_tab()
mock_iface.mapCanvas.return_value = _DummyCanvas()
project = _DummyProject()
mock_project_instance.return_value = project
layer = _DummyLayer(valid=True, layer_id="flst_first")
mock_flurstueck_alkis.return_value = (layer, LoadStatus.FIRST)
tab.handle_flurst_alkis()
mock_flurstueck_alkis.assert_called_once_with(tab)
self.assertIn(layer, project.added)
mock_success.assert_called_once()
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.success")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_flurst_aus_gemarkungen_ruft_flurstueck_alkis_ohne_schluessel_abfrage(
self, mock_iface, mock_project_instance, mock_flurstueck_alkis, mock_success
):
"""Der Gemarkungen-Auswahl-Button ruft dieselbe Ladefunktion mit ohne_schluessel_abfrage=True auf (#47)."""
tab = self._make_tab()
mock_iface.mapCanvas.return_value = _DummyCanvas()
gem_feat = _DummyGemFeature("110001", "Musterstadt")
gem_layer = _DummyGemarkungenLayer([gem_feat])
project = types.SimpleNamespace(
mapLayersByName=lambda name: [gem_layer] if name == "Gemarkungen" else []
)
mock_project_instance.return_value = project
layer = _DummyLayer(valid=True, layer_id="flst_append")
mock_flurstueck_alkis.return_value = (layer, LoadStatus.APPEND)
tab.handle_flurst_aus_gemarkungen()
mock_flurstueck_alkis.assert_called_once()
_args, kwargs = mock_flurstueck_alkis.call_args
self.assertEqual(kwargs.get("vorgabe_schluessel"), ["0001"])
self.assertEqual(kwargs.get("vorgabe_namen"), ["Musterstadt"])
self.assertTrue(kwargs.get("ohne_schluessel_abfrage"))
mock_success.assert_called_once()
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.error")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.success")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
def test_handle_flurst_aus_gemarkungen_abbruch_zeigt_keine_zusaetzliche_meldung(
self, mock_project_instance, mock_flurstueck_alkis, mock_success, mock_error
):
"""Bricht flurstueck_alkis() ab (None, NONE), zeigt der Button keine eigene Meldung – flurstueck_alkis() hat das schon getan (#47)."""
tab = self._make_tab()
gem_feat = _DummyGemFeature("110001", "Musterstadt")
gem_layer = _DummyGemarkungenLayer([gem_feat])
project = types.SimpleNamespace(
mapLayersByName=lambda name: [gem_layer] if name == "Gemarkungen" else []
)
mock_project_instance.return_value = project
mock_flurstueck_alkis.return_value = (None, LoadStatus.NONE)
tab.handle_flurst_aus_gemarkungen()
mock_success.assert_not_called()
mock_error.assert_not_called()
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# handle_grenz_alkis # handle_grenz_alkis
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -822,6 +924,68 @@ class TestHandleVerfShape(unittest.TestCase):
self.assertIn(False, busy_states, "Button muss am Ende wieder freigegeben werden") self.assertIn(False, busy_states, "Button muss am Ende wieder freigegeben werden")
class TestZoomImProjektKbs(unittest.TestCase):
"""Zoom auf das neue Verfahrensgebiet rechnet in das KBS der Kartenansicht um (Basis #99)."""
def _tab(self):
tab = TestWorkingTab._make_tab(TestWorkingTab())
tab.setze_haken = lambda *_: None
tab._on_verf_layer_changed = lambda *_: None
return tab
def _projekt(self, mock_project_instance):
proj = _DummyProject()
proj.mapLayersByName = lambda _n: []
proj.mapLayer = lambda _lid: None
mock_project_instance.return_value = proj
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.zoom_auf_layer")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.success")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_alkis_komplett")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
def test_alkis_zoom_ueber_zoom_auf_layer(self, _set_id, mock_iface, mock_alkis, mock_projekt,
_stil, _erfolg, mock_zoom):
"""Prüft, dass nach dem Laden aus ALKIS über zoom_auf_layer auf das Verfahrensgebiet gezoomt wird und nicht mit der unumgerechneten Layer-Ausdehnung."""
tab = self._tab()
enriched = _DummyLayer(valid=True, layer_id="vg")
mock_alkis.return_value = (enriched, _DummyLayer(layer_id="flst"), None, LoadStatus.FIRST)
self._projekt(mock_projekt)
canvas = _DummyCanvas()
mock_iface.mapCanvas.return_value = canvas
tab.handle_verf_alkis()
mock_zoom.assert_called_once_with(enriched, canvas)
self.assertIsNone(canvas.extent)
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.zoom_auf_layer")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.success")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
def test_shape_zoom_ueber_zoom_auf_layer(self, _set_id, mock_iface, mock_shape, mock_projekt,
_stil, _erfolg, mock_zoom, _var):
"""Prüft, dass nach dem Laden aus einem Shape über zoom_auf_layer auf das Verfahrensgebiet gezoomt wird und nicht mit der unumgerechneten Layer-Ausdehnung."""
tab = self._tab()
tab.btn_verf_shape = _DummyButton()
enriched = _DummyLayer(valid=True, layer_id="shape_vg")
mock_shape.return_value = (enriched, _DummyLayer(layer_id="flst"), None, LoadStatus.FIRST)
self._projekt(mock_projekt)
canvas = _DummyCanvas()
mock_iface.mapCanvas.return_value = canvas
tab.handle_verf_shape()
mock_zoom.assert_called_once_with(enriched, canvas)
self.assertIsNone(canvas.extent)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+1 -1
View File
@@ -38,7 +38,7 @@ class DockWidget(BaseDockWidget):
self._init_tabs_with_services() self._init_tabs_with_services()
def _init_tabs_with_services(self): def _init_tabs_with_services(self):
"""Tabs neu aufbauen und Services (z.B. fuer PrintTab) injizieren.""" """Tabs neu aufbauen und Services (z.B. für PrintTab) injizieren."""
try: try:
self.setWidget(None) self.setWidget(None)
+21 -73
View File
@@ -5,7 +5,7 @@ Verwaltet die Verfahrensgebiet-Abgrenzung und ALKIS-Integration.
from sn_basis.functions.qt_wrapper import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QMessageBox from sn_basis.functions.qt_wrapper import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QMessageBox
from sn_basis.functions.qt_wrapper import Qt from sn_basis.functions.qt_wrapper import Qt
from sn_basis.functions import Qgis, QgsProject #QgsMessageLog from sn_basis.functions import Qgis, QgsProject #QgsMessageLog
from sn_basis.functions.qgisui_wrapper import QgsMapLayerComboBox from sn_basis.functions.qgisui_wrapper import QgsMapLayerComboBox, zoom_auf_layer
from sn_basis.functions.qgiscore_wrapper import QgsMapLayerProxyModel from sn_basis.functions.qgiscore_wrapper import QgsMapLayerProxyModel
from sn_basis.functions.verfahrensgebiet_manager import get_verfahrensgebiet_layer_id, set_verfahrensgebiet_layer_id from sn_basis.functions.verfahrensgebiet_manager import get_verfahrensgebiet_layer_id, set_verfahrensgebiet_layer_id
from sn_basis.functions.dialog_wrapper import show_info_dialog, create_progress_dialog, ask_yes_no from sn_basis.functions.dialog_wrapper import show_info_dialog, create_progress_dialog, ask_yes_no
@@ -15,12 +15,7 @@ from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
from qgis.utils import iface from qgis.utils import iface
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus
from sn_verfahrensgebiet.functions.flurstueck_alkis import ( from sn_verfahrensgebiet.functions.flurstueck_alkis import flurstueck_alkis
flurstueck_alkis,
lade_flurstuecke_per_gemarkung,
ALKIS_SF_URL,
FLST_LAYER_NAME,
)
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import ( from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import (
verfahrensgebiet_alkis_komplett, verfahrensgebiet_alkis_komplett,
BROR_LAYER_NAME, BROR_LAYER_NAME,
@@ -230,8 +225,8 @@ class WorkingTab(QWidget):
if not project.mapLayer(enriched_layer.id()): if not project.mapLayer(enriched_layer.id()):
project.addMapLayer(enriched_layer) project.addMapLayer(enriched_layer)
apply_style(enriched_layer, "verfahrensgebiet.qml") apply_style(enriched_layer, "verfahrensgebiet.qml")
iface.mapCanvas().setExtent(enriched_layer.extent()) # Ausdehnung ins KBS der Kartenansicht umrechnen (Basis #99)
iface.mapCanvas().refresh() zoom_auf_layer(enriched_layer, iface.mapCanvas())
self.setze_haken(self.haken_verf, True) self.setze_haken(self.haken_verf, True)
self.verf_layer_combo.setLayer(enriched_layer) self.verf_layer_combo.setLayer(enriched_layer)
@@ -287,8 +282,8 @@ class WorkingTab(QWidget):
if not project.mapLayer(enriched_layer.id()): if not project.mapLayer(enriched_layer.id()):
project.addMapLayer(enriched_layer) project.addMapLayer(enriched_layer)
apply_style(enriched_layer, "verfahrensgebiet.qml") apply_style(enriched_layer, "verfahrensgebiet.qml")
iface.mapCanvas().setExtent(enriched_layer.extent()) # Ausdehnung ins KBS der Kartenansicht umrechnen (Basis #99)
iface.mapCanvas().refresh() zoom_auf_layer(enriched_layer, iface.mapCanvas())
self.setze_haken(self.haken_verf, True) self.setze_haken(self.haken_verf, True)
self.verf_layer_combo.setLayer(enriched_layer) self.verf_layer_combo.setLayer(enriched_layer)
@@ -392,17 +387,10 @@ class WorkingTab(QWidget):
# Flurstücke-Handler # Flurstücke-Handler
# ------------------------------------------------------------------ # ------------------------------------------------------------------
def handle_flurst_alkis(self) -> None: def _uebernimm_flurstuecke_ergebnis(self, layer, status) -> None:
"""Lädt Flurstücke aus ALKIS-WFS anhand von Gemarkungsschlüsseln.""" """Übernimmt das Ergebnis von flurstueck_alkis() ins Projekt (FIRST/RELOAD/APPEND/KEEP)."""
self._set_busy(self.btn_flurst_alkis, True, "Aus ALKIS laden")
try:
layer, status = flurstueck_alkis(self)
if not layer or not layer.isValid():
return
if status in (LoadStatus.FIRST, LoadStatus.RELOAD): if status in (LoadStatus.FIRST, LoadStatus.RELOAD):
QgsProject.instance().addMapLayer(layer) QgsProject.instance().addMapLayer(layer)
# iface.mapCanvas().setExtent(layer.extent()) # Kartenausschnitt bleibt unverändert
apply_style(layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml") apply_style(layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
iface.mapCanvas().refresh() iface.mapCanvas().refresh()
self.setze_haken(self.haken_flurst, True) self.setze_haken(self.haken_flurst, True)
@@ -418,6 +406,14 @@ class WorkingTab(QWidget):
iface.mapCanvas().refresh() iface.mapCanvas().refresh()
success("Flurstücke", "Features wurden an den vorhandenen Layer angehängt.", duration=5) success("Flurstücke", "Features wurden an den vorhandenen Layer angehängt.", duration=5)
def handle_flurst_alkis(self) -> None:
"""Lädt Flurstücke aus ALKIS-WFS anhand von Gemarkungsschlüsseln."""
self._set_busy(self.btn_flurst_alkis, True, "Aus ALKIS laden")
try:
layer, status = flurstueck_alkis(self)
if not layer or not layer.isValid():
return
self._uebernimm_flurstuecke_ergebnis(layer, status)
finally: finally:
self._set_busy(self.btn_flurst_alkis, False, "Aus ALKIS laden") self._set_busy(self.btn_flurst_alkis, False, "Aus ALKIS laden")
@@ -491,68 +487,20 @@ class WorkingTab(QWidget):
) )
return return
# Prüfen ob bereits ein Flurstücke-Layer vorhanden → 3-Knopf-Dialog # Flurstücke laden – Schlüssel bereits bekannt, aber bei bestehendem Layer
existing_flst = project.mapLayersByName(FLST_LAYER_NAME) # trotzdem interaktiv nachfragen (Überschreiben/Anhängen/Abbrechen, inkl. Dedup)
if existing_flst:
msg_box = QMessageBox(self)
msg_box.setWindowTitle("Layer bereits vorhanden")
msg_box.setText(
f"Der Layer '{FLST_LAYER_NAME}' existiert bereits.\nWas soll geschehen?"
)
btn_ueberschreiben = msg_box.addButton("Überschreiben", QMessageBox.ButtonRole.DestructiveRole)
btn_anhaengen = msg_box.addButton("Anhängen", QMessageBox.ButtonRole.AcceptRole)
btn_abbrechen = msg_box.addButton("Abbrechen", QMessageBox.ButtonRole.RejectRole)
msg_box.setDefaultButton(btn_abbrechen)
msg_box.exec()
clicked = msg_box.clickedButton()
if clicked is btn_abbrechen:
return
if clicked is btn_anhaengen:
temp_layer = lade_flurstuecke_per_gemarkung(vorgabe_schluessel)
if not temp_layer or not temp_layer.isValid() or temp_layer.featureCount() == 0:
error(
"Flurstücke",
f"Flurstücke konnten nicht geladen werden – WFS-Dienst ({ALKIS_SF_URL}) "
f"nicht erreichbar oder keine Features für die gewählten Gemarkungen.",
duration=0,
)
return
existing_flst[0].dataProvider().addFeatures(list(temp_layer.getFeatures()))
existing_flst[0].updateExtents()
iface.mapCanvas().refresh()
self.setze_haken(self.haken_flurst, True)
success("Flurstücke", "Features wurden an den vorhandenen Layer angehängt.", duration=5)
return
# else: Überschreiben → fall-through zum flurstueck_alkis(ohne_dialog=True)-Aufruf
# Flurstücke direkt laden (kein Dialog)
layer, status = flurstueck_alkis( layer, status = flurstueck_alkis(
self, self,
vorgabe_schluessel=vorgabe_schluessel, vorgabe_schluessel=vorgabe_schluessel,
vorgabe_namen=vorgabe_namen, vorgabe_namen=vorgabe_namen,
ohne_dialog=True, ohne_schluessel_abfrage=True,
) )
if not layer or not layer.isValid(): if not layer or not layer.isValid():
error( # flurstueck_alkis() hat bei Abbruch/Fehler bereits selbst eine Meldung gezeigt.
"Flurstücke",
f"Flurstücke konnten nicht geladen werden – WFS-Dienst ({ALKIS_SF_URL}) "
f"nicht erreichbar oder keine Features für die gewählten Gemarkungen.",
duration=0,
)
return return
if status in (LoadStatus.FIRST, LoadStatus.RELOAD): self._uebernimm_flurstuecke_ergebnis(layer, status)
project.addMapLayer(layer)
apply_style(layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
iface.mapCanvas().refresh()
self.setze_haken(self.haken_flurst, True)
msg = "erstmalig geladen" if status == LoadStatus.FIRST else "neu geladen"
success("Flurstücke", f"Layer wurde {msg}.", duration=5)
elif status == LoadStatus.KEEP:
self.setze_haken(self.haken_flurst, True)
info("Flurstücke", "Vorhandener Layer wurde behalten.", duration=4)
finally: finally:
self._set_busy(self.btn_flurst_aus_gemarkungen, False, "aus gewählten Gemarkungen laden") self._set_busy(self.btn_flurst_aus_gemarkungen, False, "aus gewählten Gemarkungen laden")