Compare commits

..
Author SHA1 Message Date
Daniel bcbfa4c49e QgsMaxVersion ergänzt
Release Plugin / release (push) Successful in 3s
2026-04-22 09:03:23 +02:00
release-bot 610e70ec5e Release 26.4.4-unstable 2026-04-22 06:44:01 +00:00
release-bot 1d7489f4d6 Release 26.4.3-unstable
Release Plugin / release (push) Successful in 3s
2026-04-21 20:22:04 +00:00
Daniel f1b463a1c5 Merge pull request 'Verfahrensgebiet aus Auswahl ergänzt' (#9) from feature/Verfahrensgebiet_bilden into unstable
Release Plugin / release (push) Successful in 4s
Reviewed-on: #9
2026-04-21 22:20:03 +02:00
Daniel 189855e63b Flst_Laden, VerfGebiet bilden 2026-04-21 21:50:26 +02:00
Daniel 72c9d6fa15 tests überarbeitet 2026-04-20 15:36:18 +02:00
Daniel 9caf0e3c86 pdock-dockstrings ergänzt 2026-04-20 14:33:13 +02:00
release-bot f6591b2099 Release 26.4.2-testing 2026-04-10 09:34:10 +00:00
release-bot 1a2d3109d4 Release 26.4.1-unstable
Release Plugin / release (push) Successful in 4s
2026-04-08 12:52:25 +00:00
Daniel 41b272092e Merge pull request 'verfahrensgebiet-Vaiable wird sauber gesetzt' (#8) from bug/Verfahrensgebiet_nicht_übernommen into unstable
Release Plugin / release (push) Successful in 4s
Reviewed-on: #8
2026-04-08 14:38:20 +02:00
Daniel 5be2d3b4f9 verfahrensgebiet-Vaiable wird sauber gesetzt 2026-04-08 14:36:39 +02:00
release-bot 62fae1c431 Release 26.3.3-unstable 2026-03-27 10:28:23 +00:00
Daniel 5531db9071 Merge pull request 'Verfahrensgebiet-Layerauswahl nach sn_verfahrensgebiet verschoben' (#7) from refactor/Verfahrensgebiet-Auswahl_nach_sn_Verfahrensgebiet into unstable
Release Plugin / release (push) Successful in 4s
Reviewed-on: #7
2026-03-27 11:27:13 +01:00
Daniel 1796a7d5c0 Verfahrensgebiet-Layerauswahl nach sn_verfahrensgebiet verschoben 2026-03-27 11:19:07 +01:00
release-bot 98077e47cf Release 26.3.2-unstable 2026-03-26 20:48:56 +00:00
Daniel df0ebc5543 Merge pull request 'printtab aus sn_basis eingefügt' (#6) from refactor/Druckfunktion-in-sn_basis2 into unstable
Release Plugin / release (push) Successful in 3s
Reviewed-on: #6
2026-03-26 21:42:25 +01:00
Daniel e4de5950a7 printtab aus sn_basis eingefügt 2026-03-26 20:16:02 +01:00
release-bot 52cf55d242 Release 26.3.1-unstable 2026-03-25 08:10:05 +00:00
18 changed files with 2981 additions and 122 deletions
+14
View File
@@ -1,3 +1,17 @@
"""sn_verfahrensgebiet – QGIS-Plugin für Verfahrensgebiets-Management.
Verwaltet die Abgrenzung und Verwaltung von Verfahrensgebieten.
"""
def classFactory(iface):
"""Factory-Funktion für QGIS.
Wird von QGIS aufgerufen, um die Plugin-Instanz zu erstellen.
Args:
iface: QGIS-Schnittstellenobjekt
Returns:
Verfahrensgebiet: Instanziiertes Plugin
"""
from .main import Verfahrensgebiet
return Verfahrensgebiet(iface)
+14
View File
@@ -1 +1,15 @@
---
Version 26.4.4-unstable:
---
Version 26.4.3-unstable:
---
Version 26.4.2-testing:
---
Version 26.4.1-unstable:
---
Version 26.3.3-unstable:
---
Version 26.3.2-unstable:
---
Version 26.3.1-unstable:
- erster Test als echtes PlugIn
+113
View File
@@ -0,0 +1,113 @@
"""sn_verfahrensgebiet/functions/flurstueck_alkis.py – ALKIS-Integration für Flurstücke.
Lädt Flurstücke aus dem ALKIS-WFS-Dienst (adv:AX_Flurstueck) anhand von
Gemarkungsschlüsseln, die der Nutzer interaktiv eingibt.
Der WFS-Layer wird direkt (nicht als Memory-Layer) ins Projekt geladen,
da die einzelnen Features für die spätere Auswahl benötigt werden.
"""
from sn_basis.functions import QgsVectorLayer, QgsProject
from sn_basis.functions import QMessageBox
from sn_basis.functions.dialog_wrapper import ask_text
# LoadStatus aus verfahrensgebiet_alkis wiederverwenden (identische Semantik)
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus
ALKIS_NAS_URL = "https://geodienste.sachsen.de/aaa/public_alkis/nas/wfs"
FLST_TYPENAME = "adv:AX_Flurstueck"
FLST_LAYER_NAME = "Flurstücke"
def _check_existing_flurstueck_layer(tab_widget):
"""Prüft, ob ein Flurstück-Layer bereits im Projekt existiert.
Returns
-------
Tuple[Optional[QgsVectorLayer], LoadStatus]
Bei ``LoadStatus.KEEP`` enthält das Tuple den vorhandenen Layer,
bei ``LoadStatus.FIRST`` und ``LoadStatus.RELOAD`` ist es None.
"""
project = QgsProject.instance()
existing_layers = project.mapLayersByName(FLST_LAYER_NAME)
if not existing_layers:
return None, LoadStatus.FIRST
reply = QMessageBox.question(
tab_widget,
"Layer bereits vorhanden",
f"Der Layer '{FLST_LAYER_NAME}' existiert bereits.\n"
"Möchten Sie ihn löschen und neu laden?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No,
)
if reply == QMessageBox.StandardButton.Yes:
for lyr in existing_layers:
project.removeMapLayer(lyr.id())
return None, LoadStatus.RELOAD
else:
return existing_layers[0], LoadStatus.KEEP
def flurstueck_alkis(tab_widget):
"""Lädt Flurstücke aus ALKIS-WFS anhand von Gemarkungsschlüsseln.
Der Nutzer gibt einen oder mehrere Gemarkungsschlüssel (kommagetrennt) ein.
Der WFS wird per SQL-Filter auf
``gemarkung_ax_gemarkung_schluessel_gemarkungsnummer IN (...)`` abgefragt.
Parameters
----------
tab_widget :
Eltern-Widget für Dialoge.
Returns
-------
Tuple[Optional[QgsVectorLayer], LoadStatus]
Geladener WFS-Layer und Ladestatus.
Bei Fehlern ist der Layer None und der Status LoadStatus.NONE.
"""
# 1. Vorhandenen Layer prüfen
existing, status = _check_existing_flurstueck_layer(tab_widget)
if status == LoadStatus.KEEP:
return existing, status
# 2. Gemarkungsschlüssel abfragen
eingabe, ok = ask_text(
"Gemarkungsschlüssel",
"Gemarkungsschlüssel eingeben (mehrere mit Komma trennen):",
parent=tab_widget,
)
if not ok or not eingabe.strip():
QMessageBox.warning(
tab_widget,
"Abgebrochen",
"Bitte mindestens einen gültigen Gemarkungsschlüssel eingeben.",
)
return None, LoadStatus.NONE
schluessel_liste = [s.strip() for s in eingabe.split(",") if s.strip()]
if not schluessel_liste:
QMessageBox.warning(tab_widget, "Fehler", "Keine gültigen Schlüssel gefunden.")
return None, LoadStatus.NONE
schluessel_str = ", ".join(f"'{s}'" for s in schluessel_liste)
# 3. WFS-URI aufbauen und Layer laden
wfs_uri = (
f"url='{ALKIS_NAS_URL}' typename='{FLST_TYPENAME}' srsname='EPSG:25833' "
f"sql=SELECT * FROM AX_Flurstueck "
f"WHERE gemarkung_ax_gemarkung_schluessel_gemarkungsnummer IN ({schluessel_str})"
)
layer = QgsVectorLayer(wfs_uri, FLST_LAYER_NAME, "WFS")
if not layer.isValid():
QMessageBox.critical(
tab_widget,
"Fehler",
"Flurstück-Layer konnte nicht geladen werden.\n"
"Bitte Gemarkungsschlüssel und Netzwerkverbindung prüfen.",
)
return None, LoadStatus.NONE
return layer, status
+28 -1
View File
@@ -1,4 +1,7 @@
#sn_verfahrensgebiet/functions/verfahrensgebiet_alkis
"""sn_verfahrensgebiet/functions/verfahrensgebiet_alkis.py – ALKIS-Integration.
Lädt Verfahrensgebiete aus dem ALKIS-WFS-Dienst.
"""
from enum import Enum
from sn_basis.functions import (
QgsVectorLayer, QgsProject, QgsFeature, QgsField, QgsGeometry
@@ -11,6 +14,14 @@ alkis_NAS_url = "https://geodienste.sachsen.de/aaa/public_alkis/nas/wfs"
typename = "adv:AX_BauRaumOderBodenordnungsrecht"
class LoadStatus(Enum):
"""Status eines Layer-Ladevorgangs.
Attributes:
NONE: Nichts geladen
FIRST: Erstmalig geladen
RELOAD: Neu geladen
KEEP: Vorhandener Layer behalten
"""
NONE = "none" # nichts geladen
FIRST = "first" # erstmalig geladen
RELOAD = "reload" # neu geladen
@@ -18,6 +29,14 @@ class LoadStatus(Enum):
def _check_existing_layer(tab_widget):
"""Prüft, ob ein Verfahrensgebiet-Layer bereits existiert.
Args:
tab_widget: Parent-Widget für Dialoge
Returns:
Tuple[Optional[QgsVectorLayer], LoadStatus]: Layer und Status
"""
project = QgsProject.instance()
existing_layers = project.mapLayersByName("Verfahrensgebiet")
if not existing_layers:
@@ -40,6 +59,14 @@ def _check_existing_layer(tab_widget):
def verfahrensgebiet_alkis(tab_widget):
"""Lädt Verfahrensgebiet aus ALKIS-WFS basierend auf Verfahrensnummer.
Args:
tab_widget: Parent-Widget für Dialoge und Meldungen
Returns:
Tuple[Optional[QgsVectorLayer], LoadStatus]: Geladener Layer und Status
"""
verfahrensnummer = get_variable("verfahrensnummer")
if not verfahrensnummer:
QMessageBox.critical(tab_widget, "Fehler",
+358
View File
@@ -0,0 +1,358 @@
"""sn_verfahrensgebiet/functions/verfahrensgebiet_edit_service.py
Service-Modul für Verfahrensgebiet-Editoperationen aus Flurstück-Auswahl.
Der Verfahrensgebiet-Layer ist ein Single-Feature-Memory-Layer: alle
Flurstück-Geometrien werden via ``QgsGeometry.unaryUnion`` zu einem einzigen
(Multi-)Polygon verschmolzen und als ein Feature gespeichert. Dadurch
erscheint das Verfahrensgebiet im Kartenfenster als zusammenhängendes Objekt.
Die Flurstückskennzeichen werden als kommagetrennte Zeichenkette im
Attributfeld ``flurstueckskennzeichen`` des Single-Features gespeichert,
sodass Delta-Operationen (Ergänzen/Entfernen) ohne erneuten Zugriff auf den
Quell-Flurstück-Layer möglich bleiben und die ID-Menge via
``get_kennzeichen_menge()`` jederzeit abrufbar ist.
"""
from typing import Optional, Tuple, List
from sn_basis.functions import QgsVectorLayer, QgsFeature, QgsField
from sn_basis.functions.qgiscore_wrapper import QgsGeometry
from sn_basis.functions.qt_wrapper import QVariant
from sn_basis.modules.layerpruefer import Layerpruefer
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
VERF_LAYER_NAME = "Verfahrensgebiet"
FELD_KENNZEICHEN = "flurstueckskennzeichen"
FELD_GEMARKUNG = "gemarkung_ax_gemarkung_schluessel_gemarkungsnummer"
PFLICHTFELDER: List[str] = [FELD_KENNZEICHEN, FELD_GEMARKUNG]
# ---------------------------------------------------------------------------
# Vorbedingungsprüfungen
# ---------------------------------------------------------------------------
def pruefe_flurstueck_layer(layer) -> pruef_ergebnis:
"""Prüft, ob ein Layer als Flurstück-Quelle geeignet ist.
Prüfreihenfolge via Layerpruefer:
existiert → Vektorlayer → Polygon-Geometrie → nicht leer → Pflichtfelder vorhanden.
Returns
-------
pruef_ergebnis
ok=True, wenn alle Prüfungen bestanden wurden.
"""
pruefer = Layerpruefer(
layer=layer,
erwarteter_layertyp="vector",
erwarteter_geotyp="Polygon",
erforderliche_felder=PFLICHTFELDER,
)
return pruefer.pruefe()
def pruefe_auswahl(layer) -> pruef_ergebnis:
"""Prüft, ob auf dem Layer mindestens ein Feature ausgewählt ist.
Returns
-------
pruef_ergebnis
ok=True bei mindestens einer Auswahl, sonst ok=False mit aktion ``layer_leer``.
"""
try:
count = int(layer.selectedFeatureCount())
except Exception:
count = 0
if count == 0:
return pruef_ergebnis(
ok=False,
meldung="Es sind keine Flurstücke ausgewählt. Bitte zuerst eine Auswahl treffen.",
aktion="layer_leer",
kontext=None,
)
return pruef_ergebnis(
ok=True,
meldung=f"{count} Flurstücke ausgewählt.",
aktion="ok",
kontext=None,
)
# ---------------------------------------------------------------------------
# Hilfsfunktionen
# ---------------------------------------------------------------------------
def get_kennzeichen_menge(layer) -> set:
"""Gibt die Menge aller ``flurstueckskennzeichen`` aus dem Layer zurück.
Im Single-Feature-Modell sind die Kennzeichen als kommagetrennte
Zeichenkette im einzigen Feature gespeichert.
"""
result = set()
for feat in layer.getFeatures():
val = feat[FELD_KENNZEICHEN]
if val:
for kz in str(val).split(","):
kz = kz.strip()
if kz:
result.add(kz)
return result
def _erstelle_memory_layer(crs_authid: str) -> QgsVectorLayer:
"""Erzeugt einen leeren Multi-Feature-Memory-Layer für das Verfahrensgebiet."""
layer = QgsVectorLayer(
f"MultiPolygon?crs={crs_authid}", VERF_LAYER_NAME, "memory"
)
pr = layer.dataProvider()
pr.addAttributes([
QgsField(FELD_KENNZEICHEN, QVariant.String),
QgsField(FELD_GEMARKUNG, QVariant.String),
])
layer.updateFields()
return layer
# ---------------------------------------------------------------------------
# Hauptoperationen
# ---------------------------------------------------------------------------
def erstelle_verfahrensgebiet_aus_auswahl(
source_layer,
) -> Tuple[Optional[object], int, int]:
"""Erstellt neuen Verfahrensgebiet-Memory-Layer aus der aktuellen Auswahl.
Alle Geometrien werden via ``QgsGeometry.unaryUnion`` zu einem einzigen
(Multi-)Polygon verschmolzen. Der Layer enthält danach genau ein Feature.
Die Flurstückskennzeichen werden kommagetrennt im Attribut gespeichert.
Parameters
----------
source_layer :
Flurstück-Layer mit aktiver Auswahl.
Returns
-------
Tuple[layer, erfolgreich, fehlgeschlagen]
"""
crs = source_layer.crs().authid()
mem_layer = _erstelle_memory_layer(crs)
pr = mem_layer.dataProvider()
erfolgreich = 0
fehlgeschlagen = 0
geom_list = []
kz_list = []
gemarkung_set = set()
for src_feat in source_layer.selectedFeatures():
try:
geom = src_feat.geometry()
if not geom or geom.isEmpty():
fehlgeschlagen += 1
continue
geom_list.append(geom)
kz = str(src_feat[FELD_KENNZEICHEN] or "")
gm = str(src_feat[FELD_GEMARKUNG] or "")
if kz:
kz_list.append(kz)
if gm:
gemarkung_set.add(gm)
erfolgreich += 1
except Exception:
fehlgeschlagen += 1
if geom_list:
union_geom = QgsGeometry.unaryUnion(geom_list)
if union_geom and not union_geom.isEmpty():
feat = QgsFeature()
feat.setGeometry(union_geom)
feat.setAttributes([
",".join(kz_list),
",".join(sorted(gemarkung_set)),
])
pr.addFeatures([feat])
mem_layer.updateExtents()
return mem_layer, erfolgreich, fehlgeschlagen
def ergaenze_verfahrensgebiet(
verf_layer,
source_layer,
) -> Tuple[object, int, int, int]:
"""Fügt nur neue (noch nicht enthaltene) Flurstücke zum Verfahrensgebiet hinzu.
Die Geometrien der neuen Flurstücke werden mit der bestehenden Union-Geometrie
verschmolzen. Das Single-Feature wird an Ort und Stelle aktualisiert.
Parameters
----------
verf_layer :
Bestehender Verfahrensgebiet-Memory-Layer (Single-Feature).
source_layer :
Flurstück-Layer mit aktiver Auswahl.
Returns
-------
Tuple[verf_layer, hinzugefuegt, bereits_vorhanden, fehlgeschlagen]
"""
vorhandene_ids = get_kennzeichen_menge(verf_layer)
pr = verf_layer.dataProvider()
hinzugefuegt = 0
bereits_vorhanden = 0
fehlgeschlagen = 0
neue_geoms = []
neue_kz = []
neue_gemarkungen: set = set()
for src_feat in source_layer.selectedFeatures():
try:
kz = str(src_feat[FELD_KENNZEICHEN] or "")
if kz in vorhandene_ids:
bereits_vorhanden += 1
continue
geom = src_feat.geometry()
if not geom or geom.isEmpty():
fehlgeschlagen += 1
continue
neue_geoms.append(geom)
if kz:
neue_kz.append(kz)
gm = str(src_feat[FELD_GEMARKUNG] or "")
if gm:
neue_gemarkungen.add(gm)
hinzugefuegt += 1
except Exception:
fehlgeschlagen += 1
if neue_geoms:
existing_feats = list(verf_layer.getFeatures())
if existing_feats:
existing_feat = existing_feats[0]
all_geoms = [existing_feat.geometry()] + neue_geoms
new_union = QgsGeometry.unaryUnion(all_geoms)
alle_kz = sorted(vorhandene_ids | set(neue_kz))
existing_gm_str = existing_feat[FELD_GEMARKUNG] or ""
alle_gm = sorted(
{g.strip() for g in existing_gm_str.split(",") if g.strip()} | neue_gemarkungen
)
kz_idx = verf_layer.fields().indexFromName(FELD_KENNZEICHEN)
gm_idx = verf_layer.fields().indexFromName(FELD_GEMARKUNG)
pr.changeAttributeValues({existing_feat.id(): {
kz_idx: ",".join(alle_kz),
gm_idx: ",".join(alle_gm),
}})
pr.changeGeometryValues({existing_feat.id(): new_union})
else:
# Kein vorhandenes Feature – neues anlegen
union_geom = QgsGeometry.unaryUnion(neue_geoms)
if union_geom and not union_geom.isEmpty():
feat = QgsFeature()
feat.setGeometry(union_geom)
feat.setAttributes([
",".join(sorted(neue_kz)),
",".join(sorted(neue_gemarkungen)),
])
pr.addFeatures([feat])
verf_layer.updateExtents()
verf_layer.triggerRepaint()
return verf_layer, hinzugefuegt, bereits_vorhanden, fehlgeschlagen
def entferne_aus_verfahrensgebiet(
verf_layer,
source_layer,
) -> Tuple[object, int, int, int]:
"""Entfernt Flurstücke aus dem Verfahrensgebiet.
Die Geometrien der zu entfernenden Flurstücke werden per ``difference()``
von der bestehenden Union-Geometrie subtrahiert.
Nicht enthaltene Flurstücke werden gezählt, aber ignoriert.
Parameters
----------
verf_layer :
Bestehender Verfahrensgebiet-Memory-Layer (Single-Feature).
source_layer :
Flurstück-Layer mit aktiver Auswahl.
Returns
-------
Tuple[verf_layer, entfernt, nicht_enthalten, fehlgeschlagen]
"""
existing_feats = list(verf_layer.getFeatures())
if not existing_feats:
auswahl_kz = {
str(f[FELD_KENNZEICHEN] or "")
for f in source_layer.selectedFeatures()
if f[FELD_KENNZEICHEN]
}
return verf_layer, 0, len(auswahl_kz), 0
existing_feat = existing_feats[0]
existing_kz_str = existing_feat[FELD_KENNZEICHEN] or ""
vorhandene_ids = {k.strip() for k in existing_kz_str.split(",") if k.strip()}
auswahl_kz = {
str(f[FELD_KENNZEICHEN] or "")
for f in source_layer.selectedFeatures()
if f[FELD_KENNZEICHEN]
}
nicht_enthalten = len(auswahl_kz - vorhandene_ids)
zu_entfernen_kz = auswahl_kz & vorhandene_ids
if not zu_entfernen_kz:
return verf_layer, 0, nicht_enthalten, 0
result_geom = existing_feat.geometry()
fehlgeschlagen = 0
tatsaechlich_entfernt = 0
for src_feat in source_layer.selectedFeatures():
kz = str(src_feat[FELD_KENNZEICHEN] or "")
if kz not in zu_entfernen_kz:
continue
try:
geom = src_feat.geometry()
if geom and not geom.isEmpty():
result_geom = result_geom.difference(geom)
tatsaechlich_entfernt += 1
except Exception:
fehlgeschlagen += 1
pr = verf_layer.dataProvider()
verbleibende_kz = sorted(vorhandene_ids - zu_entfernen_kz)
if not verbleibende_kz or result_geom is None or result_geom.isEmpty():
pr.deleteFeatures([existing_feat.id()])
else:
kz_idx = verf_layer.fields().indexFromName(FELD_KENNZEICHEN)
pr.changeGeometryValues({existing_feat.id(): result_geom})
pr.changeAttributeValues({existing_feat.id(): {
kz_idx: ",".join(verbleibende_kz),
}})
verf_layer.updateExtents()
verf_layer.triggerRepaint()
return verf_layer, tatsaechlich_entfernt, nicht_enthalten, fehlgeschlagen
def ersetze_verfahrensgebiet(
source_layer,
) -> Tuple[Optional[object], int, int]:
"""Erstellt einen neuen Verfahrensgebiet-Layer aus der Auswahl (Ersetzen-Semantik).
Der Aufrufer ist verantwortlich, den alten Layer aus dem Projekt zu entfernen,
bevor der neue Layer hinzugefügt wird.
Returns
-------
Tuple[layer, erfolgreich, fehlgeschlagen]
Entspricht ``erstelle_verfahrensgebiet_aus_auswahl``.
"""
return erstelle_verfahrensgebiet_aus_auswahl(source_layer)
+17 -1
View File
@@ -1,10 +1,21 @@
"""sn_verfahrensgebiet/main.py – Einstiegspunkt des sn_verfahrensgebiet Plugins.
Orchestriert die Verwaltung von Verfahrensgebiets-Layern und deren Eigenschaften.
"""
from qgis.utils import plugins
from sn_basis.ui.dockmanager import DockManager
from .ui.dockwidget import DockWidget
from sn_basis.modules.DataGrabber import DataGrabber
from sn_basis.modules.Pruefmanager import Pruefmanager
class Verfahrensgebiet:
"""Einstiegspunkt des sn_verfahrensgebiet-Plugins.
Verwaltet die DockWidget-Initialisierung und das Verfahrensgebiets-Management.
"""
def __init__(self, iface):
self.iface = iface
self.pruefmanager = Pruefmanager(ui_modus="qgis")
self.data_grabber = DataGrabber(pruefmanager=self.pruefmanager)
self.action = None
self.dockwidget = None
@@ -50,7 +61,12 @@ class Verfahrensgebiet:
# if basis and basis.ui:
# basis.ui.set_active_plugin(self.action)
def run(self):
self.dockwidget = DockWidget(self.iface.mainWindow(), subtitle=self.plugin_name)
self.dockwidget = DockWidget(
self.iface.mainWindow(),
subtitle=self.plugin_name,
pruefmanager=self.pruefmanager,
data_grabber=self.data_grabber,
)
self.dockwidget.setObjectName(self.dock_name)
# Action-Referenz im Dock speichern
+2 -2
View File
@@ -1,12 +1,12 @@
[general]
version=
version=26.4.4-unstable
name=LNO Sachsen | Verfahrensgebiet
description=Plugin zum Erzeugen eines Objektes "Verfahrensgebiet"
author=Daniel Helbig, Michael Otto
homepage=https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Plugin_SN_Verfahrensgebiet
tracker=https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Plugin_SN_Verfahrensgebiet/issues
repository=https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Plugin_SN_Verfahrensgebiet/src/branch/unstable/
QGISMaximumVersion=4.99
qgisMinimumVersion=3.40
experimental=true
deprecated=false
+349
View File
@@ -0,0 +1,349 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis labelsEnabled="1" version="3.40.4-Bratislava" styleCategories="Symbology|Labeling">
<renderer-v2 enableorderby="0" type="singleSymbol" referencescale="-1" forceraster="0" symbollevels="0">
<symbols>
<symbol name="0" type="fill" force_rhr="0" clip_to_extent="1" is_animated="0" frame_rate="10" alpha="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>
<layer id="{bc287a30-ef97-44d4-ac42-1890985a8e6e}" class="SimpleFill" locked="0" enabled="1" 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="229,182,54,255,rgb:0.89803921568627454,0.71372549019607845,0.21176470588235294,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="35,35,35,255,rgb:0.13725490196078433,0.13725490196078433,0.13725490196078433,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="0.5"/>
<Option name="outline_width_unit" type="QString" value="MM"/>
<Option name="style" type="QString" value="no"/>
</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>
<rotation/>
<sizescale/>
<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 name="" type="fill" force_rhr="0" clip_to_extent="1" is_animated="0" frame_rate="10" alpha="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>
<layer id="{819785db-4b31-4c4c-8332-821c09f81921}" class="SimpleFill" locked="0" enabled="1" 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="0,0,255,255,rgb:0,0,1,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="35,35,35,255,rgb:0.13725490196078433,0.13725490196078433,0.13725490196078433,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="0.26"/>
<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>
</selectionSymbol>
</selection>
<labeling type="rule-based">
<rules key="{15ec465c-d050-4a64-b4ba-262af80bbb43}">
<rule key="{b56520a8-9937-40dd-b8fe-c4386271088d}" filter="&quot;flurstuecksnummer_ax_flurstuecksnummer_nenner&quot; IS NULL" description="FlstNr_ohne_Zusatz">
<settings calloutType="simple">
<text-style forcedItalic="0" fontUnderline="0" previewBkgrdColor="255,255,255,255,rgb:1,1,1,1" fontItalic="0" tabStopDistanceUnit="Point" textOpacity="1" multilineHeight="1" fontFamily="Times New Roman" fontStrikeout="0" capitalization="0" fieldName="flurstuecksnummer_ax_flurstuecksnummer_zaehler" legendString="Aa" fontSizeUnit="Point" fontWeight="50" textOrientation="horizontal" multilineHeightUnit="Percentage" fontKerning="1" tabStopDistanceMapUnitScale="3x:0,0,0,0,0,0" forcedBold="0" fontWordSpacing="0" fontLetterSpacing="0" fontSizeMapUnitScale="3x:0,0,0,0,0,0" allowHtml="0" isExpression="0" useSubstitutions="0" textColor="0,0,0,255,rgb:0,0,0,1" blendMode="0" fontSize="7" namedStyle="Standard" tabStopDistance="80">
<families/>
<text-buffer bufferBlendMode="0" bufferSizeUnits="MM" bufferOpacity="1" bufferJoinStyle="128" bufferNoFill="1" bufferSizeMapUnitScale="3x:0,0,0,0,0,0" bufferDraw="1" bufferColor="255,255,255,255,rgb:1,1,1,1" bufferSize="1"/>
<text-mask maskEnabled="0" maskSizeMapUnitScale="3x:0,0,0,0,0,0" maskSizeUnits="MM" maskJoinStyle="128" maskSize2="1.5" maskSize="1.5" maskOpacity="1" maskType="0" maskedSymbolLayers=""/>
<background shapeOffsetX="0" shapeRadiiY="0" shapeSVGFile="" shapeRotation="0" shapeBorderWidth="0" shapeRadiiX="0" shapeSizeX="0" shapeBorderWidthMapUnitScale="3x:0,0,0,0,0,0" shapeSizeUnit="MM" shapeRotationType="0" shapeBorderColor="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" shapeJoinStyle="64" shapeRadiiMapUnitScale="3x:0,0,0,0,0,0" shapeType="0" shapeOffsetMapUnitScale="3x:0,0,0,0,0,0" shapeDraw="0" shapeBorderWidthUnit="MM" shapeFillColor="255,255,255,255,rgb:1,1,1,1" shapeOffsetUnit="MM" shapeRadiiUnit="MM" shapeSizeMapUnitScale="3x:0,0,0,0,0,0" shapeOffsetY="0" shapeBlendMode="0" shapeSizeType="0" shapeSizeY="0" shapeOpacity="1">
<symbol name="markerSymbol" type="marker" force_rhr="0" clip_to_extent="1" is_animated="0" frame_rate="10" alpha="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>
<layer id="" class="SimpleMarker" locked="0" enabled="1" 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="141,90,153,255,rgb:0.55294117647058827,0.35294117647058826,0.59999999999999998,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.13725490196078433,0.13725490196078433,0.13725490196078433,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>
<symbol name="fillSymbol" type="fill" force_rhr="0" clip_to_extent="1" is_animated="0" frame_rate="10" alpha="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>
<layer id="" class="SimpleFill" locked="0" enabled="1" 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="255,255,255,255,rgb:1,1,1,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="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1"/>
<Option name="outline_style" type="QString" value="no"/>
<Option name="outline_width" type="QString" value="0"/>
<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>
</background>
<shadow shadowRadiusAlphaOnly="0" shadowOffsetAngle="135" shadowOpacity="0.69999999999999996" shadowOffsetUnit="MM" shadowColor="0,0,0,255,rgb:0,0,0,1" shadowRadiusMapUnitScale="3x:0,0,0,0,0,0" shadowRadius="1.5" shadowBlendMode="6" shadowScale="100" shadowDraw="0" shadowUnder="0" shadowOffsetDist="1" shadowRadiusUnit="MM" shadowOffsetMapUnitScale="3x:0,0,0,0,0,0" shadowOffsetGlobal="1"/>
<dd_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</dd_properties>
<substitutions/>
</text-style>
<text-format multilineAlign="3" autoWrapLength="0" reverseDirectionSymbol="0" plussign="0" rightDirectionSymbol=">" addDirectionSymbol="0" leftDirectionSymbol="&lt;" formatNumbers="0" decimals="3" placeDirectionSymbol="0" wrapChar="" useMaxLineLengthForAutoWrap="1"/>
<placement lineAnchorClipping="0" maximumDistanceMapUnitScale="3x:0,0,0,0,0,0" xOffset="0" maximumDistance="0" geometryGenerator="" distMapUnitScale="3x:0,0,0,0,0,0" predefinedPositionOrder="TR,TL,BR,BL,R,L,TSR,BSR" layerType="PolygonGeometry" lineAnchorPercent="0.5" rotationUnit="AngleDegrees" repeatDistanceMapUnitScale="3x:0,0,0,0,0,0" priority="9" yOffset="0" quadOffset="4" polygonPlacementFlags="2" centroidInside="1" placement="1" placementFlags="10" prioritization="PreferCloser" overrunDistance="0" offsetUnits="MM" maxCurvedCharAngleOut="-25" preserveRotation="1" lineAnchorTextPoint="CenterOfText" overlapHandling="PreventOverlap" rotationAngle="0" repeatDistance="0" offsetType="0" distUnits="MM" allowDegraded="0" overrunDistanceUnit="MM" centroidWhole="0" geometryGeneratorType="PointGeometry" fitInPolygonOnly="0" geometryGeneratorEnabled="0" dist="0" maxCurvedCharAngleIn="25" overrunDistanceMapUnitScale="3x:0,0,0,0,0,0" maximumDistanceUnit="MM" lineAnchorType="0" repeatDistanceUnits="MM" labelOffsetMapUnitScale="3x:0,0,0,0,0,0"/>
<rendering fontMinPixelSize="3" fontMaxPixelSize="10000" maxNumLabels="2000" obstacleFactor="1" mergeLines="0" fontLimitPixelSize="0" limitNumLabels="0" minFeatureSize="0" scaleMin="0" scaleVisibility="0" labelPerPart="0" scaleMax="0" obstacleType="1" upsidedownLabels="0" drawLabels="1" zIndex="0" unplacedVisibility="0" obstacle="1"/>
<dd_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</dd_properties>
<callout type="simple">
<Option type="Map">
<Option name="anchorPoint" type="QString" value="centroid"/>
<Option name="blendMode" type="int" value="0"/>
<Option name="ddProperties" type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
<Option name="drawToAllParts" type="bool" value="false"/>
<Option name="enabled" type="QString" value="1"/>
<Option name="labelAnchorPoint" type="QString" value="point_on_exterior"/>
<Option name="lineSymbol" type="QString" value="&lt;symbol name=&quot;symbol&quot; type=&quot;line&quot; force_rhr=&quot;0&quot; clip_to_extent=&quot;1&quot; is_animated=&quot;0&quot; frame_rate=&quot;10&quot; alpha=&quot;1&quot;>&lt;data_defined_properties>&lt;Option type=&quot;Map&quot;>&lt;Option name=&quot;name&quot; type=&quot;QString&quot; value=&quot;&quot;/>&lt;Option name=&quot;properties&quot;/>&lt;Option name=&quot;type&quot; type=&quot;QString&quot; value=&quot;collection&quot;/>&lt;/Option>&lt;/data_defined_properties>&lt;layer id=&quot;{3356d22c-5f69-4911-8e91-fcf32e8243fa}&quot; class=&quot;SimpleLine&quot; locked=&quot;0&quot; enabled=&quot;1&quot; pass=&quot;0&quot;>&lt;Option type=&quot;Map&quot;>&lt;Option name=&quot;align_dash_pattern&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;capstyle&quot; type=&quot;QString&quot; value=&quot;square&quot;/>&lt;Option name=&quot;customdash&quot; type=&quot;QString&quot; value=&quot;5;2&quot;/>&lt;Option name=&quot;customdash_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;Option name=&quot;customdash_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;dash_pattern_offset&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;dash_pattern_offset_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;Option name=&quot;dash_pattern_offset_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;draw_inside_polygon&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;joinstyle&quot; type=&quot;QString&quot; value=&quot;bevel&quot;/>&lt;Option name=&quot;line_color&quot; type=&quot;QString&quot; value=&quot;60,60,60,255,rgb:0.23529411764705882,0.23529411764705882,0.23529411764705882,1&quot;/>&lt;Option name=&quot;line_style&quot; type=&quot;QString&quot; value=&quot;solid&quot;/>&lt;Option name=&quot;line_width&quot; type=&quot;QString&quot; value=&quot;0.3&quot;/>&lt;Option name=&quot;line_width_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;offset&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;offset_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;Option name=&quot;offset_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;ring_filter&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;trim_distance_end&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;trim_distance_end_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;Option name=&quot;trim_distance_end_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;trim_distance_start&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;trim_distance_start_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;Option name=&quot;trim_distance_start_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;tweak_dash_pattern_on_corners&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;use_custom_dash&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;width_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;/Option>&lt;data_defined_properties>&lt;Option type=&quot;Map&quot;>&lt;Option name=&quot;name&quot; type=&quot;QString&quot; value=&quot;&quot;/>&lt;Option name=&quot;properties&quot;/>&lt;Option name=&quot;type&quot; type=&quot;QString&quot; value=&quot;collection&quot;/>&lt;/Option>&lt;/data_defined_properties>&lt;/layer>&lt;/symbol>"/>
<Option name="minLength" type="double" value="0"/>
<Option name="minLengthMapUnitScale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="minLengthUnit" type="QString" value="MM"/>
<Option name="offsetFromAnchor" type="double" value="0"/>
<Option name="offsetFromAnchorMapUnitScale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offsetFromAnchorUnit" type="QString" value="MM"/>
<Option name="offsetFromLabel" type="double" value="0"/>
<Option name="offsetFromLabelMapUnitScale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offsetFromLabelUnit" type="QString" value="MM"/>
</Option>
</callout>
</settings>
</rule>
<rule key="{a9800348-c55c-45d0-95ce-be6db84663c8}" filter="&quot;flurstuecksnummer_ax_flurstuecksnummer_nenner&quot; IS NOT NULL" description="Flst_num_Zusatz">
<settings calloutType="simple">
<text-style forcedItalic="0" fontUnderline="0" previewBkgrdColor="255,255,255,255,rgb:1,1,1,1" fontItalic="0" tabStopDistanceUnit="Point" textOpacity="1" multilineHeight="1" fontFamily="Times New Roman" fontStrikeout="0" capitalization="0" fieldName="&quot;flurstuecksnummer_ax_flurstuecksnummer_zaehler&quot; || '\n' || &quot;flurstuecksnummer_ax_flurstuecksnummer_nenner&quot; " legendString="Aa" fontSizeUnit="Point" fontWeight="50" textOrientation="horizontal" multilineHeightUnit="Percentage" fontKerning="1" tabStopDistanceMapUnitScale="3x:0,0,0,0,0,0" forcedBold="0" fontWordSpacing="0" fontLetterSpacing="0" fontSizeMapUnitScale="3x:0,0,0,0,0,0" allowHtml="0" isExpression="1" useSubstitutions="0" textColor="0,0,0,255,rgb:0,0,0,1" blendMode="0" fontSize="7" namedStyle="Standard" tabStopDistance="80">
<families/>
<text-buffer bufferBlendMode="0" bufferSizeUnits="MM" bufferOpacity="1" bufferJoinStyle="128" bufferNoFill="1" bufferSizeMapUnitScale="3x:0,0,0,0,0,0" bufferDraw="1" bufferColor="255,255,255,255,rgb:1,1,1,1" bufferSize="0.5"/>
<text-mask maskEnabled="0" maskSizeMapUnitScale="3x:0,0,0,0,0,0" maskSizeUnits="MM" maskJoinStyle="128" maskSize2="1.5" maskSize="1.5" maskOpacity="1" maskType="0" maskedSymbolLayers=""/>
<background shapeOffsetX="0" shapeRadiiY="0" shapeSVGFile="" shapeRotation="0" shapeBorderWidth="0" shapeRadiiX="0" shapeSizeX="4" shapeBorderWidthMapUnitScale="3x:0,0,0,0,0,0" shapeSizeUnit="MM" shapeRotationType="0" shapeBorderColor="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" shapeJoinStyle="64" shapeRadiiMapUnitScale="3x:0,0,0,0,0,0" shapeType="5" shapeOffsetMapUnitScale="3x:0,0,0,0,0,0" shapeDraw="1" shapeBorderWidthUnit="MM" shapeFillColor="255,255,255,255,rgb:1,1,1,1" shapeOffsetUnit="MM" shapeRadiiUnit="MM" shapeSizeMapUnitScale="3x:0,0,0,0,0,0" shapeOffsetY="0" shapeBlendMode="0" shapeSizeType="1" shapeSizeY="0" shapeOpacity="1">
<symbol name="markerSymbol" type="marker" force_rhr="0" clip_to_extent="1" is_animated="0" frame_rate="10" alpha="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>
<layer id="" class="SimpleMarker" locked="0" enabled="1" pass="0">
<Option type="Map">
<Option name="angle" type="QString" value="90"/>
<Option name="cap_style" type="QString" value="square"/>
<Option name="color" type="QString" value="255,158,23,255,rgb:1,0.61960784313725492,0.09019607843137255,1"/>
<Option name="horizontal_anchor_point" type="QString" value="1"/>
<Option name="joinstyle" type="QString" value="bevel"/>
<Option name="name" type="QString" value="line"/>
<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.13725490196078433,0.13725490196078433,0.13725490196078433,1"/>
<Option name="outline_style" type="QString" value="solid"/>
<Option name="outline_width" type="QString" value="0.3"/>
<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="4"/>
<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>
<symbol name="fillSymbol" type="fill" force_rhr="0" clip_to_extent="1" is_animated="0" frame_rate="10" alpha="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>
<layer id="" class="SimpleFill" locked="0" enabled="1" 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="255,255,255,255,rgb:1,1,1,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="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1"/>
<Option name="outline_style" type="QString" value="no"/>
<Option name="outline_width" type="QString" value="0"/>
<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>
</background>
<shadow shadowRadiusAlphaOnly="0" shadowOffsetAngle="135" shadowOpacity="0.69999999999999996" shadowOffsetUnit="MM" shadowColor="0,0,0,255,rgb:0,0,0,1" shadowRadiusMapUnitScale="3x:0,0,0,0,0,0" shadowRadius="1.5" shadowBlendMode="6" shadowScale="100" shadowDraw="0" shadowUnder="0" shadowOffsetDist="1" shadowRadiusUnit="MM" shadowOffsetMapUnitScale="3x:0,0,0,0,0,0" shadowOffsetGlobal="1"/>
<dd_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</dd_properties>
<substitutions/>
</text-style>
<text-format multilineAlign="3" autoWrapLength="0" reverseDirectionSymbol="0" plussign="0" rightDirectionSymbol=">" addDirectionSymbol="0" leftDirectionSymbol="&lt;" formatNumbers="0" decimals="3" placeDirectionSymbol="0" wrapChar="" useMaxLineLengthForAutoWrap="1"/>
<placement lineAnchorClipping="0" maximumDistanceMapUnitScale="3x:0,0,0,0,0,0" xOffset="0" maximumDistance="0" geometryGenerator="" distMapUnitScale="3x:0,0,0,0,0,0" predefinedPositionOrder="TR,TL,BR,BL,R,L,TSR,BSR" layerType="PolygonGeometry" lineAnchorPercent="0.5" rotationUnit="AngleDegrees" repeatDistanceMapUnitScale="3x:0,0,0,0,0,0" priority="5" yOffset="0" quadOffset="4" polygonPlacementFlags="2" centroidInside="1" placement="1" placementFlags="10" prioritization="PreferCloser" overrunDistance="0" offsetUnits="MM" maxCurvedCharAngleOut="-25" preserveRotation="1" lineAnchorTextPoint="CenterOfText" overlapHandling="PreventOverlap" rotationAngle="0" repeatDistance="0" offsetType="0" distUnits="MM" allowDegraded="0" overrunDistanceUnit="MM" centroidWhole="0" geometryGeneratorType="PointGeometry" fitInPolygonOnly="0" geometryGeneratorEnabled="0" dist="0" maxCurvedCharAngleIn="25" overrunDistanceMapUnitScale="3x:0,0,0,0,0,0" maximumDistanceUnit="MM" lineAnchorType="0" repeatDistanceUnits="MM" labelOffsetMapUnitScale="3x:0,0,0,0,0,0"/>
<rendering fontMinPixelSize="3" fontMaxPixelSize="10000" maxNumLabels="2000" obstacleFactor="1" mergeLines="0" fontLimitPixelSize="0" limitNumLabels="0" minFeatureSize="0" scaleMin="0" scaleVisibility="0" labelPerPart="0" scaleMax="0" obstacleType="1" upsidedownLabels="0" drawLabels="1" zIndex="0" unplacedVisibility="0" obstacle="1"/>
<dd_properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties" type="Map">
<Option name="FontStyle" type="Map">
<Option name="active" type="bool" value="false"/>
<Option name="field" type="QString" value="flstnrzae"/>
<Option name="type" type="int" value="2"/>
</Option>
<Option name="Underline" type="Map">
<Option name="active" type="bool" value="false"/>
<Option name="field" type="QString" value="flstnrzae"/>
<Option name="type" type="int" value="2"/>
</Option>
</Option>
<Option name="type" type="QString" value="collection"/>
</Option>
</dd_properties>
<callout type="simple">
<Option type="Map">
<Option name="anchorPoint" type="QString" value="pole_of_inaccessibility"/>
<Option name="blendMode" type="int" value="0"/>
<Option name="ddProperties" type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
<Option name="drawToAllParts" type="bool" value="false"/>
<Option name="enabled" type="QString" value="0"/>
<Option name="labelAnchorPoint" type="QString" value="point_on_exterior"/>
<Option name="lineSymbol" type="QString" value="&lt;symbol name=&quot;symbol&quot; type=&quot;line&quot; force_rhr=&quot;0&quot; clip_to_extent=&quot;1&quot; is_animated=&quot;0&quot; frame_rate=&quot;10&quot; alpha=&quot;1&quot;>&lt;data_defined_properties>&lt;Option type=&quot;Map&quot;>&lt;Option name=&quot;name&quot; type=&quot;QString&quot; value=&quot;&quot;/>&lt;Option name=&quot;properties&quot;/>&lt;Option name=&quot;type&quot; type=&quot;QString&quot; value=&quot;collection&quot;/>&lt;/Option>&lt;/data_defined_properties>&lt;layer id=&quot;{0a6da4bc-e8f1-4ec2-8062-844ead072d33}&quot; class=&quot;SimpleLine&quot; locked=&quot;0&quot; enabled=&quot;1&quot; pass=&quot;0&quot;>&lt;Option type=&quot;Map&quot;>&lt;Option name=&quot;align_dash_pattern&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;capstyle&quot; type=&quot;QString&quot; value=&quot;square&quot;/>&lt;Option name=&quot;customdash&quot; type=&quot;QString&quot; value=&quot;5;2&quot;/>&lt;Option name=&quot;customdash_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;Option name=&quot;customdash_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;dash_pattern_offset&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;dash_pattern_offset_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;Option name=&quot;dash_pattern_offset_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;draw_inside_polygon&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;joinstyle&quot; type=&quot;QString&quot; value=&quot;bevel&quot;/>&lt;Option name=&quot;line_color&quot; type=&quot;QString&quot; value=&quot;60,60,60,255,rgb:0.23529411764705882,0.23529411764705882,0.23529411764705882,1&quot;/>&lt;Option name=&quot;line_style&quot; type=&quot;QString&quot; value=&quot;solid&quot;/>&lt;Option name=&quot;line_width&quot; type=&quot;QString&quot; value=&quot;0.3&quot;/>&lt;Option name=&quot;line_width_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;offset&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;offset_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;Option name=&quot;offset_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;ring_filter&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;trim_distance_end&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;trim_distance_end_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;Option name=&quot;trim_distance_end_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;trim_distance_start&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;trim_distance_start_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;Option name=&quot;trim_distance_start_unit&quot; type=&quot;QString&quot; value=&quot;MM&quot;/>&lt;Option name=&quot;tweak_dash_pattern_on_corners&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;use_custom_dash&quot; type=&quot;QString&quot; value=&quot;0&quot;/>&lt;Option name=&quot;width_map_unit_scale&quot; type=&quot;QString&quot; value=&quot;3x:0,0,0,0,0,0&quot;/>&lt;/Option>&lt;data_defined_properties>&lt;Option type=&quot;Map&quot;>&lt;Option name=&quot;name&quot; type=&quot;QString&quot; value=&quot;&quot;/>&lt;Option name=&quot;properties&quot;/>&lt;Option name=&quot;type&quot; type=&quot;QString&quot; value=&quot;collection&quot;/>&lt;/Option>&lt;/data_defined_properties>&lt;/layer>&lt;/symbol>"/>
<Option name="minLength" type="double" value="0"/>
<Option name="minLengthMapUnitScale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="minLengthUnit" type="QString" value="MM"/>
<Option name="offsetFromAnchor" type="double" value="0"/>
<Option name="offsetFromAnchorMapUnitScale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offsetFromAnchorUnit" type="QString" value="MM"/>
<Option name="offsetFromLabel" type="double" value="0"/>
<Option name="offsetFromLabelMapUnitScale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="offsetFromLabelUnit" type="QString" value="MM"/>
</Option>
</callout>
</settings>
</rule>
</rules>
</labeling>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerGeometryType>2</layerGeometryType>
</qgis>
+112 -103
View File
@@ -1,133 +1,142 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="3.40.7-Bratislava" styleCategories="Symbology">
<renderer-v2 referencescale="-1" forceraster="0" enableorderby="0" type="singleSymbol" symbollevels="0">
<symbols>
<symbol is_animated="0" frame_rate="10" clip_to_extent="1" type="fill" alpha="1" force_rhr="0" name="0">
<data_defined_properties>
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
</Option>
</data_defined_properties>
<layer locked="0" id="{feca00b2-500a-4c9a-b285-67ba2d99d8f6}" enabled="1" class="SimpleLine" pass="0">
<Option type="Map">
<Option type="QString" value="0" name="align_dash_pattern"/>
<Option type="QString" value="square" name="capstyle"/>
<Option type="QString" value="5;2" name="customdash"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="customdash_map_unit_scale"/>
<Option type="QString" value="MM" name="customdash_unit"/>
<Option type="QString" value="0" name="dash_pattern_offset"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="dash_pattern_offset_map_unit_scale"/>
<Option type="QString" value="MM" name="dash_pattern_offset_unit"/>
<Option type="QString" value="0" name="draw_inside_polygon"/>
<Option type="QString" value="round" name="joinstyle"/>
<Option type="QString" value="215,168,255,255,rgb:0.84313725490196079,0.6588235294117647,1,1" name="line_color"/>
<Option type="QString" value="solid" name="line_style"/>
<Option type="QString" value="1.5" name="line_width"/>
<Option type="QString" value="MM" name="line_width_unit"/>
<Option type="QString" value="-0.75" name="offset"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="offset_map_unit_scale"/>
<Option type="QString" value="MM" name="offset_unit"/>
<Option type="QString" value="0" name="ring_filter"/>
<Option type="QString" value="0" name="trim_distance_end"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="trim_distance_end_map_unit_scale"/>
<Option type="QString" value="MM" name="trim_distance_end_unit"/>
<Option type="QString" value="0" name="trim_distance_start"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="trim_distance_start_map_unit_scale"/>
<Option type="QString" value="MM" name="trim_distance_start_unit"/>
<Option type="QString" value="0" name="tweak_dash_pattern_on_corners"/>
<Option type="QString" value="0" name="use_custom_dash"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="width_map_unit_scale"/>
</Option>
<qgis layerType="Vector" styleCategories="Symbology" version="4.0.1-Norrköping">
<renderer-v2 enableorderby="0" forceraster="0" referencescale="-1" symbollevels="0" type="mergedFeatureRenderer">
<renderer-v2 enableorderby="0" forceraster="0" referencescale="-1" symbollevels="0" type="singleSymbol">
<symbols>
<symbol alpha="1" clip_to_extent="1" force_rhr="0" frame_rate="10" is_animated="0" name="0" type="fill">
<data_defined_properties>
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
<layer locked="0" id="{fdc4d6fd-0995-41df-bbfb-19970b4fc2cc}" enabled="1" class="SimpleLine" pass="0">
<Option type="Map">
<Option type="QString" value="0" name="align_dash_pattern"/>
<Option type="QString" value="square" name="capstyle"/>
<Option type="QString" value="5;2" name="customdash"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="customdash_map_unit_scale"/>
<Option type="QString" value="MM" name="customdash_unit"/>
<Option type="QString" value="0" name="dash_pattern_offset"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="dash_pattern_offset_map_unit_scale"/>
<Option type="QString" value="MM" name="dash_pattern_offset_unit"/>
<Option type="QString" value="0" name="draw_inside_polygon"/>
<Option type="QString" value="round" name="joinstyle"/>
<Option type="QString" value="204,174,137,255,rgb:0.80000000000000004,0.68235294117647061,0.53725490196078429,1" name="line_color"/>
<Option type="QString" value="solid" name="line_style"/>
<Option type="QString" value="0.5" name="line_width"/>
<Option type="QString" value="MM" name="line_width_unit"/>
<Option type="QString" value="0" name="offset"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="offset_map_unit_scale"/>
<Option type="QString" value="MM" name="offset_unit"/>
<Option type="QString" value="0" name="ring_filter"/>
<Option type="QString" value="0" name="trim_distance_end"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="trim_distance_end_map_unit_scale"/>
<Option type="QString" value="MM" name="trim_distance_end_unit"/>
<Option type="QString" value="0" name="trim_distance_start"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="trim_distance_start_map_unit_scale"/>
<Option type="QString" value="MM" name="trim_distance_start_unit"/>
<Option type="QString" value="0" name="tweak_dash_pattern_on_corners"/>
<Option type="QString" value="0" name="use_custom_dash"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="width_map_unit_scale"/>
</Option>
<data_defined_properties>
<layer class="SimpleLine" enabled="1" id="{feca00b2-500a-4c9a-b285-67ba2d99d8f6}" locked="0" pass="0">
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
<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="round"/>
<Option name="line_color" type="QString" value="215,168,255,255,rgb:0.8431373,0.6588235,1,1"/>
<Option name="line_style" type="QString" value="solid"/>
<Option name="line_width" type="QString" value="1.5"/>
<Option name="line_width_unit" type="QString" value="MM"/>
<Option name="offset" type="QString" value="-0.75"/>
<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>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
<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="SimpleLine" enabled="1" id="{fdc4d6fd-0995-41df-bbfb-19970b4fc2cc}" 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="round"/>
<Option name="line_color" type="QString" value="204,174,137,255,rgb:0.8,0.682353,0.5372549,1"/>
<Option name="line_style" type="QString" value="solid"/>
<Option name="line_width" type="QString" value="0.5"/>
<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>
</symbols>
<rotation/>
<sizescale/>
<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>
<data-defined-properties>
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data-defined-properties>
</renderer-v2>
<selection mode="Default">
<selectionColor invalid="1"/>
<selectionSymbol>
<symbol is_animated="0" frame_rate="10" clip_to_extent="1" type="fill" alpha="1" force_rhr="0" name="">
<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 type="QString" value="" name="name"/>
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
<layer locked="0" id="{f18003f5-220a-487f-8a6d-b24facc4c1a5}" enabled="1" class="SimpleFill" pass="0">
<layer class="SimpleFill" enabled="1" id="{f18003f5-220a-487f-8a6d-b24facc4c1a5}" locked="0" pass="0">
<Option type="Map">
<Option type="QString" value="3x:0,0,0,0,0,0" name="border_width_map_unit_scale"/>
<Option type="QString" value="0,0,255,255,rgb:0,0,1,1" name="color"/>
<Option type="QString" value="bevel" name="joinstyle"/>
<Option type="QString" value="0,0" name="offset"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="offset_map_unit_scale"/>
<Option type="QString" value="MM" name="offset_unit"/>
<Option type="QString" value="35,35,35,255,rgb:0.13725490196078433,0.13725490196078433,0.13725490196078433,1" name="outline_color"/>
<Option type="QString" value="solid" name="outline_style"/>
<Option type="QString" value="0.26" name="outline_width"/>
<Option type="QString" value="MM" name="outline_width_unit"/>
<Option type="QString" value="solid" name="style"/>
<Option name="border_width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/>
<Option name="color" type="QString" value="0,0,255,255,rgb:0,0,1,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="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.26"/>
<Option name="outline_width_unit" type="QString" value="MM"/>
<Option name="style" type="QString" value="solid"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</data_defined_properties>
</layer>
+1
View File
@@ -0,0 +1 @@
"""Tests für das Plugin sn_verfahrensgebiet."""
+86
View File
@@ -0,0 +1,86 @@
import sys
import types
import unittest
from unittest.mock import patch
if "qgis" not in sys.modules:
qgis_mod = types.ModuleType("qgis")
qgis_utils = types.ModuleType("qgis.utils")
qgis_utils.iface = types.SimpleNamespace()
qgis_utils.plugins = {}
qgis_mod.utils = qgis_utils
sys.modules["qgis"] = qgis_mod
sys.modules["qgis.utils"] = qgis_utils
from sn_verfahrensgebiet.ui.dockwidget import DockWidget
class _DummyTabWidget:
def __init__(self):
self.tabs = []
def addTab(self, widget, title):
self.tabs.append((widget, title))
class _TabWithServices:
tab_title = "WithServices"
def __init__(self, _parent):
self.services = None
def set_services(self, pruefmanager=None, data_grabber=None):
self.services = (pruefmanager, data_grabber)
class _TabWithoutServices:
tab_title = "WithoutServices"
def __init__(self, _parent):
pass
class _TabRaises:
tab_title = "Broken"
def __init__(self, _parent):
raise RuntimeError("boom")
class TestDockWidget(unittest.TestCase):
@patch("sn_verfahrensgebiet.ui.dockwidget.QTabWidget", _DummyTabWidget)
def test_init_tabs_with_services_injects_available_services(self):
obj = DockWidget.__new__(DockWidget)
obj.tabs = [_TabWithServices, _TabWithoutServices]
obj.pruefmanager = object()
obj.data_grabber = object()
obj._set_widget_calls = []
obj.setWidget = lambda value: obj._set_widget_calls.append(value)
DockWidget._init_tabs_with_services(obj)
self.assertEqual(len(obj._set_widget_calls), 2)
tab_widget = obj._set_widget_calls[-1]
self.assertIsInstance(tab_widget, _DummyTabWidget)
self.assertEqual(len(tab_widget.tabs), 2)
first_tab = tab_widget.tabs[0][0]
self.assertEqual(first_tab.services, (obj.pruefmanager, obj.data_grabber))
@patch("sn_verfahrensgebiet.ui.dockwidget.error")
@patch("sn_verfahrensgebiet.ui.dockwidget.QTabWidget", _DummyTabWidget)
def test_init_tabs_with_services_reports_errors(self, mock_error):
obj = DockWidget.__new__(DockWidget)
obj.tabs = [_TabRaises]
obj.pruefmanager = object()
obj.data_grabber = object()
obj.setWidget = lambda _value: None
DockWidget._init_tabs_with_services(obj)
mock_error.assert_called_once()
if __name__ == "__main__":
unittest.main()
+223
View File
@@ -0,0 +1,223 @@
"""sn_verfahrensgebiet/tests/test_flurstueck_alkis.py
Unit-Tests für das flurstueck_alkis-Modul.
Getestet werden:
- Nutzerabbruch bei ask_text → (None, LoadStatus.NONE)
- Ungültiger WFS-Layer → (None, LoadStatus.NONE)
- Erfolgreicher Ladevorgang → gültiger Layer + LoadStatus.FIRST
- Bestehender Layer: Nutzer wählt "Neu laden" → LoadStatus.RELOAD
- Bestehender Layer: Nutzer wählt "Behalten" → LoadStatus.KEEP
"""
import sys
import types
import unittest
from unittest.mock import patch, MagicMock
# ---------------------------------------------------------------------------
# Minimales QGIS-Mock-Setup
# ---------------------------------------------------------------------------
if "qgis" not in sys.modules:
qgis_mod = types.ModuleType("qgis")
qgis_core = types.ModuleType("qgis.core")
qgis_utils = types.ModuleType("qgis.utils")
qgis_utils.iface = types.SimpleNamespace()
qgis_utils.plugins = {}
qgis_mod.utils = qgis_utils
qgis_mod.core = qgis_core
sys.modules["qgis"] = qgis_mod
sys.modules["qgis.core"] = qgis_core
sys.modules["qgis.utils"] = qgis_utils
from sn_verfahrensgebiet.functions.flurstueck_alkis import flurstueck_alkis, FLST_LAYER_NAME
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus
# ---------------------------------------------------------------------------
# Dummies
# ---------------------------------------------------------------------------
class _DummyLayer:
def __init__(self, valid: bool, layer_id="L1"):
self._valid = valid
self._id = layer_id
def isValid(self):
return self._valid
def id(self):
return self._id
def extent(self):
return object()
class _DummyProject:
def __init__(self, named_layers=None):
self._named_layers = named_layers or []
self.removed = []
def mapLayersByName(self, name):
return self._named_layers
def removeMapLayer(self, lid):
self.removed.append(lid)
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
class TestFlurstueckAlkis(unittest.TestCase):
# ------------------------------------------------------------------ #
# 1. Nutzerabbruch (ask_text liefert ok=False / None) #
# ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QMessageBox")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_user_cancel_returns_none(self, mock_ask_text, mock_project, mock_msgbox):
"""ask_text → ok=False, text="" → (None, NONE)."""
mock_ask_text.return_value = ("", False)
mock_project.return_value = _DummyProject()
layer, status = flurstueck_alkis(None)
self.assertIsNone(layer)
self.assertEqual(status, LoadStatus.NONE)
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QMessageBox")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
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)."""
mock_ask_text.return_value = (" ", True)
mock_project.return_value = _DummyProject()
layer, status = flurstueck_alkis(None)
self.assertIsNone(layer)
self.assertEqual(status, LoadStatus.NONE)
# ------------------------------------------------------------------ #
# 2. Ungültiger WFS-Layer #
# ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QMessageBox")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_invalid_wfs_layer_returns_none(
self, mock_ask_text, mock_project, mock_wfs_layer_cls, mock_msgbox
):
"""WFS-Layer nicht valide → (None, NONE)."""
mock_ask_text.return_value = ("1234", True)
mock_project.return_value = _DummyProject()
mock_wfs_layer_cls.return_value = _DummyLayer(valid=False)
layer, status = flurstueck_alkis(None)
self.assertIsNone(layer)
self.assertEqual(status, LoadStatus.NONE)
# ------------------------------------------------------------------ #
# 3. Erfolgreicher Erstladevorgang #
# ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_success_returns_layer_and_first_status(
self, mock_ask_text, mock_project, mock_wfs_layer_cls
):
"""Kein bestehender Layer, WFS valide → (layer, FIRST)."""
mock_ask_text.return_value = ("1234, 5678", True)
mock_project.return_value = _DummyProject()
valid_layer = _DummyLayer(valid=True, layer_id="wfs_ok")
mock_wfs_layer_cls.return_value = valid_layer
layer, status = flurstueck_alkis(None)
self.assertIs(layer, valid_layer)
self.assertEqual(status, LoadStatus.FIRST)
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_sql_filter_contains_all_schluessel(
self, mock_ask_text, mock_project, mock_wfs_layer_cls
):
"""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)
# Erster Positionsarg von QgsVectorLayer ist der URI-String
call_args = mock_wfs_layer_cls.call_args
uri_str = call_args[0][0] if call_args[0] else call_args[1].get("path", "")
self.assertIn("1111", uri_str)
self.assertIn("2222", uri_str)
self.assertIn("3333", uri_str)
# ------------------------------------------------------------------ #
# 4. Bestehender Layer – Nutzer wählt "Neu laden" #
# ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QMessageBox")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_existing_layer_reload_yes(
self, mock_ask_text, mock_project, mock_msgbox_cls, mock_wfs_layer_cls
):
"""Bestehender Layer + Nutzer: 'Ja, neu laden' → LoadStatus.RELOAD."""
existing = _DummyLayer(valid=True, layer_id="old_flst")
project = _DummyProject(named_layers=[existing])
mock_ask_text.return_value = ("1234", True)
mock_project.return_value = project
# StandardButton als int, damit | Operator funktioniert
mock_msgbox_cls.StandardButton.Yes = 0x4000
mock_msgbox_cls.StandardButton.No = 0x10000
# QMessageBox.question → Yes
mock_msgbox_cls.question.return_value = 0x4000
new_layer = _DummyLayer(valid=True, layer_id="new_flst")
mock_wfs_layer_cls.return_value = new_layer
layer, status = flurstueck_alkis(None)
self.assertIn(existing.id(), project.removed)
self.assertIs(layer, new_layer)
self.assertEqual(status, LoadStatus.RELOAD)
# ------------------------------------------------------------------ #
# 5. Bestehender Layer – Nutzer wählt "Behalten" #
# ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QMessageBox")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_existing_layer_keep_no(
self, mock_ask_text, mock_project, mock_msgbox_cls
):
"""Bestehender Layer + Nutzer: 'Nein, behalten' → LoadStatus.KEEP."""
existing = _DummyLayer(valid=True, layer_id="keep_flst")
project = _DummyProject(named_layers=[existing])
mock_ask_text.return_value = ("1234", True)
mock_project.return_value = project
# StandardButton als int, damit | Operator funktioniert
mock_msgbox_cls.StandardButton.Yes = 0x4000
mock_msgbox_cls.StandardButton.No = 0x10000
# QMessageBox.question → No
mock_msgbox_cls.question.return_value = 0x10000
layer, status = flurstueck_alkis(None)
self.assertIs(layer, existing)
self.assertEqual(status, LoadStatus.KEEP)
self.assertEqual(project.removed, []) # nichts gelöscht
if __name__ == "__main__":
unittest.main()
+121
View File
@@ -0,0 +1,121 @@
import sys
import types
import unittest
from types import SimpleNamespace
from unittest.mock import Mock, patch
if "qgis" not in sys.modules:
qgis_mod = types.ModuleType("qgis")
qgis_utils = types.ModuleType("qgis.utils")
qgis_utils.iface = types.SimpleNamespace()
qgis_utils.plugins = {}
qgis_mod.utils = qgis_utils
sys.modules["qgis"] = qgis_mod
sys.modules["qgis.utils"] = qgis_utils
from sn_verfahrensgebiet.main import Verfahrensgebiet
class _DummyIface:
def __init__(self):
self.removed = []
def mainWindow(self):
return object()
def removeDockWidget(self, dock):
self.removed.append(dock)
class _DummyDock:
def __init__(self):
self.object_name = None
self.action = None
self.deleted = False
def setObjectName(self, name):
self.object_name = name
def deleteLater(self):
self.deleted = True
class TestPluginMain(unittest.TestCase):
@patch("sn_verfahrensgebiet.main.DataGrabber")
@patch("sn_verfahrensgebiet.main.Pruefmanager")
def test_init_sets_services_and_names(self, mock_pruefmanager, mock_data_grabber):
pm = object()
dg = object()
mock_pruefmanager.return_value = pm
mock_data_grabber.return_value = dg
plugin = Verfahrensgebiet(_DummyIface())
self.assertIs(plugin.pruefmanager, pm)
self.assertIs(plugin.data_grabber, dg)
self.assertEqual(plugin.plugin_name, "Verfahrensgebiet")
self.assertEqual(plugin.dock_name, "sn_dock_verfahrensgebiet")
@patch("sn_verfahrensgebiet.main.plugins", new_callable=dict)
def test_initGui_registers_action_when_basis_ui_exists(self, mock_plugins):
iface = _DummyIface()
plugin = Verfahrensgebiet(iface)
ui = SimpleNamespace(
add_action=Mock(return_value="action_obj"),
finalize_menu_and_toolbar=Mock(),
)
mock_plugins["sn_basis"] = SimpleNamespace(ui=ui)
plugin.initGui()
self.assertEqual(plugin.action, "action_obj")
ui.add_action.assert_called_once()
ui.finalize_menu_and_toolbar.assert_called_once()
@patch("sn_verfahrensgebiet.main.plugins", new_callable=dict)
@patch("sn_verfahrensgebiet.main.DockManager.show")
@patch("sn_verfahrensgebiet.main.DockWidget")
def test_run_creates_dock_and_marks_toolbar_active(self, mock_dockwidget_cls, mock_show, mock_plugins):
iface = _DummyIface()
plugin = Verfahrensgebiet(iface)
plugin.action = "action_obj"
dock = _DummyDock()
mock_dockwidget_cls.return_value = dock
ui = SimpleNamespace(set_active_plugin=Mock())
mock_plugins["sn_basis"] = SimpleNamespace(ui=ui)
plugin.run()
self.assertIs(plugin.dockwidget, dock)
self.assertEqual(dock.object_name, "sn_dock_verfahrensgebiet")
self.assertEqual(dock.action, "action_obj")
mock_show.assert_called_once_with(dock)
ui.set_active_plugin.assert_called_once_with("action_obj")
@patch("sn_verfahrensgebiet.main.plugins", new_callable=dict)
def test_unload_removes_dock_and_action(self, mock_plugins):
iface = _DummyIface()
plugin = Verfahrensgebiet(iface)
dock = _DummyDock()
plugin.dockwidget = dock
plugin.action = "action_obj"
ui = SimpleNamespace(remove_action=Mock())
mock_plugins["sn_basis"] = SimpleNamespace(ui=ui)
plugin.unload()
self.assertIn(dock, iface.removed)
self.assertTrue(dock.deleted)
self.assertIsNone(plugin.dockwidget)
ui.remove_action.assert_called_once_with("action_obj")
self.assertIsNone(plugin.action)
if __name__ == "__main__":
unittest.main()
+184
View File
@@ -0,0 +1,184 @@
import sys
import types
import unittest
from unittest.mock import patch
if "qgis" not in sys.modules:
qgis_mod = types.ModuleType("qgis")
qgis_utils = types.ModuleType("qgis.utils")
qgis_utils.iface = types.SimpleNamespace()
qgis_utils.plugins = {}
qgis_mod.utils = qgis_utils
sys.modules["qgis"] = qgis_mod
sys.modules["qgis.utils"] = qgis_utils
from sn_verfahrensgebiet.functions import verfahrensgebiet_alkis as vg
class _DummyMessageBox:
class StandardButton:
Yes = 1
No = 2
last_critical = None
next_reply = StandardButton.No
@staticmethod
def question(_parent, _title, _message, _buttons, _default):
return _DummyMessageBox.next_reply
@staticmethod
def critical(parent, title, message):
_DummyMessageBox.last_critical = (parent, title, message)
class _DummyProject:
def __init__(self, layers=None):
self._layers = layers or []
self.removed = []
def mapLayersByName(self, _name):
return self._layers
def removeMapLayer(self, layer_id):
self.removed.append(layer_id)
class _DummyExistingLayer:
def __init__(self, layer_id="existing"):
self._id = layer_id
def id(self):
return self._id
class _DummyGeom:
def __init__(self, empty=False):
self._empty = empty
def isEmpty(self):
return self._empty
class _DummyFeature:
def __init__(self, geom=None):
self._geom = geom or _DummyGeom()
def geometry(self):
return self._geom
class _DummyTempLayer:
def __init__(self, valid=True, features=None):
self._valid = valid
self._features = features or []
def isValid(self):
return self._valid
def getFeatures(self):
return self._features
def crs(self):
class _Crs:
@staticmethod
def authid():
return "EPSG:25833"
return _Crs()
class _DummyProvider:
def addAttributes(self, _attrs):
return True
def addFeatures(self, _features):
return True
class _DummyMemoryLayer:
def __init__(self):
self._provider = _DummyProvider()
def dataProvider(self):
return self._provider
def updateFields(self):
return None
def updateExtents(self):
return None
class TestVerfahrensgebietAlkis(unittest.TestCase):
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QMessageBox", new=_DummyMessageBox)
def test_check_existing_layer_first_load(self, mock_project_instance):
mock_project_instance.return_value = _DummyProject(layers=[])
layer, status = vg._check_existing_layer(tab_widget=None)
self.assertIsNone(layer)
self.assertEqual(status, vg.LoadStatus.FIRST)
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QMessageBox", new=_DummyMessageBox)
def test_check_existing_layer_keep_existing(self, mock_project_instance):
existing = _DummyExistingLayer("L1")
mock_project_instance.return_value = _DummyProject(layers=[existing])
_DummyMessageBox.next_reply = _DummyMessageBox.StandardButton.No
layer, status = vg._check_existing_layer(tab_widget=None)
self.assertIs(layer, existing)
self.assertEqual(status, vg.LoadStatus.KEEP)
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QMessageBox", new=_DummyMessageBox)
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.get_variable", return_value="")
def test_verfahrensgebiet_alkis_fails_when_verfahrensnummer_missing(self, _mock_get_var):
_DummyMessageBox.last_critical = None
layer, status = vg.verfahrensgebiet_alkis(tab_widget=None)
self.assertIsNone(layer)
self.assertEqual(status, vg.LoadStatus.NONE)
self.assertIsNotNone(_DummyMessageBox.last_critical)
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis._check_existing_layer", return_value=(None, vg.LoadStatus.FIRST))
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QMessageBox", new=_DummyMessageBox)
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.get_variable", return_value="VKZ123")
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsVectorLayer")
def test_verfahrensgebiet_alkis_fails_on_invalid_wfs_layer(self, mock_qgsvectorlayer, _mock_get_var, _mock_check):
_DummyMessageBox.last_critical = None
mock_qgsvectorlayer.return_value = _DummyTempLayer(valid=False)
layer, status = vg.verfahrensgebiet_alkis(tab_widget=None)
self.assertIsNone(layer)
self.assertEqual(status, vg.LoadStatus.NONE)
self.assertIsNotNone(_DummyMessageBox.last_critical)
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsFeature")
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsGeometry.unaryUnion", return_value=_DummyGeom(empty=False))
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis._check_existing_layer", return_value=(None, vg.LoadStatus.FIRST))
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.get_variable", return_value="VKZ123")
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsVectorLayer")
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QMessageBox", new=_DummyMessageBox)
def test_verfahrensgebiet_alkis_success_returns_memory_layer(self, mock_qgsvectorlayer, _mock_get_var, _mock_check, _mock_union, mock_feature_cls):
temp_layer = _DummyTempLayer(valid=True, features=[_DummyFeature()])
memory_layer = _DummyMemoryLayer()
mock_qgsvectorlayer.side_effect = [temp_layer, memory_layer]
mock_feature = mock_feature_cls.return_value
mock_feature.setGeometry.return_value = None
mock_feature.setAttributes.return_value = None
layer, status = vg.verfahrensgebiet_alkis(tab_widget=None)
self.assertIs(layer, memory_layer)
self.assertEqual(status, vg.LoadStatus.FIRST)
if __name__ == "__main__":
unittest.main()
+595
View File
@@ -0,0 +1,595 @@
"""sn_verfahrensgebiet/tests/test_verfahrensgebiet_edit_service.py
Unit-Tests für das Verfahrensgebiet-Edit-Service-Modul.
Getestet werden:
- pruefe_flurstueck_layer (über Layerpruefer-Integration)
- pruefe_auswahl
- erstelle_verfahrensgebiet_aus_auswahl: Erfolgs- und Fehlerpfade
- ergaenze_verfahrensgebiet: nur neue IDs werden hinzugefügt
- entferne_aus_verfahrensgebiet: nur vorhandene IDs werden entfernt
- ersetze_verfahrensgebiet: Ergebnis entspricht Erstellen
- get_kennzeichen_menge: Hilfsfunktion
- Zähler (erfolgreich/fehlgeschlagen/bereits_vorhanden/nicht_enthalten)
"""
import sys
import types
import unittest
from unittest.mock import patch, MagicMock
# ---------------------------------------------------------------------------
# Minimales QGIS-Mock-Setup (muss vor allen sn_*-Importen erfolgen)
# ---------------------------------------------------------------------------
if "qgis" not in sys.modules:
qgis_mod = types.ModuleType("qgis")
qgis_core = types.ModuleType("qgis.core")
qgis_utils = types.ModuleType("qgis.utils")
qgis_utils.iface = types.SimpleNamespace()
qgis_utils.plugins = {}
qgis_mod.utils = qgis_utils
qgis_mod.core = qgis_core
sys.modules["qgis"] = qgis_mod
sys.modules["qgis.core"] = qgis_core
sys.modules["qgis.utils"] = qgis_utils
from sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service import (
pruefe_flurstueck_layer,
pruefe_auswahl,
erstelle_verfahrensgebiet_aus_auswahl,
ergaenze_verfahrensgebiet,
entferne_aus_verfahrensgebiet,
ersetze_verfahrensgebiet,
get_kennzeichen_menge,
FELD_KENNZEICHEN,
FELD_GEMARKUNG,
)
# ---------------------------------------------------------------------------
# Test-Dummies
# ---------------------------------------------------------------------------
class _DummyGeom:
def __init__(self, empty=False):
self._empty = empty
def isEmpty(self):
return self._empty
def difference(self, other):
"""Dummy-Differenz: immer nicht-leeres Ergebnis (kein echter Geometrieschnitt)."""
return _DummyGeom(empty=False)
class _DummyFeature:
"""Feature-Dummy mit dict-artigem Attributzugriff und konfigurierbarer Geometrie."""
def __init__(self, kz: str, gemarkung: str = "1234", geom_empty: bool = False, feat_id: int = 0):
self._attrs = {FELD_KENNZEICHEN: kz, FELD_GEMARKUNG: gemarkung}
self._geom_empty = geom_empty
self._id = feat_id
self._set_geom = None
self._set_attrs = None
def __getitem__(self, key):
return self._attrs.get(key, None)
def geometry(self):
return _DummyGeom(self._geom_empty)
def id(self):
return self._id
def setGeometry(self, g):
self._set_geom = g
def setAttributes(self, attrs):
self._set_attrs = attrs
class _DummyProvider:
"""DataProvider-Dummy für Single-Feature-Verfahrensgebiet-Layer."""
def __init__(self, layer_ref):
self._layer = layer_ref
def addAttributes(self, attrs):
pass
def addFeatures(self, feats):
self._layer._features.extend(feats)
return True
def deleteFeatures(self, ids):
self._layer._deleted_ids.extend(ids)
return True
def changeAttributeValues(self, changes: dict):
"""changes: {fid: {field_idx: value}}"""
for feat in self._layer._features:
fid = feat.id() if callable(getattr(feat, "id", None)) else None
if fid in changes:
for field_idx, value in changes[fid].items():
feat._attr_by_idx[field_idx] = value
return True
def changeGeometryValues(self, changes: dict):
"""changes: {fid: new_geom}"""
for feat in self._layer._features:
fid = feat.id() if callable(getattr(feat, "id", None)) else None
if fid in changes:
feat._set_geom = changes[fid]
return True
class _DummySingleFeature:
"""Single-Feature-Dummy für den Verfahrensgebiet-Layer.
Speichert Kennzeichen als kommagetrennte Zeichenkette (new-style).
Feld-Indizes: 0=FELD_KENNZEICHEN, 1=FELD_GEMARKUNG
"""
def __init__(self, kz_komma: str, gemarkung: str = "", feat_id: int = 0, geom_empty: bool = False):
self._kz_komma = kz_komma
self._gemarkung = gemarkung
self._id = feat_id
self._geom_empty = geom_empty
self._set_geom = None
# Attribut-Mapping nach Index für changeAttributeValues
self._attr_by_idx: dict = {}
def __getitem__(self, key):
if key == FELD_KENNZEICHEN:
return self._attr_by_idx.get(0, self._kz_komma)
if key == FELD_GEMARKUNG:
return self._attr_by_idx.get(1, self._gemarkung)
return None
def geometry(self):
return _DummyGeom(self._geom_empty)
def id(self):
return self._id
def setGeometry(self, g):
self._set_geom = g
def setAttributes(self, attrs):
if len(attrs) > 0:
self._kz_komma = attrs[0]
if len(attrs) > 1:
self._gemarkung = attrs[1]
class _DummyVerfLayer:
"""Memory-Layer-Dummy für Verfahrensgebiet (Single-Feature-Modell)."""
def __init__(self, single_feature=None):
self._features = [single_feature] if single_feature else []
self._deleted_ids = []
def getFeatures(self):
deleted = set(self._deleted_ids)
return [
f for f in self._features
if (f.id() if callable(getattr(f, "id", None)) else None) not in deleted
]
def dataProvider(self):
return _DummyProvider(self)
def fields(self):
class _Fields:
def indexFromName(self, name):
if name == FELD_KENNZEICHEN:
return 0
if name == FELD_GEMARKUNG:
return 1
return -1
return _Fields()
def updateExtents(self):
pass
def triggerRepaint(self):
pass
def updateFields(self):
pass
def isValid(self):
return True
def crs(self):
class _Crs:
def authid(self):
return "EPSG:25833"
return _Crs()
class _DummySourceLayer:
"""Flurstück-Quell-Layer-Dummy mit konfigurierbarer Auswahl."""
def __init__(self, selected=None, all_features=None, feature_count=None):
self._selected = list(selected or [])
self._all = list(all_features or [])
self._count = feature_count if feature_count is not None else len(self._selected)
def selectedFeatures(self):
return self._selected
def selectedFeatureCount(self):
return self._count
def getFeatures(self):
return self._all
def crs(self):
class _Crs:
def authid(self):
return "EPSG:25833"
return _Crs()
class _MockLayerpruefer:
"""Steuerbarer Mock für Layerpruefer.pruefe()."""
def __init__(self, ok, aktion="ok", meldung=""):
self._ok = ok
self._aktion = aktion
self._meldung = meldung
def pruefe(self):
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
return pruef_ergebnis(ok=self._ok, aktion=self._aktion, meldung=self._meldung)
# ---------------------------------------------------------------------------
# Tests: pruefe_flurstueck_layer
# ---------------------------------------------------------------------------
class TestPruefeFlurstueckLayer(unittest.TestCase):
def test_layer_ok_passes(self):
"""Gültiger Polygon-Vektor-Layer mit Pflichtfeldern → ok=True."""
import sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service as module
original = module.Layerpruefer
class _PassPruefer:
def __init__(self, **_):
pass
def pruefe(self):
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
return pruef_ergebnis(ok=True, aktion="ok")
module.Layerpruefer = _PassPruefer
try:
ergebnis = pruefe_flurstueck_layer(object())
self.assertTrue(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "ok")
finally:
module.Layerpruefer = original
def test_layer_missing_field_fails(self):
"""Layer ohne Pflichtfelder → ok=False, aktion=felder_fehlen."""
import sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service as module
original = module.Layerpruefer
class _FailPruefer:
def __init__(self, **_):
pass
def pruefe(self):
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
return pruef_ergebnis(ok=False, aktion="felder_fehlen",
meldung="Feld fehlt")
module.Layerpruefer = _FailPruefer
try:
ergebnis = pruefe_flurstueck_layer(object())
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "felder_fehlen")
finally:
module.Layerpruefer = original
def test_layer_wrong_geometry_fails(self):
"""Layer mit falschem Geometrietyp → ok=False, aktion=falscher_geotyp."""
import sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service as module
original = module.Layerpruefer
class _WrongGeomPruefer:
def __init__(self, **_):
pass
def pruefe(self):
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
return pruef_ergebnis(ok=False, aktion="falscher_geotyp",
meldung="Kein Polygon")
module.Layerpruefer = _WrongGeomPruefer
try:
ergebnis = pruefe_flurstueck_layer(object())
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "falscher_geotyp")
finally:
module.Layerpruefer = original
# ---------------------------------------------------------------------------
# Tests: pruefe_auswahl
# ---------------------------------------------------------------------------
class TestPruefeAuswahl(unittest.TestCase):
def test_no_selection_fails(self):
layer = _DummySourceLayer(selected=[], feature_count=0)
ergebnis = pruefe_auswahl(layer)
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "layer_leer")
def test_with_selection_passes(self):
feats = [_DummyFeature("1000-001")]
layer = _DummySourceLayer(selected=feats)
ergebnis = pruefe_auswahl(layer)
self.assertTrue(ergebnis.ok)
self.assertIn("1", ergebnis.meldung) # "1 Flurstücke ausgewählt."
def test_selectedFeatureCount_exception_treated_as_zero(self):
class _BrokenLayer:
def selectedFeatureCount(self):
raise RuntimeError("broken")
ergebnis = pruefe_auswahl(_BrokenLayer())
self.assertFalse(ergebnis.ok)
# ---------------------------------------------------------------------------
# Tests: get_kennzeichen_menge
# ---------------------------------------------------------------------------
class TestGetKennzeichenMenge(unittest.TestCase):
def test_returns_set_of_ids(self):
"""Kommagetrennte Kennzeichen im Single-Feature werden korrekt geparst."""
feat = _DummySingleFeature("A001,A002,A001", feat_id=1) # Duplikat → set
layer = _DummyVerfLayer(feat)
result = get_kennzeichen_menge(layer)
self.assertEqual(result, {"A001", "A002"})
def test_empty_layer_returns_empty_set(self):
layer = _DummyVerfLayer()
result = get_kennzeichen_menge(layer)
self.assertEqual(result, set())
def test_single_kennzeichen_without_comma(self):
"""Einzelnes Kennzeichen ohne Komma wird korrekt erkannt."""
feat = _DummySingleFeature("X999", feat_id=1)
layer = _DummyVerfLayer(feat)
result = get_kennzeichen_menge(layer)
self.assertEqual(result, {"X999"})
# ---------------------------------------------------------------------------
# Tests: erstelle_verfahrensgebiet_aus_auswahl
# ---------------------------------------------------------------------------
class TestErstelle(unittest.TestCase):
def _patch_all(self, test_func, union_empty=False):
"""Patcht _erstelle_memory_layer und QgsGeometry.unaryUnion."""
import sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service as module
orig_erstelle = module._erstelle_memory_layer
orig_union = module.QgsGeometry.unaryUnion
mem = _DummyVerfLayer()
module._erstelle_memory_layer = lambda _crs: mem
module.QgsGeometry.unaryUnion = staticmethod(
lambda _geoms: _DummyGeom(empty=union_empty)
)
try:
test_func(mem)
finally:
module._erstelle_memory_layer = orig_erstelle
module.QgsGeometry.unaryUnion = staticmethod(orig_union)
def test_success_counts(self):
"""3 gültige Features → erfolgreich=3, fehlgeschlagen=0, 1 Union-Feature im Layer."""
feats = [
_DummyFeature("A001", feat_id=0),
_DummyFeature("A002", feat_id=1),
_DummyFeature("A003", feat_id=2),
]
source = _DummySourceLayer(selected=feats)
def run(mem):
layer, ok, fail = erstelle_verfahrensgebiet_aus_auswahl(source)
self.assertIs(layer, mem)
self.assertEqual(ok, 3)
self.assertEqual(fail, 0)
# Single-Feature-Modell: genau 1 Union-Feature
self.assertEqual(len(mem._features), 1)
self._patch_all(run)
def test_partial_failure_on_empty_geometry(self):
"""2 ok + 1 leere Geometrie → erfolgreich=2, fehlgeschlagen=1."""
feats = [
_DummyFeature("A001"),
_DummyFeature("A002", geom_empty=True),
_DummyFeature("A003"),
]
source = _DummySourceLayer(selected=feats)
def run(mem):
_, ok, fail = erstelle_verfahrensgebiet_aus_auswahl(source)
self.assertEqual(ok, 2)
self.assertEqual(fail, 1)
self._patch_all(run)
def test_all_fail_on_empty_geometries(self):
"""Alle Features haben leere Geometrie → erfolgreich=0, kein Feature im Layer."""
feats = [_DummyFeature("A001", geom_empty=True)]
source = _DummySourceLayer(selected=feats)
def run(mem):
_, ok, fail = erstelle_verfahrensgebiet_aus_auswahl(source)
self.assertEqual(ok, 0)
self.assertEqual(fail, 1)
self.assertEqual(len(mem._features), 0) # kein Union-Feature
self._patch_all(run)
# ---------------------------------------------------------------------------
# Tests: ergaenze_verfahrensgebiet
# ---------------------------------------------------------------------------
class TestErgaenzeVerfahrensgebiet(unittest.TestCase):
def _patch_union(self):
import sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service as module
orig = module.QgsGeometry.unaryUnion
module.QgsGeometry.unaryUnion = staticmethod(lambda _g: _DummyGeom(empty=False))
return module, orig
def test_adds_only_new_ids(self):
"""Auswahl enthält 1 neue + 1 bereits vorhandene ID → hinzugefügt=1."""
verf = _DummyVerfLayer(_DummySingleFeature("A001", feat_id=1))
selected = [
_DummyFeature("A001"), # bereits vorhanden
_DummyFeature("A002"), # neu
]
source = _DummySourceLayer(selected=selected)
module, orig = self._patch_union()
try:
_, hinzugefuegt, bereits_vorhanden, fehlgeschlagen = ergaenze_verfahrensgebiet(
verf, source
)
finally:
module.QgsGeometry.unaryUnion = staticmethod(orig)
self.assertEqual(hinzugefuegt, 1)
self.assertEqual(bereits_vorhanden, 1)
self.assertEqual(fehlgeschlagen, 0)
def test_skip_empty_geometry_counts_as_failed(self):
"""Neue ID mit leerer Geometrie → fehlgeschlagen=1, hinzugefügt=0."""
verf = _DummyVerfLayer() # leer
selected = [_DummyFeature("A001", geom_empty=True)]
source = _DummySourceLayer(selected=selected)
_, hinzugefuegt, _, fehlgeschlagen = ergaenze_verfahrensgebiet(verf, source)
self.assertEqual(hinzugefuegt, 0)
self.assertEqual(fehlgeschlagen, 1)
def test_all_already_present(self):
"""Alle ausgewählten IDs bereits vorhanden → bereits_vorhanden=n, hinzugefügt=0."""
verf = _DummyVerfLayer(_DummySingleFeature("A001,A002", feat_id=1))
selected = [_DummyFeature("A001"), _DummyFeature("A002")]
source = _DummySourceLayer(selected=selected)
_, hinzugefuegt, bereits_vorhanden, fehlgeschlagen = ergaenze_verfahrensgebiet(
verf, source
)
self.assertEqual(hinzugefuegt, 0)
self.assertEqual(bereits_vorhanden, 2)
self.assertEqual(fehlgeschlagen, 0)
# ---------------------------------------------------------------------------
# Tests: entferne_aus_verfahrensgebiet
# ---------------------------------------------------------------------------
class TestEntferneAusVerfahrensgebiet(unittest.TestCase):
def test_removes_present_ids(self):
"""Auswahl enthält 1 vorhandene ID → entfernt=1, nicht_enthalten=0."""
verf = _DummyVerfLayer(_DummySingleFeature("A001,A002", feat_id=1))
selected = [_DummyFeature("A001")]
source = _DummySourceLayer(selected=selected)
_, entfernt, nicht_enthalten, fehlgeschlagen = entferne_aus_verfahrensgebiet(
verf, source
)
self.assertEqual(entfernt, 1)
self.assertEqual(nicht_enthalten, 0)
self.assertEqual(fehlgeschlagen, 0)
def test_counts_ids_not_in_layer(self):
"""Auswahl enthält ID, die nicht im Verfahrensgebiet ist → nicht_enthalten=1."""
verf = _DummyVerfLayer(_DummySingleFeature("A001", feat_id=1))
selected = [
_DummyFeature("A001"), # vorhanden
_DummyFeature("A999"), # nicht enthalten
]
source = _DummySourceLayer(selected=selected)
_, entfernt, nicht_enthalten, fehlgeschlagen = entferne_aus_verfahrensgebiet(
verf, source
)
self.assertEqual(entfernt, 1)
self.assertEqual(nicht_enthalten, 1)
def test_none_present_in_layer(self):
"""Auswahl hat IDs, die alle nicht im Layer sind → entfernt=0, nicht_enthalten=n."""
verf = _DummyVerfLayer(_DummySingleFeature("X001", feat_id=1))
selected = [_DummyFeature("A999"), _DummyFeature("B888")]
source = _DummySourceLayer(selected=selected)
_, entfernt, nicht_enthalten, fehlgeschlagen = entferne_aus_verfahrensgebiet(
verf, source
)
self.assertEqual(entfernt, 0)
self.assertEqual(nicht_enthalten, 2)
def test_empty_selection_removes_nothing(self):
"""Leere Auswahl → nichts entfernt, nichts nicht_enthalten."""
verf = _DummyVerfLayer(_DummySingleFeature("A001", feat_id=1))
source = _DummySourceLayer(selected=[])
_, entfernt, nicht_enthalten, fehlgeschlagen = entferne_aus_verfahrensgebiet(
verf, source
)
self.assertEqual(entfernt, 0)
self.assertEqual(nicht_enthalten, 0)
def test_removes_last_id_deletes_feature(self):
"""Wird das letzte Kennzeichen entfernt, wird das Feature gelöscht."""
verf = _DummyVerfLayer(_DummySingleFeature("A001", feat_id=1))
selected = [_DummyFeature("A001")]
source = _DummySourceLayer(selected=selected)
_, entfernt, _, _ = entferne_aus_verfahrensgebiet(verf, source)
self.assertEqual(entfernt, 1)
# Feature soll gelöscht worden sein
self.assertIn(1, verf._deleted_ids)
# ---------------------------------------------------------------------------
# Tests: ersetze_verfahrensgebiet
# ---------------------------------------------------------------------------
class TestErsetze(unittest.TestCase):
def test_ersetze_delegates_to_erstelle(self):
"""ersetze_verfahrensgebiet gibt dasselbe Ergebnis wie erstelle zurück."""
feats = [_DummyFeature("A001")]
source = _DummySourceLayer(selected=feats)
import sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service as module
orig_erstelle = module._erstelle_memory_layer
orig_union = module.QgsGeometry.unaryUnion
mem = _DummyVerfLayer()
module._erstelle_memory_layer = lambda _crs: mem
module.QgsGeometry.unaryUnion = staticmethod(lambda _g: _DummyGeom(empty=False))
try:
layer, ok, fail = ersetze_verfahrensgebiet(source)
self.assertIs(layer, mem)
self.assertEqual(ok, 1)
self.assertEqual(fail, 0)
finally:
module._erstelle_memory_layer = orig_erstelle
module.QgsGeometry.unaryUnion = staticmethod(orig_union)
if __name__ == "__main__":
unittest.main()
+439
View File
@@ -0,0 +1,439 @@
import sys
import types
import unittest
from unittest.mock import patch
if "qgis" not in sys.modules:
qgis_mod = types.ModuleType("qgis")
qgis_utils = types.ModuleType("qgis.utils")
qgis_utils.iface = types.SimpleNamespace(
mapCanvas=lambda: types.SimpleNamespace(setExtent=lambda _e: None, refresh=lambda: None)
)
qgis_utils.plugins = {}
qgis_mod.utils = qgis_utils
sys.modules["qgis"] = qgis_mod
sys.modules["qgis.utils"] = qgis_utils
from sn_verfahrensgebiet.ui.tabs.working_tab import WorkingTab, LoadStatus
class _DummyLayer:
def __init__(self, valid=True, layer_id="L1"):
self._valid = valid
self._id = layer_id
def isValid(self):
return self._valid
def id(self):
return self._id
def extent(self):
return object()
class _DummyProject:
def __init__(self):
self.added = []
def addMapLayer(self, layer):
self.added.append(layer)
class _DummyCanvas:
def __init__(self):
self.extent = None
def setExtent(self, extent):
self.extent = extent
def refresh(self):
return None
class _DummyButton:
def __init__(self):
self._enabled = True
self._text = "Aus ALKIS laden"
def setEnabled(self, enabled):
self._enabled = enabled
def isEnabled(self):
return self._enabled
def setText(self, text):
self._text = text
def text(self):
return self._text
class _DummyCombo:
def __init__(self):
self.layer = None
def setLayer(self, layer):
self.layer = layer
class TestWorkingTab(unittest.TestCase):
def _make_tab(self):
tab = WorkingTab.__new__(WorkingTab)
tab.btn_verf_alkis = _DummyButton()
tab.btn_flurst_alkis = _DummyButton()
tab.btn_verf_aus_auswahl = _DummyButton()
tab.btn_verf_aus_liste = _DummyButton()
tab.verf_layer_combo = _DummyCombo()
tab.haken_verf = object()
tab.haken_flurst = object()
tab.setze_haken = lambda *_args, **_kwargs: None
# Pruefmanager-Mock: verarbeite() wird im Test ggf. überschrieben
tab.pruefmanager = types.SimpleNamespace(
verarbeite=lambda ergebnis: ergebnis
)
return tab
def test_set_busy_updates_button_state(self):
tab = self._make_tab()
tab._set_busy(tab.btn_verf_alkis, True)
self.assertFalse(tab.btn_verf_alkis.isEnabled())
self.assertEqual(tab.btn_verf_alkis.text(), "Lade ...")
tab._set_busy(tab.btn_verf_alkis, False)
self.assertTrue(tab.btn_verf_alkis.isEnabled())
self.assertEqual(tab.btn_verf_alkis.text(), "Aus ALKIS laden")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
def test_on_verf_layer_changed_valid_sets_variable(self, mock_set_layer_id):
tab = self._make_tab()
layer = _DummyLayer(valid=True, layer_id="layer_123")
tab._on_verf_layer_changed(layer)
mock_set_layer_id.assert_called_once_with("layer_123")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
def test_on_verf_layer_changed_none_clears_variable(self, mock_set_layer_id):
tab = self._make_tab()
tab._on_verf_layer_changed(None)
mock_set_layer_id.assert_called_once_with("")
@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")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
def test_handle_verf_alkis_success_path(
self,
mock_set_layer_id,
mock_iface,
mock_alkis,
mock_project_instance,
mock_apply_style,
mock_success,
):
tab = self._make_tab()
layer = _DummyLayer(valid=True, layer_id="layer_ok")
mock_alkis.return_value = (layer, LoadStatus.FIRST)
mock_project_instance.return_value = _DummyProject()
mock_iface.mapCanvas.return_value = _DummyCanvas()
tab.handle_verf_alkis()
mock_apply_style.assert_called_once()
mock_success.assert_called_once()
mock_set_layer_id.assert_called_with("layer_ok")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_alkis", return_value=(None, LoadStatus.NONE))
def test_handle_verf_alkis_failure_clears_layer_var(self, _mock_alkis, mock_set_layer_id):
tab = self._make_tab()
tab.handle_verf_alkis()
mock_set_layer_id.assert_called_once_with("")
# ------------------------------------------------------------------
# handle_flurst_alkis
# ------------------------------------------------------------------
@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.iface")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis")
def test_handle_flurst_alkis_success_sets_haken_and_style(
self, mock_alkis, mock_iface, mock_project, mock_apply_style, mock_success
):
tab = self._make_tab()
haken_calls = []
tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v))
layer = _DummyLayer(valid=True)
mock_alkis.return_value = (layer, LoadStatus.FIRST)
mock_project.return_value = _DummyProject()
mock_iface.mapCanvas.return_value = _DummyCanvas()
tab.handle_flurst_alkis()
mock_apply_style.assert_called_once_with(layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
mock_success.assert_called_once()
self.assertEqual(len(haken_calls), 1)
self.assertTrue(haken_calls[0][1]) # haken gesetzt
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.info")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis")
def test_handle_flurst_alkis_existing_layer_keep_path(self, mock_alkis, mock_info):
tab = self._make_tab()
haken_calls = []
tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v))
layer = _DummyLayer(valid=True)
mock_alkis.return_value = (layer, LoadStatus.KEEP)
tab.handle_flurst_alkis()
mock_info.assert_called_once()
self.assertEqual(len(haken_calls), 1)
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis")
def test_handle_flurst_alkis_invalid_input_returns_early(self, mock_alkis):
"""Status NONE (z. B. Nutzerabbruch) → kein Layer, kein Haken."""
tab = self._make_tab()
haken_calls = []
tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v))
mock_alkis.return_value = (None, LoadStatus.NONE)
tab.handle_flurst_alkis()
self.assertEqual(len(haken_calls), 0)
# Button muss am Ende wieder freigegeben sein (finally)
self.assertTrue(tab.btn_flurst_alkis.isEnabled())
# ------------------------------------------------------------------
# handle_verf_aus_auswahl – Vorbedingungsprüfungen
# ------------------------------------------------------------------
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.warning")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_verfahrensgebiet_from_selection_requires_active_layer(
self, mock_iface, mock_warning
):
"""Kein aktiver Layer → warning, kein Layer wird erstellt."""
tab = self._make_tab()
mock_iface.activeLayer.return_value = None
tab.handle_verf_aus_auswahl()
mock_warning.assert_called_once()
self.assertIn("aktiver Layer", mock_warning.call_args[0][1])
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.warning")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_verfahrensgebiet_from_selection_requires_required_fields(
self, mock_iface, mock_pruefe, mock_warning
):
"""Layer ohne Pflichtfelder → warning, kein Layer wird erstellt."""
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis as PruefErg
tab = self._make_tab()
active = _DummyLayer(valid=True)
mock_iface.activeLayer.return_value = active
mock_pruefe.return_value = PruefErg(
ok=False, aktion="felder_fehlen", meldung="Feld fehlt"
)
tab.handle_verf_aus_auswahl()
mock_warning.assert_called_once()
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.warning")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_auswahl")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_verfahrensgebiet_from_selection_requires_selection(
self, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl, mock_warning
):
"""Keine Flurstück-Auswahl → warning, kein Layer erstellt."""
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis as PruefErg
tab = self._make_tab()
active = _DummyLayer(valid=True)
mock_iface.activeLayer.return_value = active
mock_pruefe_layer.return_value = PruefErg(ok=True, aktion="ok")
mock_pruefe_auswahl.return_value = PruefErg(
ok=False, aktion="layer_leer", meldung="Keine Auswahl"
)
tab.handle_verf_aus_auswahl()
mock_warning.assert_called_once()
# ------------------------------------------------------------------
# handle_verf_aus_auswahl – Erstlauf (kein bestehender Layer)
# ------------------------------------------------------------------
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QMessageBox")
@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.erstelle_verfahrensgebiet_aus_auswahl")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_auswahl")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_verfahrensgebiet_from_selection_new_layer_success(
self, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl,
mock_erstelle, mock_project_inst, mock_apply_style, mock_msgbox
):
"""Erstlauf ohne bestehenden Layer → Layer wird erstellt und hinzugefügt."""
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis as PruefErg
tab = self._make_tab()
haken_calls = []
tab.setze_haken = lambda lbl, v: haken_calls.append(v)
active = _DummyLayer(valid=True)
mock_iface.activeLayer.return_value = active
mock_iface.mapCanvas.return_value = _DummyCanvas()
mock_pruefe_layer.return_value = PruefErg(ok=True, aktion="ok")
mock_pruefe_auswahl.return_value = PruefErg(ok=True, aktion="ok", meldung="3 Flurstücke")
new_layer = _DummyLayer(valid=True, layer_id="new_verf")
mock_erstelle.return_value = (new_layer, 3, 0)
project = _DummyProject()
project.mapLayersByName = lambda _name: [] # kein bestehender Layer
mock_project_inst.return_value = project
tab.handle_verf_aus_auswahl()
self.assertIn(new_layer, project.added)
mock_apply_style.assert_called_once_with(new_layer, "verfahrensgebiet.qml")
self.assertTrue(haken_calls[0])
# ------------------------------------------------------------------
# handle_verf_aus_auswahl – Ergänzen / Entfernen / Ersetzen
# ------------------------------------------------------------------
def _make_entscheidungs_ergebnis(self, aktion: str):
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis as PruefErg
return PruefErg(ok=True, aktion=aktion)
def _setup_existierenden_layer(self, tab, mock_iface, mock_pruefe_layer,
mock_pruefe_auswahl, mock_project_inst,
mock_canvas, entscheidung: str):
"""Gemeinsames Setup für Delta-Operationen mit bestehendem Layer."""
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis as PruefErg
active = _DummyLayer(valid=True)
mock_iface.activeLayer.return_value = active
mock_iface.mapCanvas.return_value = mock_canvas
mock_pruefe_layer.return_value = PruefErg(ok=True, aktion="ok")
mock_pruefe_auswahl.return_value = PruefErg(ok=True, aktion="ok", meldung="2 Flurstücke")
verf_layer = _DummyLayer(valid=True, layer_id="verf_existing")
project = _DummyProject()
project.added = []
project.mapLayersByName = lambda _: [verf_layer]
project.removeMapLayer = lambda lid: None
mock_project_inst.return_value = project
# Pruefmanager gibt Entscheidung zurück
tab.pruefmanager.verarbeite = lambda _e: self._make_entscheidungs_ergebnis(entscheidung)
return active, verf_layer, project
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QMessageBox")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.ergaenze_verfahrensgebiet")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_auswahl")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_verfahrensgebiet_from_selection_existing_layer_ergaenzen_dispatch(
self, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl,
mock_project_inst, mock_ergaenze, mock_apply_style, mock_msgbox
):
"""Entscheidung 'ergaenzen' → ergaenze_verfahrensgebiet wird aufgerufen."""
tab = self._make_tab()
canvas = _DummyCanvas()
active, verf_layer, _ = self._setup_existierenden_layer(
tab, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl,
mock_project_inst, canvas, "verfahrensgebiet_ergaenzen"
)
mock_ergaenze.return_value = (verf_layer, 2, 0, 0)
tab.handle_verf_aus_auswahl()
mock_ergaenze.assert_called_once_with(verf_layer, active)
mock_apply_style.assert_called_once_with(verf_layer, "verfahrensgebiet.qml")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QMessageBox")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.entferne_aus_verfahrensgebiet")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_auswahl")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_verfahrensgebiet_from_selection_existing_layer_entfernen_dispatch(
self, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl,
mock_project_inst, mock_entferne, mock_apply_style, mock_msgbox
):
"""Entscheidung 'entfernen' → entferne_aus_verfahrensgebiet wird aufgerufen."""
tab = self._make_tab()
canvas = _DummyCanvas()
active, verf_layer, _ = self._setup_existierenden_layer(
tab, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl,
mock_project_inst, canvas, "verfahrensgebiet_entfernen"
)
mock_entferne.return_value = (verf_layer, 1, 1, 0)
tab.handle_verf_aus_auswahl()
mock_entferne.assert_called_once_with(verf_layer, active)
mock_apply_style.assert_called_once_with(verf_layer, "verfahrensgebiet.qml")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QMessageBox")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.ersetze_verfahrensgebiet")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_auswahl")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.pruefe_flurstueck_layer")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_verfahrensgebiet_from_selection_existing_layer_ersetzen_dispatch(
self, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl,
mock_project_inst, mock_ersetze, mock_apply_style, mock_msgbox
):
"""Entscheidung 'ersetzen' → alter Layer entfernt, neuer hinzugefügt."""
tab = self._make_tab()
canvas = _DummyCanvas()
active, verf_layer, project = self._setup_existierenden_layer(
tab, mock_iface, mock_pruefe_layer, mock_pruefe_auswahl,
mock_project_inst, canvas, "verfahrensgebiet_ersetzen"
)
removed = []
project.removeMapLayer = lambda lid: removed.append(lid)
new_layer = _DummyLayer(valid=True, layer_id="new_verf_replaced")
mock_ersetze.return_value = (new_layer, 3, 0)
tab.handle_verf_aus_auswahl()
mock_ersetze.assert_called_once_with(active)
self.assertIn(verf_layer.id(), removed)
self.assertIn(new_layer, project.added)
mock_apply_style.assert_called_once_with(new_layer, "verfahrensgebiet.qml")
# ------------------------------------------------------------------
# handle_verf_aus_liste
# ------------------------------------------------------------------
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.show_info_dialog")
def test_aus_liste_button_shows_not_implemented_hint(self, mock_info):
tab = self._make_tab()
tab.handle_verf_aus_liste()
mock_info.assert_called_once()
if __name__ == "__main__":
unittest.main()
+53 -1
View File
@@ -1,7 +1,59 @@
"""sn_verfahrensgebiet/ui/dockwidget.py – Haupt-DockWidget für sn_verfahrensgebiet.
Verwaltet die Tabs für Verfahrensgebiet-Bearbeitung, Druck und Einstellungen.
"""
from sn_basis.ui.tabs.settings_tab import SettingsTab
from sn_basis.ui.tabs.print_tab import PrintTab
from sn_verfahrensgebiet.ui.tabs.working_tab import WorkingTab
from sn_basis.ui.base_dockwidget import BaseDockWidget
from sn_basis.functions.qt_wrapper import QTabWidget
from sn_basis.functions.message_wrapper import error
class DockWidget(BaseDockWidget):
tabs = [WorkingTab, SettingsTab]
"""Haupt-DockWidget für sn_verfahrensgebiet mit Tab-Verwaltung.
Attributes:
tabs: Liste von Tab-Klassen (WorkingTab, PrintTab, SettingsTab)
pruefmanager: Prüfmanager-Instanz für Validierung
data_grabber: DataGrabber-Instanz für Fachdatenabruf
"""
tabs = [WorkingTab, PrintTab, SettingsTab]
def __init__(self, parent=None, subtitle="", pruefmanager=None, data_grabber=None):
"""Initialisiert das DockWidget mit Services und Tabs.
Args:
parent: Übergeordnetes Widget
subtitle: Untertitel des DockWidgets
pruefmanager: Prüfmanager-Instanz
data_grabber: DataGrabber-Instanz
"""
super().__init__(parent, subtitle)
self.pruefmanager = pruefmanager
self.data_grabber = data_grabber
self._init_tabs_with_services()
def _init_tabs_with_services(self):
"""Tabs neu aufbauen und Services (z.B. fuer PrintTab) injizieren."""
try:
self.setWidget(None)
tab_widget = QTabWidget()
for tab_class in self.tabs:
tab_instance = tab_class(self)
tab_title = getattr(tab_class, "tab_title", tab_class.__name__)
tab_widget.addTab(tab_instance, tab_title)
if hasattr(tab_instance, "set_services"):
tab_instance.set_services(
pruefmanager=self.pruefmanager,
data_grabber=self.data_grabber,
)
self.setWidget(tab_widget)
except Exception as e:
error("Services-Tabs konnten nicht initialisiert werden", str(e))
+272 -14
View File
@@ -1,13 +1,38 @@
"""sn_verfahrensgebiet/ui/tabs/working_tab.py – Bearbeitungs-Tab.
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 Qt
from sn_basis.functions import Qgis, QgsProject #QgsMessageLog
from sn_basis.functions.qgisui_wrapper import QgsMapLayerComboBox
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.dialog_wrapper import show_info_dialog
from sn_basis.modules.Pruefmanager import Pruefmanager
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
from qgis.utils import iface
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import verfahrensgebiet_alkis, LoadStatus
from sn_verfahrensgebiet.functions.flurstueck_alkis import flurstueck_alkis
from sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service import (
pruefe_flurstueck_layer,
pruefe_auswahl,
erstelle_verfahrensgebiet_aus_auswahl,
ergaenze_verfahrensgebiet,
entferne_aus_verfahrensgebiet,
ersetze_verfahrensgebiet,
)
from sn_basis.functions.message_wrapper import success, info, warning, error
from sn_basis.functions.ly_style_wrapper import apply_style
class WorkingTab(QWidget):
"""Tab für die Verfahrensgebiet-Bearbeitung und Verwaltung.
Attributes:
tab_title: Anzeigetitel des Tabs ("Bearbeitung")
"""
tab_title = "Bearbeitung"
def __init__(self, parent=None):
@@ -16,7 +41,7 @@ class WorkingTab(QWidget):
layout = QVBoxLayout()
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
# Abschnitt 1: Verfahrensgebiet
# Abschnitt 1: Verfahrensgebiet laden
row_verf_header = QHBoxLayout()
lbl_verf = QLabel("<b>Verfahrensgebiet</b>")
self.haken_verf = self._haken_label()
@@ -32,7 +57,21 @@ class WorkingTab(QWidget):
row_verf_buttons.addWidget(self.btn_verf_shape)
layout.addLayout(row_verf_buttons)
# Abschnitt 2: Flurstücke (Platzhalter für spätere Implementierung)
# Abschnitt 1b: Verfahrensgebiet-Layer auswählen
row_layer_label = QHBoxLayout()
lbl_layer = QLabel("Oder: Verfahrensgebiet-Layer auswählen")
lbl_layer.setStyleSheet("font-style: italic; color: gray; margin-top: 8px;")
row_layer_label.addWidget(lbl_layer)
row_layer_label.addStretch()
layout.addLayout(row_layer_label)
self.verf_layer_combo = QgsMapLayerComboBox(self)
self.verf_layer_combo.setFilters(QgsMapLayerProxyModel.VectorLayer)
self.verf_layer_combo.setToolTip("Wähle den Verfahrensgebiet-Layer, oder lasse es leer für Automatik")
self.verf_layer_combo.layerChanged.connect(self._on_verf_layer_changed)
layout.addWidget(self.verf_layer_combo)
# Abschnitt 2: Flurstücke
row_flurst_header = QHBoxLayout()
lbl_flurst = QLabel("<b>Flurstücke</b>")
self.haken_flurst = self._haken_label()
@@ -48,51 +87,86 @@ class WorkingTab(QWidget):
row_flurst_buttons.addWidget(self.btn_flurst_shape)
layout.addLayout(row_flurst_buttons)
# Abschnitt 3: Verfahrensgebiet erstellen
row_verf_erstellen_header = QHBoxLayout()
lbl_verf_erstellen = QLabel("<b>Verfahrensgebiet erstellen</b>")
row_verf_erstellen_header.addWidget(lbl_verf_erstellen)
row_verf_erstellen_header.addStretch()
layout.addLayout(row_verf_erstellen_header)
row_verf_erstellen_buttons = QHBoxLayout()
self.btn_verf_aus_auswahl = QPushButton("aus Auswahl")
self.btn_verf_aus_liste = QPushButton("Aus Liste")
row_verf_erstellen_buttons.addWidget(self.btn_verf_aus_auswahl)
row_verf_erstellen_buttons.addWidget(self.btn_verf_aus_liste)
layout.addLayout(row_verf_erstellen_buttons)
layout.addStretch()
self.setLayout(layout)
# Pruefmanager für interaktive Entscheidungen (Ergänzen/Entfernen/Ersetzen)
self.pruefmanager = Pruefmanager(ui_modus="qgis", parent=self)
# Signale verbinden
self.btn_verf_alkis.clicked.connect(self.handle_verf_alkis)
self.btn_flurst_alkis.clicked.connect(self.handle_flurst_alkis)
self.btn_verf_aus_auswahl.clicked.connect(self.handle_verf_aus_auswahl)
self.btn_verf_aus_liste.clicked.connect(self.handle_verf_aus_liste)
# self.btn_verf_shape.clicked.connect(self.handle_verf_shape)
# self.btn_flurst_alkis.clicked.connect(self.handle_flurst_alkis)
# self.btn_flurst_shape.clicked.connect(self.handle_flurst_shape)
# Beim Init: aktuelle Verfahrensgebiet-Layer-ID laden und im Dropdown anzeigen
self._restore_verf_layer_selection()
def handle_verf_alkis(self):
self._set_busy(self.btn_verf_alkis, True)
self._set_busy(self.btn_verf_alkis, True, "Aus ALKIS laden")
try:
layer, status = verfahrensgebiet_alkis(self)
if not layer or not layer.isValid():
set_verfahrensgebiet_layer_id("")
return
if status in (LoadStatus.FIRST, LoadStatus.RELOAD):
# Gemeinsame Logik für erstmaliges und erneutes Laden
QgsProject.instance().addMapLayer(layer)
iface.mapCanvas().setExtent(layer.extent())
#iface.mapCanvas().refresh()
apply_style(layer, "verfahrensgebiet.qml")
iface.mapCanvas().refresh()
self.setze_haken(self.haken_verf, True)
# Unterschied nur in der Meldung
self.verf_layer_combo.setLayer(layer)
self._on_verf_layer_changed(layer)
msg = "erstmalig geladen" if status == LoadStatus.FIRST else "neu geladen"
success("Verfahrensgebiet", f"Layer wurde {msg}.", duration=5)
elif status == LoadStatus.KEEP:
self.verf_layer_combo.setLayer(layer)
self._on_verf_layer_changed(layer)
info("Verfahrensgebiet", "Vorhandener Layer wurde behalten.", duration=4)
elif status == LoadStatus.NONE:
self._on_verf_layer_changed(None)
warning("Verfahrensgebiet", "Layer konnte nicht geladen werden.", duration=None)
finally:
self._set_busy(self.btn_verf_alkis, False)
self._set_busy(self.btn_verf_alkis, False, "Aus ALKIS laden")
def _set_busy(self, button: QPushButton, busy: bool):
def _set_busy(self, button: QPushButton, busy: bool, original_text: str = "Aus ALKIS laden") -> None:
"""Setzt den Busy-State eines Buttons.
Parameters
----------
button :
Der zu sperrende/entsperrende Button.
busy : bool
True zum Sperren (Text → "Lade ..."), False zum Freigeben.
original_text : str
Text, der nach dem Freigeben wiederhergestellt wird.
Jeder Button gibt seinen eigenen Originaltext explizit an.
"""
button.setEnabled(not busy)
if busy:
button.setText("Lade ...")
else:
button.setText("Aus ALKIS laden")
button.setText("Lade ..." if busy else original_text)
def _haken_label(self) -> QLabel:
label = QLabel()
@@ -110,3 +184,187 @@ class WorkingTab(QWidget):
label.setText("")
label.setStyleSheet("")
def _on_verf_layer_changed(self, layer) -> None:
"""Callback: Nutzer hat einen anderen Layer im Dropdown gewählt."""
if layer and layer.isValid():
# Speichere die Layer-ID in der zentralen Variable
set_verfahrensgebiet_layer_id(layer.id())
else:
# Layer is None oder invalid; leere die Variable
set_verfahrensgebiet_layer_id("")
def _restore_verf_layer_selection(self) -> None:
"""Beim Tab-Init: Lade die zuletzt gespeicherte Verfahrensgebiet-Layer-ID."""
layer_id = get_verfahrensgebiet_layer_id()
if layer_id:
try:
project = QgsProject.instance()
layer = project.mapLayer(layer_id)
if layer and layer.isValid():
self.verf_layer_combo.setLayer(layer)
except Exception:
pass
# ------------------------------------------------------------------
# Flurstücke-Handler
# ------------------------------------------------------------------
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
if status in (LoadStatus.FIRST, LoadStatus.RELOAD):
QgsProject.instance().addMapLayer(layer)
# iface.mapCanvas().setExtent(layer.extent()) # Kartenausschnitt bleibt unverändert
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:
self._set_busy(self.btn_flurst_alkis, False, "Aus ALKIS laden")
# ------------------------------------------------------------------
# Verfahrensgebiet-erstellen-Handler
# ------------------------------------------------------------------
def handle_verf_aus_auswahl(self) -> None:
"""Erstellt oder aktualisiert den Verfahrensgebiet-Layer aus der Flurstück-Auswahl."""
self._set_busy(self.btn_verf_aus_auswahl, True, "aus Auswahl")
try:
# 1. Aktiven Layer ermitteln
active_layer = iface.activeLayer()
if not active_layer:
warning(
"Verfahrensgebiet erstellen",
"Kein aktiver Layer. Bitte den Flurstück-Layer aktivieren.",
duration=None,
)
return
# 2. Vorbedingungen via Layerpruefer
ergebnis = pruefe_flurstueck_layer(active_layer)
if not ergebnis.ok:
warning("Verfahrensgebiet erstellen", ergebnis.meldung, duration=None)
return
# 3. Auswahl prüfen
ergebnis_auswahl = pruefe_auswahl(active_layer)
if not ergebnis_auswahl.ok:
warning("Verfahrensgebiet erstellen", ergebnis_auswahl.meldung, duration=None)
return
# 4. Bestehenden Verfahrensgebiet-Layer prüfen
existing_layers = QgsProject.instance().mapLayersByName("Verfahrensgebiet")
if not existing_layers:
# Erstlauf: neuen Memory-Layer erzeugen
layer, erfolgreich, fehlgeschlagen = erstelle_verfahrensgebiet_aus_auswahl(
active_layer
)
if not layer or not layer.isValid():
error("Verfahrensgebiet erstellen", "Layer konnte nicht erstellt werden.")
return
QgsProject.instance().addMapLayer(layer)
apply_style(layer, "verfahrensgebiet.qml")
iface.mapCanvas().refresh()
self.verf_layer_combo.setLayer(layer)
self._on_verf_layer_changed(layer)
self.setze_haken(self.haken_verf, True)
QMessageBox.information(
self,
"Verfahrensgebiet erstellt",
f"Verfahrensgebiet aus Auswahl erstellt.\n"
f"Erfolgreich: {erfolgreich}\n"
f"Fehlgeschlagen: {fehlgeschlagen}",
)
return
# 5. Bestehender Layer: Nutzerentscheidung via Pruefmanager
entscheidung_ergebnis = self.pruefmanager.verarbeite(
pruef_ergebnis(
ok=False,
aktion="verfahrensgebiet_layer_existiert",
meldung=(
"Ein Verfahrensgebiet-Layer existiert bereits.\n"
"Was soll mit der Auswahl geschehen?"
),
kontext=existing_layers[0],
)
)
aktion = entscheidung_ergebnis.aktion
verf_layer = existing_layers[0]
if aktion == "verfahrensgebiet_abbrechen" or not entscheidung_ergebnis.ok:
return
elif aktion == "verfahrensgebiet_ergaenzen":
_, hinzugefuegt, bereits_vorhanden, fehlgeschlagen = ergaenze_verfahrensgebiet(
verf_layer, active_layer
)
apply_style(verf_layer, "verfahrensgebiet.qml")
iface.mapCanvas().refresh()
self.setze_haken(self.haken_verf, True)
QMessageBox.information(
self,
"Flurstücke ergänzt",
f"Hinzugefügt: {hinzugefuegt}\n"
f"Bereits vorhanden (übersprungen): {bereits_vorhanden}\n"
f"Fehlgeschlagen: {fehlgeschlagen}",
)
elif aktion == "verfahrensgebiet_entfernen":
_, entfernt, nicht_enthalten, fehlgeschlagen = entferne_aus_verfahrensgebiet(
verf_layer, active_layer
)
apply_style(verf_layer, "verfahrensgebiet.qml")
iface.mapCanvas().refresh()
QMessageBox.information(
self,
"Flurstücke entfernt",
f"Entfernt: {entfernt}\n"
f"Nicht im Verfahrensgebiet (übersprungen): {nicht_enthalten}\n"
f"Fehlgeschlagen: {fehlgeschlagen}",
)
elif aktion == "verfahrensgebiet_ersetzen":
for lyr in existing_layers:
QgsProject.instance().removeMapLayer(lyr.id())
layer, erfolgreich, fehlgeschlagen = ersetze_verfahrensgebiet(active_layer)
if not layer or not layer.isValid():
error("Verfahrensgebiet erstellen", "Layer konnte nicht erstellt werden.")
return
QgsProject.instance().addMapLayer(layer)
apply_style(layer, "verfahrensgebiet.qml")
iface.mapCanvas().refresh()
self.verf_layer_combo.setLayer(layer)
self._on_verf_layer_changed(layer)
self.setze_haken(self.haken_verf, True)
QMessageBox.information(
self,
"Verfahrensgebiet ersetzt",
f"Verfahrensgebiet neu erstellt.\n"
f"Erfolgreich: {erfolgreich}\n"
f"Fehlgeschlagen: {fehlgeschlagen}",
)
finally:
self._set_busy(self.btn_verf_aus_auswahl, False, "aus Auswahl")
def handle_verf_aus_liste(self) -> None:
"""Platzhalter – Verfahrensgebiet aus Liste erstellen (noch nicht implementiert)."""
show_info_dialog(
"Noch nicht implementiert",
"Die Funktion 'Aus Liste' steht in einer zukünftigen Version zur Verfügung.",
parent=self,
)