11 Commits
25.11.4 ... dev

58 changed files with 5824 additions and 174 deletions

12
.coveragerc Normal file
View File

@@ -0,0 +1,12 @@
[run]
source = modules
omit =
*/test/*
*/__init__.py
[report]
show_missing = True
skip_covered = False
[html]
directory = coverage_html

20
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to QGIS (Port 5678)",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "C:/Users/helbi/AppData/Roaming/QGIS/QGIS3/profiles/dev/python/plugins/sn_basis",
"remoteRoot": "C:/Users/helbi/AppData/Roaming/QGIS/QGIS3/profiles/dev/python/plugins/sn_basis"
}
]
}
]
}

35
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{
// OSGeo Python als Interpreter (QGIS 4.0, Qt6)
"python.defaultInterpreterPath": "D:/OSGeo/apps/Python312/python.exe",
// Pylance: zusätzliche Suchpfade
"python.analysis.extraPaths": [
"D:/OSGeo/apps/qgis/python",
"D:/OSGeo/apps/Python312/Lib/site-packages",
"C:/Users/helbi/AppData/Roaming/QGIS/QGIS3/profiles/dev/python/plugins",
"C:/Users/helbi/AppData/Roaming/QGIS/QGIS3/profiles/dev/python/plugins/sn_basis"
],
// Autocomplete ebenfalls erweitern
"python.autoComplete.extraPaths": [
"D:/OSGeo/apps/qgis/python",
"D:/OSGeo/apps/Python312/Lib/site-packages",
"C:/Users/helbi/AppData/Roaming/QGIS/QGIS3/profiles/dev/python/plugins",
"C:/Users/helbi/AppData/Roaming/QGIS/QGIS3/profiles/dev/python/plugins/sn_basis"
],
// Pylance-Modus
"python.analysis.typeCheckingMode": "basic",
"python.analysis.diagnosticMode": "workspace",
// Tests aktivieren
"python.testing.pytestEnabled": false,
"python.testing.unittestEnabled": true,
"python.testing.unittestArgs": [
"-v",
"-s",
"./test",
"-p",
"*test*.py"
]
}

View File

@@ -1,5 +1,3 @@
from .logic.variable_utils import get_variable
def classFactory(iface): def classFactory(iface):
from .main import BasisPlugin from .main import BasisPlugin
return BasisPlugin(iface) return BasisPlugin(iface)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 115 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 160 KiB

188
assets/Objektstruktur.txt Normal file
View File

@@ -0,0 +1,188 @@
# WrapperArchitektur Übersicht
Die WrapperArchitektur von sn_basis bildet das Fundament für eine robuste, testbare und zukunftssichere QGISPluginEntwicklung.
Sie kapselt sämtliche QGIS und QtAbhängigkeiten hinter klar definierten Schnittstellen und ermöglicht dadurch:
Mockfähige UnitTests ohne QGIS
PyQt5/6Kompatibilität ohne CodeÄnderungen
saubere Trennung von UI, Logik und Infrastruktur
stabile APIs, die unabhängig von QGISVersionen bleiben
klare Erweiterbarkeit für zukünftige Module und Plugins
Die WrapperSchicht ist das zentrale Bindeglied zwischen der PluginLogik und der QGIS/QtUmgebung.
## Ziele der WrapperArchitektur
🎯 1. Entkopplung von QGIS und Qt
Alle direkten Importe wie from qgis.core import ... oder from qgis.PyQt.QtWidgets import ... verschwinden aus der PluginLogik.
Stattdessen werden sie über WrapperModule abstrahiert.
🎯 2. Testbarkeit ohne QGIS
Im MockModus liefern die Wrapper:
DummyObjekte
simulierte Rückgabewerte
speicherbare Zustände (z.B. Variablen, Layer, Nachrichten)
Damit laufen Tests in jeder CIUmgebung.
🎯 3. Einheitliche API für alle Plugins
Plugins greifen nicht mehr direkt auf QGIS zu, sondern nutzen:
Code
sn_basis.functions.qgiscore_wrapper
sn_basis.functions.qgisui_wrapper
sn_basis.functions.qt_wrapper
sn_basis.functions.variable_wrapper
sn_basis.functions.message_wrapper
sn_basis.functions.dialog_wrapper
🎯 4. Zukunftssicherheit
Ändert sich die QGIS oder QtAPI, wird nur der Wrapper angepasst, nicht jedes Plugin.
## Architekturüberblick
Die WrapperSchicht besteht aus mehreren Modulen, die jeweils einen klar abgegrenzten Verantwortungsbereich haben.
### 1. qt_wrapper QtAbstraktion
Kapselt alle QtWidgets, Dialoge und Konstanten:
QWidget, QDialog, QMessageBox, QToolBar, QMenu, …
Layouts, Buttons, Labels, LineEdits
QtKonstanten wie YES, NO, DockAreas
MockModus:
Stellt DummyWidgets bereit, die keine UI öffnen.
### 2. qgiscore_wrapper QGISCoreAbstraktion
Abstraktion für:
QgsProject
LayerZugriff
ProjektMetadaten
Pfade, CRS, FeatureZugriff
MockModus:
Simuliert ein Projekt und LayerContainer.
### 3. qgisui_wrapper QGISUIAbstraktion
Kapselt UIbezogene QGISFunktionen:
Zugriff auf iface
DockManagement
Menü und ToolbarIntegration
HauptfensterZugriff
MockModus:
Stellt ein DummyInterface bereit.
### 4. variable_wrapper QGISVariablen
Abstraktion für:
Projektvariablen (projectScope)
globale Variablen (globalScope)
MockSpeicher für Tests
Vorteile:
keine QGISAbhängigkeit in der Logik
testbare Variablenverwaltung
einheitliches API
### 5. message_wrapper Meldungen & Logging
Einheitliche Schnittstelle für:
Fehlermeldungen
Warnungen
InfoMeldungen
Logging
MockModus:
Speichert Nachrichten statt sie an QGIS zu senden.
### 6. dialog_wrapper BenutzerDialoge
Abstraktion für:
Ja/NeinDialoge
spätere Erweiterungen (Eingabedialoge, Dateidialoge, etc.)
MockModus:
Gibt DefaultWerte zurück, öffnet keine UI.
### 7. DockManager & Navigation
Diese Module nutzen die WrapperSchicht, um:
DockWidgets sicher zu verwalten
Toolbars und Menüs zu erzeugen
Reloadsichere UIStrukturen aufzubauen
Sie sind keine Wrapper, sondern WrapperKonsumenten.
## Designprinzipien
🧱 1. Single Source of Truth
Jede QGIS oder QtFunktionalität wird nur an einer Stelle implementiert.
🔄 2. Austauschbarkeit
MockModus und Echtmodus sind vollständig austauschbar.
🧪 3. Testbarkeit
Jede Funktion kann ohne QGIS getestet werden.
🧼 4. Saubere Trennung
UI → qt_wrapper
QGISCore → qgiscore_wrapper
QGISUI → qgisui_wrapper
Logik → settings_logic, layer_logic, prüfmanager, …
🔌 5. Erweiterbarkeit
Neue Wrapper können jederzeit ergänzt werden, ohne bestehende Plugins zu brechen.
## Vorteile für Entwickler
Keine QGISAbhängigkeiten in der Logik
IDEfreundlich (Pylance, Autocomplete, Typing)
CIfähig (Tests ohne QGIS)
saubere Architektur
leichte Wartbarkeit
klare Dokumentation
## Fazit
Die WrapperArchitektur ist das Herzstück von sn_basis.
Sie ermöglicht eine moderne, modulare und testbare QGISPluginEntwicklung, die unabhängig von QGISVersionen, QtVersionen und Entwicklungsumgebungen funktioniert.
Sie bildet die Grundlage für:
stabile APIs
saubere UIAbstraktion
automatisierte Tests
nachhaltige Weiterentwicklung

144
assets/Pluginkonzept.md Normal file
View File

@@ -0,0 +1,144 @@
# WrapperArchitektur Übersicht
Die WrapperArchitektur von sn_basis bildet das Fundament für eine robuste, testbare und zukunftssichere QGISPluginEntwicklung.
Sie kapselt sämtliche QGIS und QtAbhängigkeiten hinter klar definierten Schnittstellen und ermöglicht dadurch:
- Mockfähige UnitTests ohne QGIS
- PyQt5/6Kompatibilität ohne CodeÄnderungen
- saubere Trennung von UI, Logik und Infrastruktur
- stabile APIs, die unabhängig von QGISVersionen bleiben
- klare Erweiterbarkeit für zukünftige Module und Plugins
Die WrapperSchicht ist das zentrale Bindeglied zwischen der PluginLogik und der QGIS/QtUmgebung.
## Ziele der WrapperArchitektur
1. Entkopplung von QGIS und Qt
Alle direkten Importe wie from qgis.core import ... oder from qgis.PyQt.QtWidgets import ... verschwinden aus der PluginLogik.
Stattdessen werden sie über WrapperModule abstrahiert.
2. Testbarkeit ohne QGIS
Im MockModus liefern die Wrapper:
- DummyObjekte
- simulierte Rückgabewerte
- speicherbare Zustände (z.B. Variablen, Layer, Nachrichten)
Damit laufen Tests in jeder CIUmgebung.
3. Einheitliche API für alle Plugins
Plugins greifen nicht mehr direkt auf QGIS zu, sondern nutzen:
- sn_basis.functions.qgiscore_wrapper
- sn_basis.functions.qgisui_wrapper
- sn_basis.functions.qt_wrapper
- sn_basis.functions.variable_wrapper
- sn_basis.functions.message_wrapper
- sn_basis.functions.dialog_wrapper
Aufgrund des Umfangs ist der Wrapper für die Layerbehandlung aufgeteilt:
- ly_existence_wrapper
- ly_geometry_wrapper
- ly_Metadata_wrapper
- ly_style_wrapper
- ly_visibility_wrapper
4. Zukunftssicherheit
Ändert sich die QGIS oder QtAPI, wird nur der Wrapper angepasst, nicht jedes Plugin.
## Architekturüberblick
Die WrapperSchicht besteht aus mehreren Modulen, die jeweils einen klar abgegrenzten Verantwortungsbereich haben.
### 1. qt_wrapper QtAbstraktion
Kapselt alle QtWidgets, Dialoge und Konstanten:
- QWidget, QDialog, QMessageBox, QToolBar, QMenu, …
- Layouts, Buttons, Labels, LineEdits
- QtKonstanten wie YES, NO, DockAreas
MockModus:
Stellt DummyWidgets bereit, die keine UI öffnen.
### 2. qgiscore_wrapper QGISCoreAbstraktion
Abstraktion für:
- QgsProject
- LayerZugriff
- ProjektMetadaten
- Pfade, CRS, FeatureZugriff
MockModus:
Simuliert ein Projekt und LayerContainer.
### 3. qgisui_wrapper QGISUIAbstraktion
Kapselt UIbezogene QGISFunktionen:
- Zugriff auf iface
- DockManagement
- Menü und ToolbarIntegration
- HauptfensterZugriff
MockModus:
Stellt ein DummyInterface bereit.
### 4. variable_wrapper QGISVariablen
Abstraktion für:
- Projektvariablen (projectScope)
- globale Variablen (globalScope)
- MockSpeicher für Tests
Vorteile:
- keine QGISAbhängigkeit in der Logik
- testbare Variablenverwaltung
- einheitliches API
### 5. message_wrapper Meldungen & Logging
Einheitliche Schnittstelle für:
- Fehlermeldungen
- Warnungen
- InfoMeldungen
- Logging
MockModus:
Speichert Nachrichten statt sie an QGIS zu senden.
### 6. dialog_wrapper BenutzerDialoge
Abstraktion für:
- Ja/NeinDialoge
- spätere Erweiterungen (Eingabedialoge, Dateidialoge, etc.)
MockModus:
Gibt DefaultWerte zurück, öffnet keine UI.
### 7. DockManager & Navigation
Diese Module nutzen die WrapperSchicht, um:
- DockWidgets sicher zu verwalten
- Toolbars und Menüs zu erzeugen
- Reloadsichere UIStrukturen aufzubauen
Sie sind keine Wrapper, sondern WrapperKonsumenten. Alle Fach-Plugins nutzen den Dockmanager des Basisplugins.
## Designprinzipien
1. Single Source of Truth
Jede QGIS oder QtFunktionalität wird nur an einer Stelle implementiert.
2. Austauschbarkeit
MockModus und Echtmodus sind vollständig austauschbar.
3. Testbarkeit
Jede Funktion kann ohne QGIS getestet werden.
4. Saubere Trennung
- UI → qt_wrapper
- QGISCore → qgiscore_wrapper
- QGISUI → qgisui_wrapper
- Logik → settings_logic, layer_logic, prüfmanager, …
5. Erweiterbarkeit
Neue Wrapper können jederzeit ergänzt werden, ohne bestehende Plugins zu brechen.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

BIN
assets/UML_Struktur.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View File

@@ -0,0 +1,9 @@
```mermaid
graph TD
M1["<div style='text-align:center'><b>sn_basis</b><br/><br/></b></div><div style='text-align:left'>➡ Initialisierung der GUI<br/> ➡ Exception Handling<br/> ➡ Bereitstellung der Stile"]
M2["<div style='text-align:center'><b>sn_verfahrensgebiet</b><br/><br/><div style='text-align:left'>➡ Abruf und Aufbereitung der Gebietsgrenze"<br/>➡ Erstellung neuer Gebietsgrenzen <br/>➡ Grenzpunktextraktion<br/>➡ Grenzpunktprüfung]
M3["<div style='text-align:center'><b>sn_Plan41</b><br/><br/><div style='text-align:left'>➡ Fachdatenabruf<br/>➡Versionierung der Fachdaten<br/>➡ Planung der TG-Maßnahmen <br/>➡Kartenerzeugung (NGG und P41)<br/>➡ Erzeugung der Begleitdokumente (Anlagenverzeichnis, MVZ, Maßnahmeblätter)"]
M1 --> M2
M1 --> M3
```

43
functions/__init__.py Normal file
View File

@@ -0,0 +1,43 @@
from .ly_existence_wrapper import layer_exists
from .ly_geometry_wrapper import (
get_layer_geometry_type,
get_layer_feature_count,
)
from .ly_visibility_wrapper import (
is_layer_visible,
set_layer_visible,
)
from .ly_metadata_wrapper import (
get_layer_type,
get_layer_crs,
get_layer_fields,
get_layer_source,
is_layer_editable,
)
from .ly_style_wrapper import apply_style
from .dialog_wrapper import ask_yes_no
from .message_wrapper import (
_get_message_bar,
push_message,
error,
warning,
info,
success,
)
from .os_wrapper import *
from .qgiscore_wrapper import *
from .qt_wrapper import *
from .settings_logic import *
from .sys_wrapper import *
from .variable_wrapper import *
from .qgisui_wrapper import (
get_main_window,
add_dock_widget,
remove_dock_widget,
find_dock_widgets,
add_menu,
remove_menu,
add_toolbar,
remove_toolbar)

View File

@@ -0,0 +1,62 @@
"""
sn_basis/functions/dialog_wrapper.py Benutzer-Dialoge
Dieser Wrapper kapselt alle Benutzer-Dialoge (z. B. Ja/Nein-Abfragen)
und sorgt dafür, dass sie sowohl in QGIS als auch im Mock-/Testmodus
einheitlich funktionieren.
"""
from typing import Any
# Import der abstrahierten Qt-Klassen aus dem qt_wrapper.
# QMessageBox, YES und NO sind bereits kompatibel zu Qt5/Qt6
# und im Mock-Modus durch Dummy-Objekte ersetzt.
from sn_basis.functions.qt_wrapper import (
QMessageBox,
YES,
NO,
)
# ---------------------------------------------------------
# Öffentliche API
# ---------------------------------------------------------
def ask_yes_no(
title: str,
message: str,
default: bool = False,
parent: Any = None,
) -> bool:
"""
Stellt dem Benutzer eine Ja/Nein-Frage.
- In einer echten QGIS-Umgebung wird ein QMessageBox-Dialog angezeigt.
- Im Mock-/Testmodus wird kein Dialog geöffnet, sondern der Default-Wert
zurückgegeben, damit Tests ohne UI laufen können.
:param title: Titel des Dialogs
:param message: Nachrichtentext
:param default: Rückgabewert im Fehler- oder Mock-Fall
:param parent: Optionales Parent-Widget
:return: True bei "Ja", False bei "Nein"
"""
try:
# Definiert die beiden Buttons, die angezeigt werden sollen.
buttons = QMessageBox.Yes | QMessageBox.No
# Öffnet den Dialog (oder im Mock-Modus: simuliert ihn).
result = QMessageBox.question(
parent,
title,
message,
buttons,
YES if default else NO, # Vorauswahl abhängig vom Default
)
# Gibt True zurück, wenn der Benutzer "Ja" gewählt hat.
return result == YES
except Exception:
# Falls Qt nicht verfügbar ist (Mock/CI), wird der Default-Wert genutzt.
return default

View File

@@ -0,0 +1,31 @@
# sn_basis/functions/ly_existence_wrapper.py
def layer_exists(layer) -> bool:
"""
Prüft, ob ein Layer-Objekt existiert (nicht None).
"""
return layer is not None
def layer_is_valid(layer) -> bool:
"""
Prüft, ob ein Layer gültig ist (QGIS-konform).
"""
if layer is None:
return False
is_valid = getattr(layer, "isValid", None)
if callable(is_valid):
try:
return bool(is_valid())
except Exception:
return False
return False
def layer_is_usable(layer) -> bool:
"""
Prüft, ob ein Layer existiert und gültig ist.
"""
return layer_exists(layer) and layer_is_valid(layer)

View File

@@ -0,0 +1,65 @@
# sn_basis/functions/ly_geometry_wrapper.py
from typing import Optional
GEOM_NONE = None
GEOM_POINT = "Point"
GEOM_LINE = "LineString"
GEOM_POLYGON = "Polygon"
def get_layer_geometry_type(layer) -> Optional[str]:
"""
Gibt den Geometrietyp eines Layers zurück.
Rückgabewerte:
- "Point"
- "LineString"
- "Polygon"
- None (nicht räumlich / ungültig / unbekannt)
"""
if layer is None:
return None
try:
is_spatial = getattr(layer, "isSpatial", None)
if callable(is_spatial) and not is_spatial():
return None
gtype = getattr(layer, "geometryType", None)
if callable(gtype):
value = gtype()
if value == 0:
return GEOM_POINT
if value == 1:
return GEOM_LINE
if value == 2:
return GEOM_POLYGON
except Exception:
pass
return None
def get_layer_feature_count(layer) -> int:
"""
Gibt die Anzahl der Features eines Layers zurück.
"""
if layer is None:
return 0
try:
is_spatial = getattr(layer, "isSpatial", None)
if callable(is_spatial) and not is_spatial():
return 0
fc = getattr(layer, "featureCount", None)
if callable(fc):
value = fc()
if isinstance(value, int):
return value
except Exception:
pass
return 0

View File

@@ -0,0 +1,97 @@
# sn_basis/functions/ly_metadata_wrapper.py
from typing import Optional, List
LAYER_TYPE_VECTOR = "vector"
LAYER_TYPE_TABLE = "table"
def get_layer_type(layer) -> Optional[str]:
"""
Gibt den Layer-Typ zurück.
Rückgabewerte:
- "vector"
- "table"
- None (unbekannt / nicht bestimmbar)
"""
if layer is None:
return None
try:
is_spatial = getattr(layer, "isSpatial", None)
if callable(is_spatial):
return LAYER_TYPE_VECTOR if is_spatial() else LAYER_TYPE_TABLE
except Exception:
pass
return None
def get_layer_crs(layer) -> Optional[str]:
"""
Gibt das CRS als AuthID zurück (z.B. 'EPSG:25833').
"""
if layer is None:
return None
try:
crs = layer.crs()
authid = getattr(crs, "authid", None)
if callable(authid):
value = authid()
if isinstance(value, str):
return value
except Exception:
pass
return None
def get_layer_fields(layer) -> List[str]:
"""
Gibt die Feldnamen eines Layers zurück.
"""
if layer is None:
return []
try:
return list(layer.fields().names())
except Exception:
return []
def get_layer_source(layer) -> Optional[str]:
"""
Gibt die Datenquelle eines Layers zurück.
"""
if layer is None:
return None
try:
value = layer.source()
if isinstance(value, str) and value:
return value
except Exception:
pass
return None
def is_layer_editable(layer) -> bool:
"""
Prüft, ob ein Layer editierbar ist.
"""
if layer is None:
return False
try:
is_editable = getattr(layer, "isEditable", None)
if callable(is_editable):
return bool(is_editable())
except Exception:
pass
return False

View File

@@ -0,0 +1,27 @@
# sn_basis/functions/ly_style_wrapper.py
from sn_basis.functions.ly_existence_wrapper import layer_exists
from sn_basis.functions.sys_wrapper import (
get_plugin_root,
join_path,
file_exists,
)
def apply_style(layer, style_name: str) -> bool:
if not layer_exists(layer):
return False
style_path = join_path(get_plugin_root(), "styles", style_name)
if not file_exists(style_path):
return False
try:
ok, _ = layer.loadNamedStyle(style_path)
if ok:
getattr(layer, "triggerRepaint", lambda: None)()
return True
except Exception:
pass
return False

View File

@@ -0,0 +1,41 @@
# sn_basis/functions/ly_visibility_wrapper.py
def is_layer_visible(layer) -> bool:
"""
Prüft, ob ein Layer im Layer-Tree sichtbar ist.
"""
if layer is None:
return False
try:
node = getattr(layer, "treeLayer", None)
if callable(node):
tree_node = node()
is_visible = getattr(tree_node, "isVisible", None)
if callable(is_visible):
return bool(is_visible())
except Exception:
pass
return False
def set_layer_visible(layer, visible: bool) -> bool:
"""
Setzt die Sichtbarkeit eines Layers im Layer-Tree.
"""
if layer is None:
return False
try:
node = getattr(layer, "treeLayer", None)
if callable(node):
tree_node = node()
setter = getattr(tree_node, "setItemVisibilityChecked", None)
if callable(setter):
setter(bool(visible))
return True
except Exception:
pass
return False

View File

@@ -0,0 +1,84 @@
"""
sn_basis/functions/message_wrapper.py zentrale MessageBar-Abstraktion
"""
from typing import Any
from sn_basis.functions.qgisui_wrapper import iface
from sn_basis.functions.qgiscore_wrapper import Qgis
# ---------------------------------------------------------
# Interne Hilfsfunktion
# ---------------------------------------------------------
def _get_message_bar():
"""
Liefert eine MessageBar-Instanz (QGIS oder Mock).
"""
try:
bar = iface.messageBar()
if bar is not None:
return bar
except Exception:
pass
class _MockMessageBar:
def pushMessage(self, title, text, level=0, duration=5):
return {
"title": title,
"text": text,
"level": level,
"duration": duration,
}
return _MockMessageBar()
# ---------------------------------------------------------
# Öffentliche API
# ---------------------------------------------------------
def push_message(
level: int,
title: str,
text: str,
duration: int = 5,
parent: Any = None,
):
"""
Zeigt eine Message in der QGIS-MessageBar an.
Im Mock-Modus wird ein strukturierter Dict zurückgegeben.
"""
bar = _get_message_bar()
try:
return bar.pushMessage(
title,
text,
level=level,
duration=duration,
)
except Exception:
return None
def info(title: str, text: str, duration: int = 5):
level = Qgis.MessageLevel.Info
return push_message(level, title, text, duration)
def warning(title: str, text: str, duration: int = 5):
level = Qgis.MessageLevel.Warning
return push_message(level, title, text, duration)
def error(title: str, text: str, duration: int = 5):
level = Qgis.MessageLevel.Critical
return push_message(level, title, text, duration)
def success(title: str, text: str, duration: int = 5):
level = Qgis.MessageLevel.Success
return push_message(level, title, text, duration)

77
functions/os_wrapper.py Normal file
View File

@@ -0,0 +1,77 @@
"""
sn_basis/functions/os_wrapper.py Betriebssystem-Abstraktion
"""
from pathlib import Path
import platform
from typing import Union
# ---------------------------------------------------------
# OS-Erkennung
# ---------------------------------------------------------
_SYSTEM = platform.system().lower()
if _SYSTEM.startswith("win"):
OS_NAME = "windows"
elif _SYSTEM.startswith("darwin"):
OS_NAME = "macos"
else:
OS_NAME = "linux"
IS_WINDOWS = OS_NAME == "windows"
IS_LINUX = OS_NAME == "linux"
IS_MACOS = OS_NAME == "macos"
# ---------------------------------------------------------
# OS-Eigenschaften
# ---------------------------------------------------------
PATH_SEPARATOR = "\\" if IS_WINDOWS else "/"
LINE_SEPARATOR = "\r\n" if IS_WINDOWS else "\n"
# ---------------------------------------------------------
# Pfad-Utilities
# ---------------------------------------------------------
_PathLike = Union[str, Path]
def normalize_path(path: _PathLike) -> Path:
"""
Normalisiert einen Pfad OS-unabhängig.
"""
try:
return Path(path).expanduser().resolve()
except Exception:
return Path(path)
def get_home_dir() -> Path:
"""
Liefert das Home-Verzeichnis des aktuellen Users.
"""
return Path.home()
# ---------------------------------------------------------
# Dateisystem-Eigenschaften
# ---------------------------------------------------------
def is_case_sensitive_fs() -> bool:
"""
Gibt zurück, ob das Dateisystem case-sensitiv ist.
"""
# Windows ist immer case-insensitive
if IS_WINDOWS:
return False
# macOS meist case-insensitive, aber nicht garantiert
if IS_MACOS:
return False
# Linux praktisch immer case-sensitiv
return True

View File

@@ -0,0 +1,156 @@
"""
sn_basis/functions/qgiscore_wrapper.py zentrale QGIS-Core-Abstraktion
"""
from typing import Type, Any
from sn_basis.functions.qt_wrapper import (
QUrl,
QEventLoop,
QNetworkRequest,
)
# ---------------------------------------------------------
# QGIS-Symbole (werden dynamisch gesetzt)
# ---------------------------------------------------------
QgsProject: Type[Any]
QgsVectorLayer: Type[Any]
QgsNetworkAccessManager: Type[Any]
Qgis: Type[Any]
QgsMapLayerProxyModel: Type[Any]
QGIS_AVAILABLE = False
# ---------------------------------------------------------
# Versuch: QGIS-Core importieren
# ---------------------------------------------------------
try:
from qgis.core import (
QgsProject as _QgsProject,
QgsVectorLayer as _QgsVectorLayer,
QgsNetworkAccessManager as _QgsNetworkAccessManager,
Qgis as _Qgis,
QgsMapLayerProxyModel as _QgsMaplLayerProxyModel
)
QgsProject = _QgsProject
QgsVectorLayer = _QgsVectorLayer
QgsNetworkAccessManager = _QgsNetworkAccessManager
Qgis = _Qgis
QgsMapLayerProxyModel=_QgsMaplLayerProxyModel
QGIS_AVAILABLE = True
# ---------------------------------------------------------
# Mock-Modus
# ---------------------------------------------------------
except Exception:
QGIS_AVAILABLE = False
class _MockQgsProject:
def __init__(self):
self._variables = {}
@staticmethod
def instance() -> "_MockQgsProject":
return _MockQgsProject()
def read(self) -> bool:
return True
QgsProject = _MockQgsProject
class _MockQgsVectorLayer:
def __init__(self, *args, **kwargs):
self._valid = True
def isValid(self) -> bool:
return self._valid
def loadNamedStyle(self, path: str):
return True, ""
def triggerRepaint(self) -> None:
pass
QgsVectorLayer = _MockQgsVectorLayer
class _MockQgsNetworkAccessManager:
@staticmethod
def instance():
return _MockQgsNetworkAccessManager()
def head(self, request: Any):
return None
QgsNetworkAccessManager = _MockQgsNetworkAccessManager
class _MockQgis:
class MessageLevel:
Success = 0
Info = 1
Warning = 2
Critical = 3
Qgis = _MockQgis
class _MockQgsMapLayerProxyModel:
# Layer-Typen (entsprechen QGIS-Konstanten)
NoLayer = 0
VectorLayer = 1
RasterLayer = 2
PluginLayer = 3
MeshLayer = 4
VectorTileLayer = 5
PointCloudLayer = 6
def __init__(self, *args, **kwargs):
pass
QgsMapLayerProxyModel = _MockQgsMapLayerProxyModel
# ---------------------------------------------------------
# Netzwerk
# ---------------------------------------------------------
class NetworkReply:
"""
Minimaler Wrapper für Netzwerkantworten.
"""
def __init__(self, error: int):
self.error = error
def network_head(url: str) -> NetworkReply | None:
"""
Führt einen HTTP-HEAD-Request aus.
Rückgabe:
- NetworkReply(error=0) → erreichbar
- NetworkReply(error!=0) → nicht erreichbar
- None → Netzwerk nicht verfügbar / Fehler beim Request
"""
if not QGIS_AVAILABLE:
return None
if QUrl is None or QNetworkRequest is None:
return None
try:
manager = QgsNetworkAccessManager.instance()
request = QNetworkRequest(QUrl(url))
reply = manager.head(request)
# synchron warten (kurz)
if QEventLoop is not None:
loop = QEventLoop()
reply.finished.connect(loop.quit)
loop.exec()
return NetworkReply(error=reply.error())
except Exception:
return None

201
functions/qgisui_wrapper.py Normal file
View File

@@ -0,0 +1,201 @@
"""
sn_basis/functions/qgisui_wrapper.py zentrale QGIS-UI-Abstraktion
"""
from __future__ import annotations
from typing import Any, List, Type
from sn_basis.functions.qt_wrapper import QDockWidget
iface: Any
QGIS_UI_AVAILABLE = False
QgsFileWidget: Type[Any]
QgsMapLayerComboBox: Type[Any]
# ---------------------------------------------------------
# iface + QGIS-Widgets initialisieren (QGIS oder Mock)
# ---------------------------------------------------------
try:
from qgis.utils import iface as _iface
from qgis.gui import (
QgsFileWidget as _QgsFileWidget,
QgsMapLayerComboBox as _QgsMapLayerComboBox,
)
iface = _iface
QgsFileWidget = _QgsFileWidget
QgsMapLayerComboBox = _QgsMapLayerComboBox
QGIS_UI_AVAILABLE = True
except Exception:
QGIS_UI_AVAILABLE = False
class _MockSignal:
def __init__(self):
self._callbacks: list[Any] = []
def connect(self, callback):
self._callbacks.append(callback)
def emit(self, *args, **kwargs):
for cb in list(self._callbacks):
cb(*args, **kwargs)
class _MockMessageBar:
def pushMessage(self, title, text, level=0, duration=5):
return {
"title": title,
"text": text,
"level": level,
"duration": duration,
}
class _MockIface:
def messageBar(self):
return _MockMessageBar()
def mainWindow(self):
return None
def addDockWidget(self, *args, **kwargs):
pass
def removeDockWidget(self, *args, **kwargs):
pass
def addToolBar(self, *args, **kwargs):
pass
def removeToolBar(self, *args, **kwargs):
pass
iface = _MockIface()
class _MockQgsFileWidget:
GetFile = 0
def __init__(self, *args, **kwargs):
self._path = ""
self.fileChanged = _MockSignal()
def setStorageMode(self, *args, **kwargs):
pass
def setFilter(self, *args, **kwargs):
pass
def setFilePath(self, path: str):
self._path = path
self.fileChanged.emit(path)
def filePath(self) -> str:
return self._path
class _MockQgsMapLayerComboBox:
def __init__(self, *args, **kwargs):
self.layerChanged = _MockSignal()
self._layer = None
self._count = 0
def setFilters(self, *args, **kwargs):
pass
def setLayer(self, layer):
self._layer = layer
self.layerChanged.emit(layer)
def count(self) -> int:
return self._count
def setCurrentIndex(self, idx: int):
pass
QgsFileWidget = _MockQgsFileWidget
QgsMapLayerComboBox = _MockQgsMapLayerComboBox
# ---------------------------------------------------------
# Main Window
# ---------------------------------------------------------
def get_main_window():
try:
return iface.mainWindow()
except Exception:
return None
# ---------------------------------------------------------
# Dock-Handling
# ---------------------------------------------------------
def add_dock_widget(area, dock: Any) -> None:
try:
iface.addDockWidget(area, dock)
except Exception:
pass
def remove_dock_widget(dock: Any) -> None:
try:
iface.removeDockWidget(dock)
except Exception:
pass
def find_dock_widgets() -> List[Any]:
main_window = get_main_window()
if not main_window:
return []
try:
return main_window.findChildren(QDockWidget)
except Exception:
return []
# ---------------------------------------------------------
# Menü-Handling
# ---------------------------------------------------------
def add_menu(menu):
main_window = iface.mainWindow()
if not main_window:
return
# Nur echte Qt-Menüs an Qt übergeben
if hasattr(menu, "menuAction"):
main_window.menuBar().addMenu(menu)
def remove_menu(menu):
main_window = iface.mainWindow()
if not main_window:
return
if hasattr(menu, "menuAction"):
main_window.menuBar().removeAction(menu.menuAction())
# ---------------------------------------------------------
# Toolbar-Handling
# ---------------------------------------------------------
def add_toolbar(toolbar: Any) -> None:
try:
iface.addToolBar(toolbar)
except Exception:
pass
def remove_toolbar(toolbar: Any) -> None:
try:
iface.removeToolBar(toolbar)
except Exception:
pass

520
functions/qt_wrapper.py Normal file
View File

@@ -0,0 +1,520 @@
"""
sn_basis/functions/qt_wrapper.py zentrale Qt-Abstraktion (PyQt5 / PyQt6 / Mock)
"""
from typing import Optional, Type, Any
# ---------------------------------------------------------
# Qt-Symbole (werden dynamisch gesetzt)
# ---------------------------------------------------------
QDockWidget: Type[Any]
QMessageBox: Type[Any]
QFileDialog: Type[Any]
QEventLoop: Type[Any]
QUrl: Type[Any]
QNetworkRequest: Type[Any]
QNetworkReply: Type[Any]
QCoreApplication: Type[Any]
QWidget: Type[Any]
QGridLayout: Type[Any]
QLabel: Type[Any]
QLineEdit: Type[Any]
QGroupBox: Type[Any]
QVBoxLayout: Type[Any]
QPushButton: Type[Any]
QAction: Type[Any]
QMenu: Type[Any]
QToolBar: Type[Any]
QActionGroup: Type[Any]
QTabWidget: type
QToolButton: Type[Any]
QSizePolicy: Type[Any]
Qt: Type[Any]
YES: Optional[Any] = None
NO: Optional[Any] = None
CANCEL: Optional[Any] = None
ICON_QUESTION: Optional[Any] = None
QT_VERSION = 0 # 0 = Mock, 5 = PyQt5, 6 = PyQt6
def exec_dialog(dialog: Any) -> Any:
raise NotImplementedError
# ---------------------------------------------------------
# Versuch: PyQt6
# ---------------------------------------------------------
try:
from qgis.PyQt.QtWidgets import ( # type: ignore
QMessageBox as _QMessageBox,# type: ignore
QFileDialog as _QFileDialog,# type: ignore
QWidget as _QWidget,# type: ignore
QGridLayout as _QGridLayout,# type: ignore
QLabel as _QLabel,# type: ignore
QLineEdit as _QLineEdit,# type: ignore
QGroupBox as _QGroupBox,# type: ignore
QVBoxLayout as _QVBoxLayout,# type: ignore
QPushButton as _QPushButton,# type: ignore
QAction as _QAction,
QMenu as _QMenu,# type: ignore
QToolBar as _QToolBar,# type: ignore
QActionGroup as _QActionGroup,# type: ignore
QDockWidget as _QDockWidget,# type: ignore
QTabWidget as _QTabWidget,# type: ignore
QToolButton as _QToolButton,#type:ignore
QSizePolicy as _QSizePolicy,#type:ignore
)
from qgis.PyQt.QtCore import ( # type: ignore
QEventLoop as _QEventLoop,# type: ignore
QUrl as _QUrl,# type: ignore
QCoreApplication as _QCoreApplication,# type: ignore
Qt as _Qt#type:ignore
)
from qgis.PyQt.QtNetwork import ( # type: ignore
QNetworkRequest as _QNetworkRequest,# type: ignore
QNetworkReply as _QNetworkReply,# type: ignore
)
QT_VERSION = 6
QMessageBox = _QMessageBox
QFileDialog = _QFileDialog
QEventLoop = _QEventLoop
QUrl = _QUrl
QNetworkRequest = _QNetworkRequest
QNetworkReply = _QNetworkReply
QCoreApplication = _QCoreApplication
Qt=_Qt
QDockWidget = _QDockWidget
QWidget = _QWidget
QGridLayout = _QGridLayout
QLabel = _QLabel
QLineEdit = _QLineEdit
QGroupBox = _QGroupBox
QVBoxLayout = _QVBoxLayout
QPushButton = _QPushButton
QAction = _QAction
QMenu = _QMenu
QToolBar = _QToolBar
QActionGroup = _QActionGroup
QTabWidget = _QTabWidget
QToolButton=_QToolButton
QSizePolicy=_QSizePolicy
YES = QMessageBox.StandardButton.Yes
NO = QMessageBox.StandardButton.No
CANCEL = QMessageBox.StandardButton.Cancel
ICON_QUESTION = QMessageBox.Icon.Question
# ---------------------------------------------------------
# Qt6 Enum-Aliase (vereinheitlicht)
# ---------------------------------------------------------
ToolButtonTextBesideIcon = Qt.ToolButtonStyle.ToolButtonTextBesideIcon
ArrowDown = Qt.ArrowType.DownArrow
ArrowRight = Qt.ArrowType.RightArrow
# QSizePolicy Enum-Aliase (Qt6)
SizePolicyPreferred = QSizePolicy.Policy.Preferred
SizePolicyMaximum = QSizePolicy.Policy.Maximum
# ---------------------------------------------------------
# QDockWidget Feature-Aliase (Qt6)
# ---------------------------------------------------------
DockWidgetMovable = QDockWidget.DockWidgetFeature.DockWidgetMovable
DockWidgetFloatable = QDockWidget.DockWidgetFeature.DockWidgetFloatable
DockWidgetClosable = QDockWidget.DockWidgetFeature.DockWidgetClosable
# ---------------------------------------------------------
# Dock-Area-Aliase (Qt6)
# ---------------------------------------------------------
DockAreaLeft = Qt.DockWidgetArea.LeftDockWidgetArea
DockAreaRight = Qt.DockWidgetArea.RightDockWidgetArea
def exec_dialog(dialog: Any) -> Any:
return dialog.exec()
# ---------------------------------------------------------
# Versuch: PyQt5
# ---------------------------------------------------------
except Exception:
try:
from PyQt5.QtWidgets import (
QMessageBox as _QMessageBox,
QFileDialog as _QFileDialog,
QWidget as _QWidget,
QGridLayout as _QGridLayout,
QLabel as _QLabel,
QLineEdit as _QLineEdit,
QGroupBox as _QGroupBox,
QVBoxLayout as _QVBoxLayout,
QPushButton as _QPushButton,
QAction as _QAction,
QMenu as _QMenu,
QToolBar as _QToolBar,
QActionGroup as _QActionGroup,
QDockWidget as _QDockWidget,
QTabWidget as _QTabWidget,
QToolButton as _QToolButton,
QSizePolicy as _QSizePolicy,
)
from PyQt5.QtCore import (
QEventLoop as _QEventLoop,
QUrl as _QUrl,
QCoreApplication as _QCoreApplication,
Qt as _Qt,
)
from PyQt5.QtNetwork import (
QNetworkRequest as _QNetworkRequest,
QNetworkReply as _QNetworkReply,
)
QMessageBox = _QMessageBox
QFileDialog = _QFileDialog
QEventLoop = _QEventLoop
QUrl = _QUrl
QNetworkRequest = _QNetworkRequest
QNetworkReply = _QNetworkReply
QCoreApplication = _QCoreApplication
Qt=_Qt
QDockWidget = _QDockWidget
QWidget = _QWidget
QGridLayout = _QGridLayout
QLabel = _QLabel
QLineEdit = _QLineEdit
QGroupBox = _QGroupBox
QVBoxLayout = _QVBoxLayout
QPushButton = _QPushButton
QAction = _QAction
QMenu = _QMenu
QToolBar = _QToolBar
QActionGroup = _QActionGroup
QTabWidget = _QTabWidget
QToolButton=_QToolButton
QSizePolicy=_QSizePolicy
YES = QMessageBox.Yes
NO = QMessageBox.No
CANCEL = QMessageBox.Cancel
ICON_QUESTION = QMessageBox.Question
QT_VERSION = 5
# ---------------------------------------------------------
# Qt5 Enum-Aliase (vereinheitlicht)
# ---------------------------------------------------------
ToolButtonTextBesideIcon = Qt.ToolButtonTextBesideIcon
ArrowDown = Qt.DownArrow
ArrowRight = Qt.RightArrow
# QSizePolicy Enum-Aliase (Qt5)
SizePolicyPreferred = QSizePolicy.Preferred
SizePolicyMaximum = QSizePolicy.Maximum
# ---------------------------------------------------------
# QDockWidget Feature-Aliase (Qt5)
# ---------------------------------------------------------
DockWidgetMovable = QDockWidget.DockWidgetMovable
DockWidgetFloatable = QDockWidget.DockWidgetFloatable
DockWidgetClosable = QDockWidget.DockWidgetClosable
# ---------------------------------------------------------
# Dock-Area-Aliase (Qt5)
# ---------------------------------------------------------
DockAreaLeft = Qt.LeftDockWidgetArea
DockAreaRight = Qt.RightDockWidgetArea
def exec_dialog(dialog: Any) -> Any:
return dialog.exec_()
# ---------------------------------------------------------
# Mock-Modus
# ---------------------------------------------------------
except Exception:
QT_VERSION = 0
class FakeEnum(int):
def __or__(self, other: "FakeEnum") -> "FakeEnum":
return FakeEnum(int(self) | int(other))
YES = FakeEnum(1)
NO = FakeEnum(2)
CANCEL = FakeEnum(4)
ICON_QUESTION = FakeEnum(8)
class _MockQMessageBox:
Yes = YES
No = NO
Cancel = CANCEL
Question = ICON_QUESTION
QMessageBox = _MockQMessageBox
class _MockQFileDialog:
@staticmethod
def getOpenFileName(*args, **kwargs):
return ("", "")
@staticmethod
def getSaveFileName(*args, **kwargs):
return ("", "")
QFileDialog = _MockQFileDialog
class _MockQEventLoop:
def exec(self) -> int:
return 0
def quit(self) -> None:
pass
QEventLoop = _MockQEventLoop
class _MockQUrl(str):
def isValid(self) -> bool:
return True
QUrl = _MockQUrl
class _MockQNetworkRequest:
def __init__(self, url: Any):
self.url = url
QNetworkRequest = _MockQNetworkRequest
class _MockQNetworkReply:
def error(self) -> int:
return 0
def errorString(self) -> str:
return ""
def readAll(self) -> bytes:
return b""
def deleteLater(self) -> None:
pass
QNetworkReply = _MockQNetworkReply
class _MockWidget:
def __init__(self, *args, **kwargs):
pass
class _MockLayout:
def __init__(self, *args, **kwargs):
self._widgets = []
def addWidget(self, widget):
self._widgets.append(widget)
def addLayout(self, layout):
pass
def addStretch(self, *args, **kwargs):
pass
def setSpacing(self, *args, **kwargs):
pass
def setContentsMargins(self, *args, **kwargs):
pass
class _MockLabel:
def __init__(self, text: str = ""):
self._text = text
class _MockLineEdit:
def __init__(self, *args, **kwargs):
self._text = ""
def text(self) -> str:
return self._text
def setText(self, value: str) -> None:
self._text = value
class _MockButton:
def __init__(self, *args, **kwargs):
self.clicked = lambda *a, **k: None
QWidget = _MockWidget
QGridLayout = _MockLayout
QLabel = _MockLabel
QLineEdit = _MockLineEdit
QGroupBox = _MockWidget
QVBoxLayout = _MockLayout
QPushButton = _MockButton
class _MockQCoreApplication:
pass
QCoreApplication = _MockQCoreApplication
class _MockQt:
# ToolButtonStyle
ToolButtonTextBesideIcon = 0
# ArrowType
ArrowDown = 1
ArrowRight = 2
Qt=_MockQt
ToolButtonTextBesideIcon = Qt.ToolButtonTextBesideIcon
ArrowDown = Qt.ArrowDown
ArrowRight = Qt.ArrowRight
class _MockQDockWidget(_MockWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._object_name = ""
def setObjectName(self, name: str) -> None:
self._object_name = name
def objectName(self) -> str:
return self._object_name
def show(self) -> None:
pass
def deleteLater(self) -> None:
pass
QDockWidget = _MockQDockWidget
class _MockAction:
def __init__(self, *args, **kwargs):
self._checked = False
self.triggered = lambda *a, **k: None
def setToolTip(self, text: str) -> None:
pass
def setCheckable(self, value: bool) -> None:
pass
def setChecked(self, value: bool) -> None:
self._checked = value
class _MockMenu:
def __init__(self, *args, **kwargs):
self._actions = []
def addAction(self, action):
self._actions.append(action)
def removeAction(self, action):
if action in self._actions:
self._actions.remove(action)
def clear(self):
self._actions.clear()
def menuAction(self):
return self
class _MockToolBar:
def __init__(self, *args, **kwargs):
self._actions = []
def setObjectName(self, name: str) -> None:
pass
def addAction(self, action):
self._actions.append(action)
def removeAction(self, action):
if action in self._actions:
self._actions.remove(action)
def clear(self):
self._actions.clear()
class _MockActionGroup:
def __init__(self, *args, **kwargs):
self._actions = []
def setExclusive(self, value: bool) -> None:
pass
def addAction(self, action):
self._actions.append(action)
QAction = _MockAction
QMenu = _MockMenu
QToolBar = _MockToolBar
QActionGroup = _MockActionGroup
class _MockToolButton(_MockWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._checked = False
self.toggled = lambda *a, **k: None
def setText(self, text: str) -> None:
pass
def setCheckable(self, value: bool) -> None:
pass
def setChecked(self, value: bool) -> None:
self._checked = value
def setToolButtonStyle(self, *args, **kwargs):
pass
def setArrowType(self, *args, **kwargs):
pass
def setStyleSheet(self, *args, **kwargs):
pass
QToolButton=_MockToolButton
class _MockQSizePolicy:
# horizontale Policies
Fixed = 0
Minimum = 1
Maximum = 2
Preferred = 3
Expanding = 4
MinimumExpanding = 5
Ignored = 6
# vertikale Policies (Qt nutzt dieselben Werte)
def __init__(self, horizontal=None, vertical=None):
self.horizontal = horizontal
self.vertical = vertical
QSizePolicy=_MockQSizePolicy
SizePolicyPreferred = QSizePolicy.Preferred
SizePolicyMaximum = QSizePolicy.Maximum
DockWidgetMovable = 1
DockWidgetFloatable = 2
DockWidgetClosable = 4
DockAreaLeft = 1
DockAreaRight = 2
def exec_dialog(dialog: Any) -> Any:
return YES
class _MockTabWidget:
def __init__(self, *args, **kwargs):
self._tabs = []
def addTab(self, widget, title: str):
self._tabs.append((widget, title))
QTabWidget = _MockTabWidget

View File

@@ -0,0 +1,47 @@
"""
sn_basis/functions/settings_logic.py Logik zum Lesen und Schreiben der Plugin-Einstellungen
über den zentralen variable_wrapper.
"""
from sn_basis.functions.variable_wrapper import (
get_variable,
set_variable,
)
class SettingsLogic:
"""
Verwaltet das Laden und Speichern der Plugin-Einstellungen.
Alle Variablen werden als sn_* Projektvariablen gespeichert.
"""
# Alle Variablen, die gespeichert werden sollen
VARIABLEN = [
"amt",
"behoerde",
"landkreis_user",
"sachgebiet",
"bezeichnung",
"verfahrensnummer",
"gemeinden",
"landkreise_proj",
]
def load(self) -> dict[str, str]:
"""
Lädt alle Variablen aus dem Projekt.
Rückgabe: dict mit allen Werten (leere Strings, wenn nicht gesetzt).
"""
daten: dict[str, str] = {}
for key in self.VARIABLEN:
daten[key] = get_variable(key, scope="project")
return daten
def save(self, daten: dict[str, str]) -> None:
"""
Speichert alle übergebenen Variablen im Projekt.
daten: dict mit key → value
"""
for key, value in daten.items():
if key in self.VARIABLEN:
set_variable(key, value, scope="project")

104
functions/sys_wrapper.py Normal file
View File

@@ -0,0 +1,104 @@
"""
sn_basis/functions/sys_wrapper.py System- und Pfad-Abstraktion
"""
from pathlib import Path
from typing import Union
import sys
_PathLike = Union[str, Path]
# ---------------------------------------------------------
# Plugin Root
# ---------------------------------------------------------
def get_plugin_root() -> Path:
"""
Liefert das Basisverzeichnis des Plugins.
"""
return Path(__file__).resolve().parents[2]
# ---------------------------------------------------------
# Pfad-Utilities
# ---------------------------------------------------------
def join_path(*parts: _PathLike) -> Path:
"""
Verbindet Pfadbestandteile OS-sicher.
"""
path = Path(parts[0])
for part in parts[1:]:
path /= part
return path
def file_exists(path: _PathLike) -> bool:
"""
Prüft, ob eine Datei existiert.
"""
try:
return Path(path).exists()
except Exception:
return False
def ensure_dir(path: _PathLike) -> Path:
"""
Stellt sicher, dass ein Verzeichnis existiert.
"""
p = Path(path)
p.mkdir(parents=True, exist_ok=True)
return p
# ---------------------------------------------------------
# Datei-IO
# ---------------------------------------------------------
def read_text(path: _PathLike, encoding: str = "utf-8") -> str:
"""
Liest eine Textdatei.
"""
try:
return Path(path).read_text(encoding=encoding)
except Exception:
return ""
def write_text(
path: _PathLike,
content: str,
encoding: str = "utf-8",
) -> bool:
"""
Schreibt eine Textdatei.
"""
try:
Path(path).write_text(content, encoding=encoding)
return True
except Exception:
return False
def add_to_sys_path(path: Union[str, Path]) -> None:
"""
Fügt einen Pfad zu sys.path hinzu, falls er noch nicht enthalten ist.
"""
p = str(path)
if p not in sys.path:
sys.path.insert(0, p)
def getattr_safe(obj, attr, default=None):
"""
Sicherer Zugriff auf ein Attribut.
Gibt das Attribut zurück, wenn es existiert,
ansonsten den Default-Wert (None, wenn nicht angegeben).
"""
try:
return getattr(obj, attr)
except Exception:
return default

View File

@@ -0,0 +1,115 @@
"""
sn_basis/functions/variable_wrapper.py QGIS-Variablen-Abstraktion
"""
from typing import Any
from sn_basis.functions.qgiscore_wrapper import QgsProject
# ---------------------------------------------------------
# Versuch: QgsExpressionContextUtils importieren
# ---------------------------------------------------------
try:
from qgis.core import QgsExpressionContextUtils
_HAS_QGIS_VARIABLES = True
# ---------------------------------------------------------
# Mock-Modus
# ---------------------------------------------------------
except Exception:
_HAS_QGIS_VARIABLES = False
class _MockVariableStore:
global_vars: dict[str, str] = {}
project_vars: dict[str, str] = {}
class QgsExpressionContextUtils:
@staticmethod
def setGlobalVariable(name: str, value: str) -> None:
_MockVariableStore.global_vars[name] = value
@staticmethod
def globalScope():
class _Scope:
def variable(self, name: str) -> str:
return _MockVariableStore.global_vars.get(name, "")
return _Scope()
@staticmethod
def setProjectVariable(project: Any, name: str, value: str) -> None:
_MockVariableStore.project_vars[name] = value
@staticmethod
def projectScope(project: Any):
class _Scope:
def variable(self, name: str) -> str:
return _MockVariableStore.project_vars.get(name, "")
return _Scope()
# ---------------------------------------------------------
# Öffentliche API
# ---------------------------------------------------------
def get_variable(key: str, scope: str = "project") -> str:
"""
Liest eine QGIS-Variable.
:param key: Variablenname ohne Prefix
:param scope: 'project' oder 'global'
"""
var_name = f"sn_{key}"
if scope == "project":
project = QgsProject.instance()
return (
QgsExpressionContextUtils
.projectScope(project)
.variable(var_name)
or ""
)
if scope == "global":
return (
QgsExpressionContextUtils
.globalScope()
.variable(var_name)
or ""
)
raise ValueError("Scope muss 'project' oder 'global' sein.")
def set_variable(key: str, value: str, scope: str = "project") -> None:
"""
Setzt eine QGIS-Variable.
:param key: Variablenname ohne Prefix
:param value: Wert
:param scope: 'project' oder 'global'
"""
var_name = f"sn_{key}"
if scope == "project":
project = QgsProject.instance()
QgsExpressionContextUtils.setProjectVariable(
project,
var_name,
value,
)
return
if scope == "global":
QgsExpressionContextUtils.setGlobalVariable(
var_name,
value,
)
return
raise ValueError("Scope muss 'project' oder 'global' sein.")

View File

@@ -1,37 +0,0 @@
from qgis.core import QgsProject, QgsExpressionContextUtils
class SettingsLogic:
def __init__(self):
self.project = QgsProject.instance()
# Definition der Variablen-Namen
self.global_vars = ["amt", "behoerde", "landkreis_user", "sachgebiet"]
self.project_vars = ["bezeichnung", "verfahrensnummer", "gemeinden", "landkreise_proj"]
def save(self, fields: dict):
"""Speichert Felder als globale und projektbezogene Ausdrucksvariablen."""
# Globale Variablen
for key in self.global_vars:
QgsExpressionContextUtils.setGlobalVariable(f"sn_{key}", fields.get(key, ""))
# Projektvariablen
for key in self.project_vars:
QgsExpressionContextUtils.setProjectVariable(self.project, f"sn_{key}", fields.get(key, ""))
print("✅ Ausdrucksvariablen gespeichert.")
def load(self) -> dict:
"""Lädt Werte ausschließlich aus Ausdrucksvariablen (global + projektbezogen)."""
data = {}
# Globale Variablen
for key in self.global_vars:
data[key] = QgsExpressionContextUtils.globalScope().variable(f"sn_{key}") or ""
# Projektvariablen
for key in self.project_vars:
data[key] = QgsExpressionContextUtils.projectScope(self.project).variable(f"sn_{key}") or ""
return data

View File

@@ -1,35 +0,0 @@
from qgis.core import QgsProject, QgsExpressionContextUtils
def get_variable(key: str, scope: str = "project") -> str:
"""
Liefert den Wert einer sn_* Variable zurück.
key: Name ohne Präfix, z.B. "verfahrensnummer"
scope: 'project' oder 'global'
"""
projekt = QgsProject.instance()
var_name = f"sn_{key}"
if scope == "project":
return QgsExpressionContextUtils.projectScope(projekt).variable(var_name) or ""
elif scope == "global":
return QgsExpressionContextUtils.globalScope().variable(var_name) or ""
else:
raise ValueError("Scope muss 'project' oder 'global' sein.")
def set_variable(key: str, value: str, scope: str = "project"):
"""
Schreibt den Wert einer sn_* Variable.
key: Name ohne Präfix, z.B. "verfahrensnummer"
value: Wert, der gespeichert werden soll
scope: 'project' oder 'global'
"""
projekt = QgsProject.instance()
var_name = f"sn_{key}"
if scope == "project":
QgsExpressionContextUtils.setProjectVariable(projekt, var_name, value)
elif scope == "global":
QgsExpressionContextUtils.setGlobalVariable(var_name, value)
else:
raise ValueError("Scope muss 'project' oder 'global' sein.")

43
main.py
View File

@@ -1,26 +1,53 @@
from qgis.PyQt.QtCore import QCoreApplication # sn_basis/main.py
from qgis.utils import plugins from qgis.utils import plugins
from sn_basis.functions.qt_wrapper import QCoreApplication
from sn_basis.functions.sys_wrapper import getattr_safe
from sn_basis.ui.navigation import Navigation from sn_basis.ui.navigation import Navigation
class BasisPlugin: class BasisPlugin:
"""
Einstiegspunkt des sn_basis-Plugins.
Orchestriert UI und Fachmodule keine UI-Logik.
"""
def __init__(self, iface): def __init__(self, iface):
self.iface = iface # iface wird von QGIS übergeben, aber nicht direkt verwendet
self.ui = None self.ui = None
QCoreApplication.instance().aboutToQuit.connect(self.unload)
# QCoreApplication kann im Mock-Modus None sein
if QCoreApplication is not None:
app = getattr_safe(QCoreApplication, "instance")
if callable(app):
instance = app()
about_to_quit = getattr_safe(instance, "aboutToQuit")
connect = getattr_safe(about_to_quit, "connect")
if callable(connect):
connect(self.unload)
def initGui(self): def initGui(self):
# Basis-Navigation neu aufbauen """
self.ui = Navigation(self.iface) Initialisiert die Basis-Navigation und triggert initGui
aller abhängigen sn_-Plugins.
# Alle Fachplugins mit "sn_" prüfen und neu initialisieren """
self.ui = Navigation()
self.ui.init_ui()
for name, plugin in plugins.items(): for name, plugin in plugins.items():
if name.startswith("sn_") and name != "sn_basis": if name.startswith("sn_") and name != "sn_basis":
try: try:
plugin.initGui() init_gui = getattr_safe(plugin, "initGui")
if callable(init_gui):
init_gui()
except Exception as e: except Exception as e:
print(f"Fehler beim Neuinitialisieren von {name}: {e}") print(f"Fehler beim Neuinitialisieren von {name}: {e}")
self.ui.finalize_menu_and_toolbar()
def unload(self): def unload(self):
"""
Räumt UI-Komponenten sauber auf.
"""
if self.ui: if self.ui:
self.ui.remove_all() self.ui.remove_all()
self.ui = None self.ui = None

View File

@@ -2,7 +2,7 @@
name=LNO Sachsen | Basisfunktionen name=LNO Sachsen | Basisfunktionen
qgisMinimumVersion=3.0 qgisMinimumVersion=3.0
description=Plugin mit Basisfunktionen description=Plugin mit Basisfunktionen
version=25.11.3 version=25.11.4
author=Michael Otto author=Michael Otto
email=michael.otto@landkreis-mittelsachsen.de email=michael.otto@landkreis-mittelsachsen.de
about=Plugin mit Basisfunktionen about=Plugin mit Basisfunktionen

134
modules/Dateipruefer.py Normal file
View File

@@ -0,0 +1,134 @@
"""
sn_basis/modules/Dateipruefer.py Prüfung von Dateieingaben für das Plugin.
Verwendet sys_wrapper und gibt pruef_ergebnis an den Pruefmanager zurück.
"""
from pathlib import Path
from sn_basis.functions.sys_wrapper import (
join_path,
file_exists,
)
from sn_basis.modules.Pruefmanager import pruef_ergebnis
class Dateipruefer:
"""
Prüft Dateieingaben und liefert ein pruef_ergebnis zurück.
Die eigentliche Nutzerinteraktion übernimmt der Pruefmanager.
"""
def __init__(
self,
pfad: str,
basis_pfad: str = "",
leereingabe_erlaubt: bool = False,
standarddatei: str | None = None,
temporaer_erlaubt: bool = False,
):
self.pfad = pfad
self.basis_pfad = basis_pfad
self.leereingabe_erlaubt = leereingabe_erlaubt
self.standarddatei = standarddatei
self.temporaer_erlaubt = temporaer_erlaubt
# ---------------------------------------------------------
# Hilfsfunktion
# ---------------------------------------------------------
def _pfad(self, relativer_pfad: str) -> Path:
"""
Erzeugt einen OSunabhängigen Pfad relativ zum Basisverzeichnis.
"""
return join_path(self.basis_pfad, relativer_pfad)
# ---------------------------------------------------------
# Hauptfunktion
# ---------------------------------------------------------
def pruefe(self) -> pruef_ergebnis:
"""
Prüft eine Dateieingabe und liefert ein pruef_ergebnis zurück.
Der Pruefmanager entscheidet später, wie der Nutzer gefragt wird.
"""
# -----------------------------------------------------
# 1. Fall: Eingabe ist leer
# -----------------------------------------------------
if not self.pfad:
return self._handle_leere_eingabe()
# -----------------------------------------------------
# 2. Fall: Eingabe ist nicht leer → Datei prüfen
# -----------------------------------------------------
pfad = self._pfad(self.pfad)
if not file_exists(pfad):
return pruef_ergebnis(
ok=False,
meldung=f"Die Datei '{self.pfad}' wurde nicht gefunden.",
aktion="datei_nicht_gefunden",
kontext=pfad,
)
# -----------------------------------------------------
# 3. Datei existiert → Erfolg
# -----------------------------------------------------
return pruef_ergebnis(
ok=True,
meldung="Datei gefunden.",
aktion="ok",
kontext=pfad,
)
# ---------------------------------------------------------
# Behandlung leerer Eingaben
# ---------------------------------------------------------
def _handle_leere_eingabe(self) -> pruef_ergebnis:
"""
Liefert ein pruef_ergebnis für den Fall, dass das Dateifeld leer ist.
Der Pruefmanager fragt später den Nutzer.
"""
# 1. Leereingabe erlaubt → Nutzer fragen, ob das beabsichtigt war
if self.leereingabe_erlaubt:
return pruef_ergebnis(
ok=False,
meldung="Das Dateifeld ist leer. Soll ohne Datei fortgefahren werden?",
aktion="leereingabe_erlaubt",
kontext=None,
)
# 2. Standarddatei verfügbar → Nutzer fragen, ob sie verwendet werden soll
if self.standarddatei:
return pruef_ergebnis(
ok=False,
meldung=(
f"Es wurde keine Datei angegeben. "
f"Soll die Standarddatei '{self.standarddatei}' verwendet werden?"
),
aktion="standarddatei_vorschlagen",
kontext=self._pfad(self.standarddatei),
)
# 3. Temporäre Datei erlaubt → Nutzer fragen, ob temporär gearbeitet werden soll
if self.temporaer_erlaubt:
return pruef_ergebnis(
ok=False,
meldung=(
"Es wurde keine Datei angegeben. "
"Soll eine temporäre Datei erzeugt werden?"
),
aktion="temporaer_erlaubt",
kontext=None,
)
# 4. Leereingabe nicht erlaubt → Fehler
return pruef_ergebnis(
ok=False,
meldung="Es wurde keine Datei angegeben.",
aktion="leereingabe_nicht_erlaubt",
kontext=None,
)

View File

@@ -0,0 +1 @@
#Datenbankpruefer.py

160
modules/Pruefmanager.py Normal file
View File

@@ -0,0 +1,160 @@
"""
sn_basis/modules/Pruefmanager.py zentrale Verarbeitung von pruef_ergebnis-Objekten.
Steuert die Nutzerinteraktion über Wrapper.
"""
from sn_basis.functions import (
ask_yes_no,
info,
warning,
error,
set_layer_visible,
)
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
class Pruefmanager:
"""
Verarbeitet pruef_ergebnis-Objekte und steuert die Nutzerinteraktion.
"""
def __init__(self, ui_modus: str = "qgis"):
self.ui_modus = ui_modus
# ---------------------------------------------------------
# Hauptfunktion
# ---------------------------------------------------------
def verarbeite(self, ergebnis: pruef_ergebnis) -> pruef_ergebnis:
"""
Verarbeitet ein pruef_ergebnis und führt ggf. Nutzerinteraktion durch.
Rückgabe: neues oder unverändertes pruef_ergebnis.
"""
if ergebnis.ok:
return ergebnis
aktion = ergebnis.aktion
kontext = ergebnis.kontext
# -----------------------------------------------------
# Allgemeine Aktionen
# -----------------------------------------------------
if aktion == "leer":
warning("Eingabe fehlt", ergebnis.meldung)
return ergebnis
if aktion == "leereingabe_erlaubt":
if ask_yes_no("Ohne Eingabe fortfahren", ergebnis.meldung):
return pruef_ergebnis(
ok=True,
meldung="Ohne Eingabe fortgefahren.",
aktion="ok",
kontext=None,
)
return ergebnis
if aktion == "leereingabe_nicht_erlaubt":
warning("Eingabe erforderlich", ergebnis.meldung)
return ergebnis
if aktion == "standarddatei_vorschlagen":
if ask_yes_no("Standarddatei verwenden", ergebnis.meldung):
return pruef_ergebnis(
ok=True,
meldung="Standarddatei wird verwendet.",
aktion="ok",
kontext=kontext,
)
return ergebnis
if aktion == "temporaer_erlaubt":
if ask_yes_no("Temporäre Datei erzeugen", ergebnis.meldung):
return pruef_ergebnis(
ok=True,
meldung="Temporäre Datei soll erzeugt werden.",
aktion="temporaer_erzeugen",
kontext=None,
)
return ergebnis
if aktion == "datei_nicht_gefunden":
warning("Datei nicht gefunden", ergebnis.meldung)
return ergebnis
if aktion == "kein_dateipfad":
warning("Ungültiger Pfad", ergebnis.meldung)
return ergebnis
if aktion == "pfad_nicht_gefunden":
warning("Pfad nicht gefunden", ergebnis.meldung)
return ergebnis
if aktion == "url_nicht_erreichbar":
warning("URL nicht erreichbar", ergebnis.meldung)
return ergebnis
if aktion == "netzwerkfehler":
error("Netzwerkfehler", ergebnis.meldung)
return ergebnis
# -----------------------------------------------------
# Layer-Aktionen
# -----------------------------------------------------
if aktion == "layer_nicht_gefunden":
error("Layer fehlt", ergebnis.meldung)
return ergebnis
if aktion == "layer_unsichtbar":
if ask_yes_no("Layer einblenden", ergebnis.meldung):
if kontext is not None:
try:
set_layer_visible(kontext, True)
except Exception:
pass
return pruef_ergebnis(
ok=True,
meldung="Layer wurde eingeblendet.",
aktion="ok",
kontext=kontext,
)
return ergebnis
if aktion == "falscher_geotyp":
warning("Falscher Geometrietyp", ergebnis.meldung)
return ergebnis
if aktion == "layer_leer":
warning("Layer enthält keine Objekte", ergebnis.meldung)
return ergebnis
if aktion == "falscher_layertyp":
warning("Falscher Layertyp", ergebnis.meldung)
return ergebnis
if aktion == "falsches_crs":
warning("Falsches CRS", ergebnis.meldung)
return ergebnis
if aktion == "felder_fehlen":
warning("Fehlende Felder", ergebnis.meldung)
return ergebnis
if aktion == "datenquelle_unerwartet":
warning("Unerwartete Datenquelle", ergebnis.meldung)
return ergebnis
if aktion == "layer_nicht_editierbar":
warning("Layer nicht editierbar", ergebnis.meldung)
return ergebnis
# -----------------------------------------------------
# Fallback
# -----------------------------------------------------
warning("Unbekannte Aktion", f"Unbekannte Aktion: {aktion}")
return ergebnis

182
modules/layerpruefer.py Normal file
View File

@@ -0,0 +1,182 @@
"""
sn_basis/modules/layerpruefer.py Prüfung von QGIS-Layern.
Verwendet ausschließlich Wrapper und gibt pruef_ergebnis zurück.
"""
from sn_basis.functions import (
layer_exists,
get_layer_geometry_type,
get_layer_feature_count,
is_layer_visible,
get_layer_type,
get_layer_crs,
get_layer_fields,
get_layer_source,
is_layer_editable,
)
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis, PruefAktion
class Layerpruefer:
"""
Prüft Layer auf Existenz, Sichtbarkeit, Geometrietyp, Objektanzahl,
Layertyp, CRS, Felder, Datenquelle und Editierbarkeit.
"""
def __init__(
self,
layer,
erwarteter_geotyp: str | None = None,
muss_sichtbar_sein: bool = False,
erwarteter_layertyp: str | None = None,
erwartetes_crs: str | None = None,
erforderliche_felder: list[str] | None = None,
erlaubte_datenquellen: list[str] | None = None,
muss_editierbar_sein: bool = False,
):
self.layer = layer
self.erwarteter_geotyp = erwarteter_geotyp
self.muss_sichtbar_sein = muss_sichtbar_sein
self.erwarteter_layertyp = erwarteter_layertyp
self.erwartetes_crs = erwartetes_crs
self.erforderliche_felder = erforderliche_felder or []
self.erlaubte_datenquellen = erlaubte_datenquellen or []
self.muss_editierbar_sein = muss_editierbar_sein
# ---------------------------------------------------------
# Hauptfunktion
# ---------------------------------------------------------
def pruefe(self) -> pruef_ergebnis:
# -----------------------------------------------------
# 1. Existenz
# -----------------------------------------------------
if not layer_exists(self.layer):
return pruef_ergebnis(
ok=False,
meldung="Der Layer existiert nicht oder wurde nicht geladen.",
aktion="layer_nicht_gefunden",
kontext=None,
)
# -----------------------------------------------------
# 2. Sichtbarkeit
# -----------------------------------------------------
sichtbar = is_layer_visible(self.layer)
if self.muss_sichtbar_sein and not sichtbar:
return pruef_ergebnis(
ok=False,
meldung="Der Layer ist unsichtbar. Soll er eingeblendet werden?",
aktion="layer_unsichtbar",
kontext=self.layer, # Layerobjekt als Kontext
)
# -----------------------------------------------------
# 3. Layertyp
# -----------------------------------------------------
layertyp = get_layer_type(self.layer)
if self.erwarteter_layertyp and layertyp != self.erwarteter_layertyp:
return pruef_ergebnis(
ok=False,
meldung=(
f"Der Layer hat den Typ '{layertyp}', "
f"erwartet wurde '{self.erwarteter_layertyp}'."
),
aktion="falscher_layertyp",
kontext=None,
)
# -----------------------------------------------------
# 4. Geometrietyp
# -----------------------------------------------------
geotyp = get_layer_geometry_type(self.layer)
if self.erwarteter_geotyp and geotyp != self.erwarteter_geotyp:
return pruef_ergebnis(
ok=False,
meldung=(
f"Der Layer hat den Geometrietyp '{geotyp}', "
f"erwartet wurde '{self.erwarteter_geotyp}'."
),
aktion="falscher_geotyp",
kontext=None,
)
# -----------------------------------------------------
# 5. Featureanzahl
# -----------------------------------------------------
anzahl = get_layer_feature_count(self.layer)
if anzahl == 0:
return pruef_ergebnis(
ok=False,
meldung="Der Layer enthält keine Objekte.",
aktion="layer_leer",
kontext=None,
)
# -----------------------------------------------------
# 6. CRS
# -----------------------------------------------------
crs = get_layer_crs(self.layer)
if self.erwartetes_crs and crs != self.erwartetes_crs:
return pruef_ergebnis(
ok=False,
meldung=(
f"Der Layer hat das CRS '{crs}', "
f"erwartet wurde '{self.erwartetes_crs}'."
),
aktion="falsches_crs",
kontext=None,
)
# -----------------------------------------------------
# 7. Felder
# -----------------------------------------------------
felder = get_layer_fields(self.layer)
fehlende = [f for f in self.erforderliche_felder if f not in felder]
if fehlende:
return pruef_ergebnis(
ok=False,
meldung=(
"Der Layer enthält nicht alle erforderlichen Felder: "
+ ", ".join(fehlende)
),
aktion="felder_fehlen",
kontext=None,
)
# -----------------------------------------------------
# 8. Datenquelle
# -----------------------------------------------------
quelle = get_layer_source(self.layer)
if self.erlaubte_datenquellen and quelle not in self.erlaubte_datenquellen:
return pruef_ergebnis(
ok=False,
meldung=f"Die Datenquelle '{quelle}' ist nicht erlaubt.",
aktion="datenquelle_unerwartet",
kontext=None,
)
# -----------------------------------------------------
# 9. Editierbarkeit
# -----------------------------------------------------
editable = is_layer_editable(self.layer)
if self.muss_editierbar_sein and not editable:
return pruef_ergebnis(
ok=False,
meldung="Der Layer ist nicht editierbar.",
aktion="layer_nicht_editierbar",
kontext=None,
)
# -----------------------------------------------------
# 10. Alles OK
# -----------------------------------------------------
return pruef_ergebnis(
ok=True,
meldung="Layerprüfung erfolgreich.",
aktion="ok",
kontext=None,
)

134
modules/linkpruefer.py Normal file
View File

@@ -0,0 +1,134 @@
"""
sn_basis/modules/linkpruefer.py Prüfung von URLs und lokalen Links.
Verwendet Wrapper und gibt pruef_ergebnis an den Pruefmanager zurück.
"""
from pathlib import Path
from sn_basis.functions import (
file_exists,
join_path,
network_head,
)
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis, PruefAktion
class Linkpruefer:
"""
Prüft URLs und lokale Pfade.
Die eigentliche Nutzerinteraktion übernimmt der Pruefmanager.
"""
def __init__(self, basis_pfad: str | None = None):
"""
basis_pfad: optionaler Basisordner für relative Pfade.
"""
self.basis = basis_pfad
# ---------------------------------------------------------
# Hilfsfunktionen
# ---------------------------------------------------------
def _pfad(self, relativer_pfad: str) -> Path:
"""
Erzeugt einen OSunabhängigen Pfad relativ zum Basisverzeichnis.
"""
if not self.basis:
return Path(relativer_pfad)
return join_path(self.basis, relativer_pfad)
def _ist_url(self, text: str) -> bool:
"""
Einfache URL-Erkennung.
"""
return text.startswith("http://") or text.startswith("https://")
# ---------------------------------------------------------
# Hauptfunktion
# ---------------------------------------------------------
def pruefe(self, eingabe: str) -> pruef_ergebnis:
"""
Prüft einen Link (URL oder lokalen Pfad).
Rückgabe: pruef_ergebnis
"""
if not eingabe:
return pruef_ergebnis(
ok=False,
meldung="Es wurde kein Link angegeben.",
aktion="leer",
kontext=None,
)
# -----------------------------------------------------
# 1. Fall: URL
# -----------------------------------------------------
if self._ist_url(eingabe):
return self._pruefe_url(eingabe)
# -----------------------------------------------------
# 2. Fall: lokaler Pfad
# -----------------------------------------------------
return self._pruefe_dateipfad(eingabe)
# ---------------------------------------------------------
# URLPrüfung
# ---------------------------------------------------------
def _pruefe_url(self, url: str) -> pruef_ergebnis:
"""
Prüft eine URL über einen HEADRequest.
"""
reply = network_head(url)
if reply is None:
return pruef_ergebnis(
ok=False,
meldung=f"Die URL '{url}' konnte nicht geprüft werden.",
aktion="netzwerkfehler",
kontext=url,
)
if reply.error != 0:
return pruef_ergebnis(
ok=False,
meldung=f"Die URL '{url}' ist nicht erreichbar.",
aktion="url_nicht_erreichbar",
kontext=url,
)
return pruef_ergebnis(
ok=True,
meldung="URL ist erreichbar.",
aktion="ok",
kontext=url,
)
# ---------------------------------------------------------
# Lokale Datei/Pfadprüfung
# ---------------------------------------------------------
def _pruefe_dateipfad(self, eingabe: str) -> pruef_ergebnis:
"""
Prüft einen lokalen Pfad.
"""
pfad = self._pfad(eingabe)
if not file_exists(pfad):
return pruef_ergebnis(
ok=False,
meldung=f"Der Pfad '{eingabe}' wurde nicht gefunden.",
aktion="pfad_nicht_gefunden",
kontext=pfad,
)
return pruef_ergebnis(
ok=True,
meldung="Dateipfad ist gültig.",
aktion="ok",
kontext=pfad,
)

49
modules/pruef_ergebnis.py Normal file
View File

@@ -0,0 +1,49 @@
"""
sn_basis/modules/pruef_ergebnis.py Ergebnisobjekt für alle Prüfer.
"""
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Optional, Literal
# Alle möglichen Aktionen, die ein Prüfer auslösen kann.
# Erweiterbar ohne Umbau der Klasse.
PruefAktion = Literal[
"ok",
"leer",
"leereingabe_erlaubt",
"leereingabe_nicht_erlaubt",
"standarddatei_vorschlagen",
"temporaer_erlaubt",
"datei_nicht_gefunden",
"kein_dateipfad",
"pfad_nicht_gefunden",
"url_nicht_erreichbar",
"netzwerkfehler",
"falscher_layertyp",
"falscher_geotyp",
"layer_leer",
"falsches_crs",
"felder_fehlen",
"datenquelle_unerwartet",
"layer_nicht_editierbar",
"temporaer_erzeugen",
"stil_nicht_anwendbar",
"layer_unsichtbar",
"layer_nicht_gefunden",
"unbekannt",
"stil_anwendbar",
"falsche_endung",
]
@dataclass(slots=True)
class pruef_ergebnis:
ok: bool
meldung: str
aktion: PruefAktion
kontext: Optional[Any] = None

75
modules/stilpruefer.py Normal file
View File

@@ -0,0 +1,75 @@
"""
sn_basis/modules/stilpruefer.py Prüfung von Layerstilen.
Prüft ausschließlich, ob ein Stilpfad gültig ist.
Die Anwendung erfolgt später über eine Aktion.
"""
from pathlib import Path
from sn_basis.functions import file_exists
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
class Stilpruefer:
"""
Prüft, ob ein Stilpfad gültig ist und angewendet werden kann.
Keine Seiteneffekte, keine QGIS-Aufrufe.
"""
def __init__(self):
pass
# ---------------------------------------------------------
# Hauptfunktion
# ---------------------------------------------------------
def pruefe(self, stil_pfad: str) -> pruef_ergebnis:
"""
Prüft einen Stilpfad.
Rückgabe: pruef_ergebnis
"""
# -----------------------------------------------------
# 1. Kein Stil angegeben → OK
# -----------------------------------------------------
if not stil_pfad:
return pruef_ergebnis(
ok=True,
meldung="Kein Stil angegeben.",
aktion="ok",
kontext=None,
)
pfad = Path(stil_pfad)
# -----------------------------------------------------
# 2. Datei existiert nicht
# -----------------------------------------------------
if not file_exists(pfad):
return pruef_ergebnis(
ok=False,
meldung=f"Die Stil-Datei '{stil_pfad}' wurde nicht gefunden.",
aktion="datei_nicht_gefunden",
kontext=pfad,
)
# -----------------------------------------------------
# 3. Falsche Endung
# -----------------------------------------------------
if pfad.suffix.lower() != ".qml":
return pruef_ergebnis(
ok=False,
meldung="Die Stil-Datei muss die Endung '.qml' haben.",
aktion="falsche_endung",
kontext=pfad,
)
# -----------------------------------------------------
# 4. Stil ist gültig → Anwendung später
# -----------------------------------------------------
return pruef_ergebnis(
ok=True,
meldung="Stil-Datei ist gültig.",
aktion="stil_anwendbar",
kontext=pfad,
)

View File

@@ -0,0 +1,609 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis hasScaleBasedVisibilityFlag="0" version="3.16.0-Hannover" minScale="100000000" labelsEnabled="0" readOnly="0" styleCategories="AllStyleCategories" simplifyLocal="1" maxScale="0" simplifyMaxScale="1" simplifyDrawingTol="1" simplifyDrawingHints="1" simplifyAlgorithm="0">
<flags>
<Identifiable>1</Identifiable>
<Removable>1</Removable>
<Searchable>1</Searchable>
</flags>
<temporal startExpression="" enabled="0" endExpression="" accumulate="0" durationField="" durationUnit="min" fixedDuration="0" startField="" mode="0" endField="">
<fixedRange>
<start></start>
<end></end>
</fixedRange>
</temporal>
<renderer-v2 symbollevels="0" forceraster="0" type="singleSymbol" enableorderby="0">
<symbols>
<symbol name="0" force_rhr="0" type="fill" alpha="1" clip_to_extent="1">
<layer pass="0" enabled="1" class="LinePatternFill" locked="0">
<prop k="angle" v="45"/>
<prop k="color" v="196,60,57,255"/>
<prop k="distance" v="10"/>
<prop k="distance_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="distance_unit" v="Pixel"/>
<prop k="line_width" v="0.26"/>
<prop k="line_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="line_width_unit" v="MM"/>
<prop k="offset" v="0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<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>
<symbol name="@0@0" force_rhr="0" type="line" alpha="1" clip_to_extent="1">
<layer pass="0" enabled="1" class="SimpleLine" locked="0">
<prop k="align_dash_pattern" v="0"/>
<prop k="capstyle" v="square"/>
<prop k="customdash" v="5;2"/>
<prop k="customdash_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="customdash_unit" v="MM"/>
<prop k="dash_pattern_offset" v="0"/>
<prop k="dash_pattern_offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="dash_pattern_offset_unit" v="MM"/>
<prop k="draw_inside_polygon" v="0"/>
<prop k="joinstyle" v="bevel"/>
<prop k="line_color" v="0,0,0,255"/>
<prop k="line_style" v="solid"/>
<prop k="line_width" v="1"/>
<prop k="line_width_unit" v="Pixel"/>
<prop k="offset" v="0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="ring_filter" v="0"/>
<prop k="tweak_dash_pattern_on_corners" v="0"/>
<prop k="use_custom_dash" v="0"/>
<prop k="width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<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>
</layer>
<layer pass="0" enabled="1" class="LinePatternFill" locked="0">
<prop k="angle" v="135"/>
<prop k="color" v="0,0,255,255"/>
<prop k="distance" v="10"/>
<prop k="distance_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="distance_unit" v="Pixel"/>
<prop k="line_width" v="0.26"/>
<prop k="line_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="line_width_unit" v="MM"/>
<prop k="offset" v="0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<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>
<symbol name="@0@1" force_rhr="0" type="line" alpha="1" clip_to_extent="1">
<layer pass="0" enabled="1" class="SimpleLine" locked="0">
<prop k="align_dash_pattern" v="0"/>
<prop k="capstyle" v="square"/>
<prop k="customdash" v="5;2"/>
<prop k="customdash_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="customdash_unit" v="MM"/>
<prop k="dash_pattern_offset" v="0"/>
<prop k="dash_pattern_offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="dash_pattern_offset_unit" v="MM"/>
<prop k="draw_inside_polygon" v="0"/>
<prop k="joinstyle" v="bevel"/>
<prop k="line_color" v="0,0,0,255"/>
<prop k="line_style" v="solid"/>
<prop k="line_width" v="1"/>
<prop k="line_width_unit" v="Pixel"/>
<prop k="offset" v="0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="ring_filter" v="0"/>
<prop k="tweak_dash_pattern_on_corners" v="0"/>
<prop k="use_custom_dash" v="0"/>
<prop k="width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<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>
</layer>
<layer pass="0" enabled="1" class="SimpleLine" locked="0">
<prop k="align_dash_pattern" v="0"/>
<prop k="capstyle" v="square"/>
<prop k="customdash" v="5;2"/>
<prop k="customdash_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="customdash_unit" v="MM"/>
<prop k="dash_pattern_offset" v="0"/>
<prop k="dash_pattern_offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="dash_pattern_offset_unit" v="MM"/>
<prop k="draw_inside_polygon" v="0"/>
<prop k="joinstyle" v="bevel"/>
<prop k="line_color" v="35,35,35,255"/>
<prop k="line_style" v="solid"/>
<prop k="line_width" v="1"/>
<prop k="line_width_unit" v="Pixel"/>
<prop k="offset" v="0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="ring_filter" v="0"/>
<prop k="tweak_dash_pattern_on_corners" v="0"/>
<prop k="use_custom_dash" v="0"/>
<prop k="width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<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/>
</renderer-v2>
<customproperties>
<property key="dualview/previewExpressions">
<value>"gml_id"</value>
</property>
<property key="embeddedWidgets/count" value="0"/>
<property key="variableNames"/>
<property key="variableValues"/>
</customproperties>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>1</layerOpacity>
<SingleCategoryDiagramRenderer diagramType="Histogram" attributeLegend="1">
<DiagramCategory enabled="0" spacing="5" showAxis="1" direction="0" sizeType="MM" barWidth="5" height="15" minScaleDenominator="0" scaleBasedVisibility="0" spacingUnit="MM" maxScaleDenominator="1e+08" sizeScale="3x:0,0,0,0,0,0" diagramOrientation="Up" rotationOffset="270" penColor="#000000" penWidth="0" backgroundColor="#ffffff" penAlpha="255" width="15" spacingUnitScale="3x:0,0,0,0,0,0" backgroundAlpha="255" opacity="1" lineSizeType="MM" scaleDependency="Area" minimumSize="0" labelPlacementMethod="XHeight" lineSizeScale="3x:0,0,0,0,0,0">
<fontProperties description="MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0" style=""/>
<axisSymbol>
<symbol name="" force_rhr="0" type="line" alpha="1" clip_to_extent="1">
<layer pass="0" enabled="1" class="SimpleLine" locked="0">
<prop k="align_dash_pattern" v="0"/>
<prop k="capstyle" v="square"/>
<prop k="customdash" v="5;2"/>
<prop k="customdash_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="customdash_unit" v="MM"/>
<prop k="dash_pattern_offset" v="0"/>
<prop k="dash_pattern_offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="dash_pattern_offset_unit" v="MM"/>
<prop k="draw_inside_polygon" v="0"/>
<prop k="joinstyle" v="bevel"/>
<prop k="line_color" v="35,35,35,255"/>
<prop k="line_style" v="solid"/>
<prop k="line_width" v="0.26"/>
<prop k="line_width_unit" v="MM"/>
<prop k="offset" v="0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="ring_filter" v="0"/>
<prop k="tweak_dash_pattern_on_corners" v="0"/>
<prop k="use_custom_dash" v="0"/>
<prop k="width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<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>
</axisSymbol>
</DiagramCategory>
</SingleCategoryDiagramRenderer>
<DiagramLayerSettings priority="0" obstacle="0" showAll="1" placement="1" dist="0" linePlacementFlags="18" zIndex="0">
<properties>
<Option type="Map">
<Option name="name" type="QString" value=""/>
<Option name="properties"/>
<Option name="type" type="QString" value="collection"/>
</Option>
</properties>
</DiagramLayerSettings>
<geometryOptions removeDuplicateNodes="0" geometryPrecision="0">
<activeChecks/>
<checkConfiguration type="Map">
<Option name="QgsGeometryGapCheck" type="Map">
<Option name="allowedGapsBuffer" type="double" value="0"/>
<Option name="allowedGapsEnabled" type="bool" value="false"/>
<Option name="allowedGapsLayer" type="QString" value=""/>
</Option>
</checkConfiguration>
</geometryOptions>
<legend type="default-vector"/>
<referencedLayers/>
<fieldConfiguration>
<field configurationFlags="None" name="gml_id">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="objectid">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="obj_nr">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="typ">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="text_1">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_objnr">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_hida_n">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_eigenn">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_kreis">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_gemein">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_ortste">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_strass">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_hausnu">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_gemark">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_flurst">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_kchar1">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_kchar2">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_kchar3">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_datier">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="ext_typ">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="gd_ueberar">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="sort">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field configurationFlags="None" name="Stand">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
</fieldConfiguration>
<aliases>
<alias name="" field="gml_id" index="0"/>
<alias name="" field="objectid" index="1"/>
<alias name="" field="obj_nr" index="2"/>
<alias name="" field="typ" index="3"/>
<alias name="" field="text_1" index="4"/>
<alias name="" field="ext_objnr" index="5"/>
<alias name="" field="ext_hida_n" index="6"/>
<alias name="" field="ext_eigenn" index="7"/>
<alias name="" field="ext_kreis" index="8"/>
<alias name="" field="ext_gemein" index="9"/>
<alias name="" field="ext_ortste" index="10"/>
<alias name="" field="ext_strass" index="11"/>
<alias name="" field="ext_hausnu" index="12"/>
<alias name="" field="ext_gemark" index="13"/>
<alias name="" field="ext_flurst" index="14"/>
<alias name="" field="ext_kchar1" index="15"/>
<alias name="" field="ext_kchar2" index="16"/>
<alias name="" field="ext_kchar3" index="17"/>
<alias name="" field="ext_datier" index="18"/>
<alias name="" field="ext_typ" index="19"/>
<alias name="" field="gd_ueberar" index="20"/>
<alias name="" field="sort" index="21"/>
<alias name="" field="Stand" index="22"/>
</aliases>
<defaults>
<default field="gml_id" expression="" applyOnUpdate="0"/>
<default field="objectid" expression="" applyOnUpdate="0"/>
<default field="obj_nr" expression="" applyOnUpdate="0"/>
<default field="typ" expression="" applyOnUpdate="0"/>
<default field="text_1" expression="" applyOnUpdate="0"/>
<default field="ext_objnr" expression="" applyOnUpdate="0"/>
<default field="ext_hida_n" expression="" applyOnUpdate="0"/>
<default field="ext_eigenn" expression="" applyOnUpdate="0"/>
<default field="ext_kreis" expression="" applyOnUpdate="0"/>
<default field="ext_gemein" expression="" applyOnUpdate="0"/>
<default field="ext_ortste" expression="" applyOnUpdate="0"/>
<default field="ext_strass" expression="" applyOnUpdate="0"/>
<default field="ext_hausnu" expression="" applyOnUpdate="0"/>
<default field="ext_gemark" expression="" applyOnUpdate="0"/>
<default field="ext_flurst" expression="" applyOnUpdate="0"/>
<default field="ext_kchar1" expression="" applyOnUpdate="0"/>
<default field="ext_kchar2" expression="" applyOnUpdate="0"/>
<default field="ext_kchar3" expression="" applyOnUpdate="0"/>
<default field="ext_datier" expression="" applyOnUpdate="0"/>
<default field="ext_typ" expression="" applyOnUpdate="0"/>
<default field="gd_ueberar" expression="" applyOnUpdate="0"/>
<default field="sort" expression="" applyOnUpdate="0"/>
<default field="Stand" expression="" applyOnUpdate="0"/>
</defaults>
<constraints>
<constraint exp_strength="0" field="gml_id" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="objectid" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="obj_nr" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="typ" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="text_1" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_objnr" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_hida_n" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_eigenn" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_kreis" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_gemein" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_ortste" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_strass" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_hausnu" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_gemark" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_flurst" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_kchar1" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_kchar2" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_kchar3" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_datier" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="ext_typ" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="gd_ueberar" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="sort" unique_strength="0" notnull_strength="0" constraints="0"/>
<constraint exp_strength="0" field="Stand" unique_strength="0" notnull_strength="0" constraints="0"/>
</constraints>
<constraintExpressions>
<constraint field="gml_id" desc="" exp=""/>
<constraint field="objectid" desc="" exp=""/>
<constraint field="obj_nr" desc="" exp=""/>
<constraint field="typ" desc="" exp=""/>
<constraint field="text_1" desc="" exp=""/>
<constraint field="ext_objnr" desc="" exp=""/>
<constraint field="ext_hida_n" desc="" exp=""/>
<constraint field="ext_eigenn" desc="" exp=""/>
<constraint field="ext_kreis" desc="" exp=""/>
<constraint field="ext_gemein" desc="" exp=""/>
<constraint field="ext_ortste" desc="" exp=""/>
<constraint field="ext_strass" desc="" exp=""/>
<constraint field="ext_hausnu" desc="" exp=""/>
<constraint field="ext_gemark" desc="" exp=""/>
<constraint field="ext_flurst" desc="" exp=""/>
<constraint field="ext_kchar1" desc="" exp=""/>
<constraint field="ext_kchar2" desc="" exp=""/>
<constraint field="ext_kchar3" desc="" exp=""/>
<constraint field="ext_datier" desc="" exp=""/>
<constraint field="ext_typ" desc="" exp=""/>
<constraint field="gd_ueberar" desc="" exp=""/>
<constraint field="sort" desc="" exp=""/>
<constraint field="Stand" desc="" exp=""/>
</constraintExpressions>
<expressionfields/>
<attributeactions>
<defaultAction key="Canvas" value="{00000000-0000-0000-0000-000000000000}"/>
</attributeactions>
<attributetableconfig sortOrder="0" actionWidgetStyle="dropDown" sortExpression="&quot;gml_id&quot;">
<columns>
<column width="-1" name="gml_id" type="field" hidden="0"/>
<column width="-1" name="objectid" type="field" hidden="0"/>
<column width="-1" name="obj_nr" type="field" hidden="0"/>
<column width="-1" name="typ" type="field" hidden="0"/>
<column width="-1" name="text_1" type="field" hidden="0"/>
<column width="-1" name="ext_objnr" type="field" hidden="0"/>
<column width="-1" name="ext_hida_n" type="field" hidden="0"/>
<column width="-1" name="ext_eigenn" type="field" hidden="0"/>
<column width="-1" name="ext_kreis" type="field" hidden="0"/>
<column width="-1" name="ext_gemein" type="field" hidden="0"/>
<column width="-1" name="ext_ortste" type="field" hidden="0"/>
<column width="-1" name="ext_strass" type="field" hidden="0"/>
<column width="-1" name="ext_hausnu" type="field" hidden="0"/>
<column width="-1" name="ext_gemark" type="field" hidden="0"/>
<column width="-1" name="ext_flurst" type="field" hidden="0"/>
<column width="-1" name="ext_kchar1" type="field" hidden="0"/>
<column width="-1" name="ext_kchar2" type="field" hidden="0"/>
<column width="-1" name="ext_kchar3" type="field" hidden="0"/>
<column width="-1" name="ext_datier" type="field" hidden="0"/>
<column width="-1" name="ext_typ" type="field" hidden="0"/>
<column width="-1" name="gd_ueberar" type="field" hidden="0"/>
<column width="-1" name="sort" type="field" hidden="0"/>
<column width="-1" name="Stand" type="field" hidden="0"/>
<column width="-1" type="actions" hidden="1"/>
</columns>
</attributetableconfig>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
<storedexpressions/>
<editform tolerant="1"></editform>
<editforminit/>
<editforminitcodesource>0</editforminitcodesource>
<editforminitfilepath></editforminitfilepath>
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
"""
QGIS forms can have a Python function that is called when the form is
opened.
Use this function to add extra logic to your forms.
Enter the name of the function in the "Python Init function"
field.
An example follows:
"""
from qgis.PyQt.QtWidgets import QWidget
def my_form_open(dialog, layer, feature):
geom = feature.geometry()
control = dialog.findChild(QWidget, "MyLineEdit")
]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<editorlayout>generatedlayout</editorlayout>
<editable>
<field name="Stand" editable="1"/>
<field name="ext_datier" editable="1"/>
<field name="ext_eigenn" editable="1"/>
<field name="ext_flurst" editable="1"/>
<field name="ext_gemark" editable="1"/>
<field name="ext_gemein" editable="1"/>
<field name="ext_hausnu" editable="1"/>
<field name="ext_hida_n" editable="1"/>
<field name="ext_kchar1" editable="1"/>
<field name="ext_kchar2" editable="1"/>
<field name="ext_kchar3" editable="1"/>
<field name="ext_kreis" editable="1"/>
<field name="ext_objnr" editable="1"/>
<field name="ext_ortste" editable="1"/>
<field name="ext_strass" editable="1"/>
<field name="ext_typ" editable="1"/>
<field name="gd_ueberar" editable="1"/>
<field name="gml_id" editable="1"/>
<field name="obj_nr" editable="1"/>
<field name="objectid" editable="1"/>
<field name="sort" editable="1"/>
<field name="text_1" editable="1"/>
<field name="typ" editable="1"/>
</editable>
<labelOnTop>
<field name="Stand" labelOnTop="0"/>
<field name="ext_datier" labelOnTop="0"/>
<field name="ext_eigenn" labelOnTop="0"/>
<field name="ext_flurst" labelOnTop="0"/>
<field name="ext_gemark" labelOnTop="0"/>
<field name="ext_gemein" labelOnTop="0"/>
<field name="ext_hausnu" labelOnTop="0"/>
<field name="ext_hida_n" labelOnTop="0"/>
<field name="ext_kchar1" labelOnTop="0"/>
<field name="ext_kchar2" labelOnTop="0"/>
<field name="ext_kchar3" labelOnTop="0"/>
<field name="ext_kreis" labelOnTop="0"/>
<field name="ext_objnr" labelOnTop="0"/>
<field name="ext_ortste" labelOnTop="0"/>
<field name="ext_strass" labelOnTop="0"/>
<field name="ext_typ" labelOnTop="0"/>
<field name="gd_ueberar" labelOnTop="0"/>
<field name="gml_id" labelOnTop="0"/>
<field name="obj_nr" labelOnTop="0"/>
<field name="objectid" labelOnTop="0"/>
<field name="sort" labelOnTop="0"/>
<field name="text_1" labelOnTop="0"/>
<field name="typ" labelOnTop="0"/>
</labelOnTop>
<dataDefinedFieldProperties/>
<widgets/>
<previewExpression>"gml_id"</previewExpression>
<mapTip></mapTip>
<layerGeometryType>2</layerGeometryType>
</qgis>

225
styles/GIS_Biotope_F.qml Normal file
View File

@@ -0,0 +1,225 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="3.40.7-Bratislava" styleCategories="Symbology">
<renderer-v2 forceraster="0" enableorderby="0" referencescale="-1" type="singleSymbol" symbollevels="0">
<symbols>
<symbol alpha="1" clip_to_extent="1" frame_rate="10" type="fill" force_rhr="0" is_animated="0" name="0">
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
<layer enabled="1" pass="0" id="{ef62a644-15f0-4a1c-aa78-8ae94dc5749c}" class="SimpleFill" locked="0">
<Option type="Map">
<Option type="QString" name="border_width_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="color" value="0,0,255,255,rgb:0,0,1,1"/>
<Option type="QString" name="joinstyle" value="bevel"/>
<Option type="QString" name="offset" value="0,0"/>
<Option type="QString" name="offset_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="offset_unit" value="MM"/>
<Option type="QString" name="outline_color" value="89,89,89,255,rgb:0.34901960784313724,0.34901960784313724,0.34901960784313724,1"/>
<Option type="QString" name="outline_style" value="solid"/>
<Option type="QString" name="outline_width" value="0.4"/>
<Option type="QString" name="outline_width_unit" value="MM"/>
<Option type="QString" name="style" value="no"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
</layer>
<layer enabled="1" pass="0" id="{4b0f1a3b-14cf-4149-bf17-8f30fa594ef1}" class="LinePatternFill" locked="0">
<Option type="Map">
<Option type="QString" name="angle" value="0"/>
<Option type="QString" name="clip_mode" value="during_render"/>
<Option type="QString" name="color" value="255,0,0,255,rgb:1,0,0,1"/>
<Option type="QString" name="coordinate_reference" value="feature"/>
<Option type="QString" name="distance" value="4"/>
<Option type="QString" name="distance_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="distance_unit" value="MM"/>
<Option type="QString" name="line_width" value="0.25"/>
<Option type="QString" name="line_width_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="line_width_unit" value="Point"/>
<Option type="QString" name="offset" value="0"/>
<Option type="QString" name="offset_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="offset_unit" value="MM"/>
<Option type="QString" name="outline_width_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="outline_width_unit" value="Point"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
<symbol alpha="1" clip_to_extent="1" frame_rate="10" type="line" force_rhr="0" is_animated="0" name="@0@1">
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
<layer enabled="1" pass="0" id="{853c5350-e21e-49ba-a151-7de585769136}" class="SimpleLine" locked="0">
<Option type="Map">
<Option type="QString" name="align_dash_pattern" value="0"/>
<Option type="QString" name="capstyle" value="square"/>
<Option type="QString" name="customdash" value="5;2"/>
<Option type="QString" name="customdash_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="customdash_unit" value="MM"/>
<Option type="QString" name="dash_pattern_offset" value="0"/>
<Option type="QString" name="dash_pattern_offset_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="dash_pattern_offset_unit" value="MM"/>
<Option type="QString" name="draw_inside_polygon" value="0"/>
<Option type="QString" name="joinstyle" value="bevel"/>
<Option type="QString" name="line_color" value="0,0,0,255,rgb:0,0,0,1"/>
<Option type="QString" name="line_style" value="solid"/>
<Option type="QString" name="line_width" value="0.3"/>
<Option type="QString" name="line_width_unit" value="MM"/>
<Option type="QString" name="offset" value="0"/>
<Option type="QString" name="offset_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="offset_unit" value="MM"/>
<Option type="QString" name="ring_filter" value="0"/>
<Option type="QString" name="trim_distance_end" value="0"/>
<Option type="QString" name="trim_distance_end_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="trim_distance_end_unit" value="MM"/>
<Option type="QString" name="trim_distance_start" value="0"/>
<Option type="QString" name="trim_distance_start_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="trim_distance_start_unit" value="MM"/>
<Option type="QString" name="tweak_dash_pattern_on_corners" value="0"/>
<Option type="QString" name="use_custom_dash" value="0"/>
<Option type="QString" name="width_map_unit_scale" value="3x:0,0,0,0,0,0"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</layer>
<layer enabled="1" pass="0" id="{8675ccd2-9bf1-447f-9cc7-f95b932972f6}" class="MarkerLine" locked="0">
<Option type="Map">
<Option type="QString" name="average_angle_length" value="4"/>
<Option type="QString" name="average_angle_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="average_angle_unit" value="MM"/>
<Option type="QString" name="interval" value="5"/>
<Option type="QString" name="interval_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="interval_unit" value="MM"/>
<Option type="QString" name="offset" value="0"/>
<Option type="QString" name="offset_along_line" value="0"/>
<Option type="QString" name="offset_along_line_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="offset_along_line_unit" value="MM"/>
<Option type="QString" name="offset_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="offset_unit" value="MM"/>
<Option type="bool" name="place_on_every_part" value="true"/>
<Option type="QString" name="placements" value="Interval"/>
<Option type="QString" name="ring_filter" value="0"/>
<Option type="QString" name="rotate" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
<symbol alpha="1" clip_to_extent="1" frame_rate="10" type="marker" force_rhr="0" is_animated="0" name="@0@2">
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
<layer enabled="1" pass="0" id="{3260c32a-e9bb-45db-8ef8-3abb54ae868e}" class="SimpleMarker" locked="0">
<Option type="Map">
<Option type="QString" name="angle" value="270"/>
<Option type="QString" name="cap_style" value="square"/>
<Option type="QString" name="color" value="255,0,0,255,rgb:1,0,0,1"/>
<Option type="QString" name="horizontal_anchor_point" value="1"/>
<Option type="QString" name="joinstyle" value="bevel"/>
<Option type="QString" name="name" value="arrowhead"/>
<Option type="QString" name="offset" value="-0.29999999999999999,0"/>
<Option type="QString" name="offset_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="offset_unit" value="MM"/>
<Option type="QString" name="outline_color" value="89,89,89,255,rgb:0.34901960784313724,0.34901960784313724,0.34901960784313724,1"/>
<Option type="QString" name="outline_style" value="solid"/>
<Option type="QString" name="outline_width" value="0.3"/>
<Option type="QString" name="outline_width_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="outline_width_unit" value="MM"/>
<Option type="QString" name="scale_method" value="diameter"/>
<Option type="QString" name="size" value="1.5"/>
<Option type="QString" name="size_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="size_unit" value="MM"/>
<Option type="QString" name="vertical_anchor_point" value="1"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
<data-defined-properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data-defined-properties>
</renderer-v2>
<selection mode="Default">
<selectionColor invalid="1"/>
<selectionSymbol>
<symbol alpha="1" clip_to_extent="1" frame_rate="10" type="fill" force_rhr="0" is_animated="0" name="">
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
<layer enabled="1" pass="0" id="{fc42b18b-5949-4fd5-b614-0205e0f7d69c}" class="SimpleFill" locked="0">
<Option type="Map">
<Option type="QString" name="border_width_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="color" value="0,0,255,255,rgb:0,0,1,1"/>
<Option type="QString" name="joinstyle" value="bevel"/>
<Option type="QString" name="offset" value="0,0"/>
<Option type="QString" name="offset_map_unit_scale" value="3x:0,0,0,0,0,0"/>
<Option type="QString" name="offset_unit" value="MM"/>
<Option type="QString" name="outline_color" value="35,35,35,255,rgb:0.13725490196078433,0.13725490196078433,0.13725490196078433,1"/>
<Option type="QString" name="outline_style" value="solid"/>
<Option type="QString" name="outline_width" value="0.26"/>
<Option type="QString" name="outline_width_unit" value="MM"/>
<Option type="QString" name="style" value="solid"/>
</Option>
<data_defined_properties>
<Option type="Map">
<Option type="QString" name="name" value=""/>
<Option name="properties"/>
<Option type="QString" name="type" value="collection"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</selectionSymbol>
</selection>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerGeometryType>2</layerGeometryType>
</qgis>

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>

371
styles/GIS_LfULG_LSG.qml Normal file
View File

@@ -0,0 +1,371 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis styleCategories="Symbology" version="3.40.7-Bratislava">
<renderer-v2 type="singleSymbol" forceraster="0" enableorderby="0" symbollevels="0" referencescale="-1">
<symbols>
<symbol type="fill" force_rhr="0" is_animated="0" frame_rate="10" name="0" alpha="1" clip_to_extent="1">
<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 class="MarkerLine" id="{edf1fe5f-96c0-427c-ac7b-b04c95e0da9c}" enabled="1" locked="0" pass="0">
<Option type="Map">
<Option type="QString" value="4" name="average_angle_length"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="average_angle_map_unit_scale"/>
<Option type="QString" value="MM" name="average_angle_unit"/>
<Option type="QString" value="5" name="interval"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="interval_map_unit_scale"/>
<Option type="QString" value="MM" name="interval_unit"/>
<Option type="QString" value="0" name="offset"/>
<Option type="QString" value="0" name="offset_along_line"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="offset_along_line_map_unit_scale"/>
<Option type="QString" value="MM" name="offset_along_line_unit"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="offset_map_unit_scale"/>
<Option type="QString" value="Pixel" name="offset_unit"/>
<Option type="bool" value="true" name="place_on_every_part"/>
<Option type="QString" value="Interval" name="placements"/>
<Option type="QString" value="1" name="ring_filter"/>
<Option type="QString" value="1" name="rotate"/>
</Option>
<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>
<symbol type="marker" force_rhr="0" is_animated="0" frame_rate="10" name="@0@0" alpha="1" clip_to_extent="1">
<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 class="SimpleMarker" id="{857cb357-9b68-40ea-9e9f-edbdbd7de638}" enabled="1" locked="0" pass="0">
<Option type="Map">
<Option type="QString" value="90" name="angle"/>
<Option type="QString" value="square" name="cap_style"/>
<Option type="QString" value="255,0,0,255,rgb:1,0,0,1" name="color"/>
<Option type="QString" value="1" name="horizontal_anchor_point"/>
<Option type="QString" value="bevel" name="joinstyle"/>
<Option type="QString" value="line" name="name"/>
<Option type="QString" value="1.99999999999999978,-1.5" 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="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" name="outline_color"/>
<Option type="QString" value="solid" name="outline_style"/>
<Option type="QString" value="0.5" name="outline_width"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="outline_width_map_unit_scale"/>
<Option type="QString" value="MM" name="outline_width_unit"/>
<Option type="QString" value="diameter" name="scale_method"/>
<Option type="QString" value="3" name="size"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="size_map_unit_scale"/>
<Option type="QString" value="MM" name="size_unit"/>
<Option type="QString" value="1" name="vertical_anchor_point"/>
</Option>
<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>
<layer class="SimpleMarker" id="{8da6ec0d-bd1d-4942-8241-be9e412a4ecd}" enabled="1" locked="0" pass="0">
<Option type="Map">
<Option type="QString" value="0" name="angle"/>
<Option type="QString" value="square" name="cap_style"/>
<Option type="QString" value="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" name="color"/>
<Option type="QString" value="1" name="horizontal_anchor_point"/>
<Option type="QString" value="bevel" name="joinstyle"/>
<Option type="QString" value="line" name="name"/>
<Option type="QString" value="3,1" 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="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" name="outline_color"/>
<Option type="QString" value="solid" name="outline_style"/>
<Option type="QString" value="0.5" name="outline_width"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="outline_width_map_unit_scale"/>
<Option type="QString" value="MM" name="outline_width_unit"/>
<Option type="QString" value="diameter" name="scale_method"/>
<Option type="QString" value="2" name="size"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="size_map_unit_scale"/>
<Option type="QString" value="MM" name="size_unit"/>
<Option type="QString" value="1" name="vertical_anchor_point"/>
</Option>
<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>
<layer class="SimpleMarker" id="{cf06f0f3-27d8-4b64-b56f-6bda0ed8508c}" enabled="1" locked="0" pass="0">
<Option type="Map">
<Option type="QString" value="0" name="angle"/>
<Option type="QString" value="square" name="cap_style"/>
<Option type="QString" value="255,0,0,255,rgb:1,0,0,1" name="color"/>
<Option type="QString" value="1" name="horizontal_anchor_point"/>
<Option type="QString" value="bevel" name="joinstyle"/>
<Option type="QString" value="line" name="name"/>
<Option type="QString" value="2,1" 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="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" name="outline_color"/>
<Option type="QString" value="solid" name="outline_style"/>
<Option type="QString" value="0.5" name="outline_width"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="outline_width_map_unit_scale"/>
<Option type="QString" value="MM" name="outline_width_unit"/>
<Option type="QString" value="diameter" name="scale_method"/>
<Option type="QString" value="2" name="size"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="size_map_unit_scale"/>
<Option type="QString" value="MM" name="size_unit"/>
<Option type="QString" value="1" name="vertical_anchor_point"/>
</Option>
<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>
<layer class="SimpleMarker" id="{0f1b9591-1b5b-4e8a-8353-c0a7caaf7b21}" enabled="1" locked="0" pass="0">
<Option type="Map">
<Option type="QString" value="0" name="angle"/>
<Option type="QString" value="square" name="cap_style"/>
<Option type="QString" value="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" name="color"/>
<Option type="QString" value="1" name="horizontal_anchor_point"/>
<Option type="QString" value="bevel" name="joinstyle"/>
<Option type="QString" value="line" name="name"/>
<Option type="QString" value="1,1" 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="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" name="outline_color"/>
<Option type="QString" value="solid" name="outline_style"/>
<Option type="QString" value="0.5" name="outline_width"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="outline_width_map_unit_scale"/>
<Option type="QString" value="MM" name="outline_width_unit"/>
<Option type="QString" value="diameter" name="scale_method"/>
<Option type="QString" value="2" name="size"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="size_map_unit_scale"/>
<Option type="QString" value="MM" name="size_unit"/>
<Option type="QString" value="1" name="vertical_anchor_point"/>
</Option>
<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>
<layer class="SimpleMarker" id="{c9ae8a84-1a7d-4599-b27d-37bc1484098b}" enabled="1" locked="0" pass="0">
<Option type="Map">
<Option type="QString" value="0" name="angle"/>
<Option type="QString" value="square" name="cap_style"/>
<Option type="QString" value="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" name="color"/>
<Option type="QString" value="1" name="horizontal_anchor_point"/>
<Option type="QString" value="bevel" name="joinstyle"/>
<Option type="QString" value="line" name="name"/>
<Option type="QString" value="0,1" 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="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,1" name="outline_color"/>
<Option type="QString" value="solid" name="outline_style"/>
<Option type="QString" value="0.5" name="outline_width"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="outline_width_map_unit_scale"/>
<Option type="QString" value="MM" name="outline_width_unit"/>
<Option type="QString" value="diameter" name="scale_method"/>
<Option type="QString" value="2" name="size"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="size_map_unit_scale"/>
<Option type="QString" value="MM" name="size_unit"/>
<Option type="QString" value="1" name="vertical_anchor_point"/>
</Option>
<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>
</symbol>
</layer>
<layer class="SimpleLine" id="{fd1fd75a-85c6-4ace-a1bc-300a87cb1bcf}" enabled="1" locked="0" 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="bevel" name="joinstyle"/>
<Option type="QString" value="128,128,128,255,rgb:0.50196078431372548,0.50196078431372548,0.50196078431372548,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>
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
</Option>
</data_defined_properties>
</layer>
<layer class="CentroidFill" id="{801668fb-6226-4c79-a72a-ec430781b326}" enabled="1" locked="0" pass="0">
<Option type="Map">
<Option type="QString" value="1" name="clip_on_current_part_only"/>
<Option type="QString" value="1" name="clip_points"/>
<Option type="QString" value="1" name="point_on_all_parts"/>
<Option type="QString" value="1" name="point_on_surface"/>
</Option>
<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>
<symbol type="marker" force_rhr="0" is_animated="0" frame_rate="10" name="@0@2" alpha="1" clip_to_extent="1">
<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 class="SimpleMarker" id="{d075cd2b-6c94-49c8-b6a6-61d75f00fbc5}" enabled="1" locked="0" pass="0">
<Option type="Map">
<Option type="QString" value="0" name="angle"/>
<Option type="QString" value="square" name="cap_style"/>
<Option type="QString" value="255,0,0,0,rgb:1,0,0,0" name="color"/>
<Option type="QString" value="1" name="horizontal_anchor_point"/>
<Option type="QString" value="bevel" name="joinstyle"/>
<Option type="QString" value="circle" name="name"/>
<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="MapUnit" name="offset_unit"/>
<Option type="QString" value="0,0,0,255,rgb:0,0,0,1" name="outline_color"/>
<Option type="QString" value="solid" name="outline_style"/>
<Option type="QString" value="0.5" name="outline_width"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="outline_width_map_unit_scale"/>
<Option type="QString" value="MM" name="outline_width_unit"/>
<Option type="QString" value="diameter" name="scale_method"/>
<Option type="QString" value="15" name="size"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="size_map_unit_scale"/>
<Option type="QString" value="MM" name="size_unit"/>
<Option type="QString" value="1" name="vertical_anchor_point"/>
</Option>
<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>
<layer class="FontMarker" id="{d6023cfc-3bfd-46de-9557-e37b755164f3}" enabled="1" locked="0" pass="0">
<Option type="Map">
<Option type="QString" value="0" name="angle"/>
<Option type="QString" value="L" name="chr"/>
<Option type="QString" value="0,0,0,255,rgb:0,0,0,1" name="color"/>
<Option type="QString" value="Arial" name="font"/>
<Option type="QString" value="Standard" name="font_style"/>
<Option type="QString" value="1" name="horizontal_anchor_point"/>
<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="MapUnit" 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="0" name="outline_width"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="outline_width_map_unit_scale"/>
<Option type="QString" value="MM" name="outline_width_unit"/>
<Option type="QString" value="5" name="size"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="size_map_unit_scale"/>
<Option type="QString" value="MM" name="size_unit"/>
<Option type="QString" value="1" name="vertical_anchor_point"/>
</Option>
<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>
</symbol>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
<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>
</renderer-v2>
<selection mode="Default">
<selectionColor invalid="1"/>
<selectionSymbol>
<symbol type="fill" force_rhr="0" is_animated="0" frame_rate="10" name="" alpha="1" clip_to_extent="1">
<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 class="SimpleFill" id="{790c0e9c-4587-46ac-845d-d8d4c15b81b7}" enabled="1" 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>
<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>
</symbol>
</selectionSymbol>
</selection>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerGeometryType>2</layerGeometryType>
</qgis>

140
styles/verfahrensgebiet.qml Normal file
View File

@@ -0,0 +1,140 @@
<!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>
<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>
<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>
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
<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>
</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="">
<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="{f18003f5-220a-487f-8a6d-b24facc4c1a5}" enabled="1" class="SimpleFill" 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>
<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>
</symbol>
</selectionSymbol>
</selection>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerGeometryType>2</layerGeometryType>
</qgis>

1
tests/__init__.py Normal file
View File

@@ -0,0 +1 @@
#Testordner

154
tests/run_tests.py Normal file
View File

@@ -0,0 +1,154 @@
"""
sn_basis/test/run_tests.py
Zentraler Test-Runner für sn_basis.
Wrapper-konform, QGIS-unabhängig, CI- und IDE-fähig.
"""
import unittest
import datetime
import inspect
import os
import sys
from pathlib import Path
# ---------------------------------------------------------
# Pre-Bootstrap: Plugin-Root in sys.path eintragen
# ---------------------------------------------------------
THIS_FILE = Path(__file__).resolve()
PLUGIN_ROOT = THIS_FILE.parents[2]
if str(PLUGIN_ROOT) not in sys.path:
sys.path.insert(0, str(PLUGIN_ROOT))
from sn_basis.functions import (
get_plugin_root,
add_to_sys_path,
)
# ---------------------------------------------------------
# Bootstrap: Plugin-Root in sys.path eintragen
# ---------------------------------------------------------
def bootstrap():
"""
Simuliert das QGIS-Plugin-Startverhalten:
stellt sicher, dass sn_basis importierbar ist.
"""
plugin_root = get_plugin_root()
add_to_sys_path(plugin_root)
bootstrap()
# ---------------------------------------------------------
# Farben
# ---------------------------------------------------------
RED = "\033[91m"
YELLOW = "\033[93m"
GREEN = "\033[92m"
CYAN = "\033[96m"
MAGENTA = "\033[95m"
RESET = "\033[0m"
GLOBAL_TEST_COUNTER = 0
# ---------------------------------------------------------
# Farbige TestResult-Klasse
# ---------------------------------------------------------
class ColoredTestResult(unittest.TextTestResult):
_last_test_class: type | None = None
def startTest(self, test):
global GLOBAL_TEST_COUNTER
GLOBAL_TEST_COUNTER += 1
self.stream.write(f"{CYAN}[Test {GLOBAL_TEST_COUNTER}]{RESET}\n")
super().startTest(test)
def startTestClass(self, test):
cls = test.__class__
file = inspect.getfile(cls)
filename = os.path.basename(file)
self.stream.write(
f"\n{MAGENTA}{'=' * 70}\n"
f"Starte Testklasse: {filename}{cls.__name__}\n"
f"{'=' * 70}{RESET}\n"
)
def addError(self, test, err):
super().addError(test, err)
self.stream.write(f"{RED}ERROR{RESET}\n")
def addFailure(self, test, err):
super().addFailure(test, err)
self.stream.write(f"{RED}FAILURE{RESET}\n")
def addSkip(self, test, reason):
super().addSkip(test, reason)
self.stream.write(f"{YELLOW}SKIPPED{RESET}: {reason}\n")
def addSuccess(self, test):
super().addSuccess(test)
self.stream.write(f"{GREEN}OK{RESET}\n")
# ---------------------------------------------------------
# Farbiger TestRunner
# ---------------------------------------------------------
class ColoredTestRunner(unittest.TextTestRunner):
def _makeResult(self):
result = ColoredTestResult(
self.stream,
self.descriptions,
self.verbosity,
)
original_start_test = result.startTest
def patched_start_test(test):
if not hasattr(result, "_last_test_class") or \
result._last_test_class != test.__class__:
result.startTestClass(test)
result._last_test_class = test.__class__
original_start_test(test)
result.startTest = patched_start_test
return result
# ---------------------------------------------------------
# Testlauf starten
# ---------------------------------------------------------
def main():
print("\n" + "=" * 70)
print(
f"{CYAN}Testlauf gestartet am: "
f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S}{RESET}"
)
print("=" * 70 + "\n")
loader = unittest.TestLoader()
suite = loader.discover(
start_dir=os.path.dirname(__file__),
pattern="test_*.py"
)
runner = ColoredTestRunner(verbosity=2)
result = runner.run(suite)
# Exit-Code für CI / Skripte
return 0 if result.wasSuccessful() else 1
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,9 @@
@echo off
SET OSGEO4W_ROOT=D:\QGISQT5
call %OSGEO4W_ROOT%\bin\o4w_env.bat
set QGIS_PREFIX_PATH=%OSGEO4W_ROOT%\apps\qgis
set PYTHONPATH=%QGIS_PREFIX_PATH%\python;%PYTHONPATH%
set PATH=%OSGEO4W_ROOT%\bin;%QGIS_PREFIX_PATH%\bin;%PATH%
REM Neue Eingabeaufforderung starten und Python-Skript ausführen
start cmd /k "python run_tests.py"

2
tests/test_bootstrap.py Normal file
View File

@@ -0,0 +1,2 @@
from sn_basis.functions import sys_wrapper
sys_wrapper.add_to_sys_path(sys_wrapper.get_plugin_root())

104
tests/test_dateipruefer.py Normal file
View File

@@ -0,0 +1,104 @@
# sn_basis/test/test_dateipruefer.py
import unittest
from pathlib import Path
from unittest.mock import patch
from sn_basis.modules.Dateipruefer import Dateipruefer
class TestDateipruefer(unittest.TestCase):
# -----------------------------------------------------
# 1. Leere Eingabe erlaubt
# -----------------------------------------------------
def test_leereingabe_erlaubt(self):
pruefer = Dateipruefer(
pfad="",
leereingabe_erlaubt=True
)
result = pruefer.pruefe()
self.assertFalse(result.ok)
self.assertEqual(result.aktion, "leereingabe_erlaubt")
self.assertIsNone(result.kontext)
# -----------------------------------------------------
# 2. Leere Eingabe nicht erlaubt
# -----------------------------------------------------
def test_leereingabe_nicht_erlaubt(self):
pruefer = Dateipruefer(
pfad="",
leereingabe_erlaubt=False
)
result = pruefer.pruefe()
self.assertFalse(result.ok)
self.assertEqual(result.aktion, "leereingabe_nicht_erlaubt")
self.assertIsNone(result.kontext)
# -----------------------------------------------------
# 3. Standarddatei vorschlagen
# -----------------------------------------------------
def test_standarddatei_vorschlagen(self):
pruefer = Dateipruefer(
pfad="",
standarddatei="/tmp/std.txt"
)
result = pruefer.pruefe()
self.assertFalse(result.ok)
self.assertEqual(result.aktion, "standarddatei_vorschlagen")
self.assertEqual(result.kontext, Path("/tmp/std.txt"))
# -----------------------------------------------------
# 4. Temporäre Datei erlaubt
# -----------------------------------------------------
def test_temporaer_erlaubt(self):
pruefer = Dateipruefer(
pfad="",
temporaer_erlaubt=True
)
result = pruefer.pruefe()
self.assertFalse(result.ok)
self.assertEqual(result.aktion, "temporaer_erlaubt")
self.assertIsNone(result.kontext)
# -----------------------------------------------------
# 5. Datei existiert nicht
# -----------------------------------------------------
@patch("sn_basis.modules.Dateipruefer.file_exists", return_value=False)
def test_datei_nicht_gefunden(self, mock_exists):
pruefer = Dateipruefer(
pfad="/tmp/nichtvorhanden.txt"
)
result = pruefer.pruefe()
self.assertFalse(result.ok)
self.assertEqual(result.aktion, "datei_nicht_gefunden")
self.assertEqual(result.kontext, Path("/tmp/nichtvorhanden.txt"))
# -----------------------------------------------------
# 6. Datei existiert
# -----------------------------------------------------
@patch("sn_basis.modules.Dateipruefer.file_exists", return_value=True)
def test_datei_ok(self, mock_exists):
pruefer = Dateipruefer(
pfad="/tmp/test.txt"
)
result = pruefer.pruefe()
self.assertTrue(result.ok)
self.assertEqual(result.aktion, "ok")
self.assertEqual(result.kontext, Path("/tmp/test.txt"))
if __name__ == "__main__":
unittest.main()

171
tests/test_layerpruefer.py Normal file
View File

@@ -0,0 +1,171 @@
# sn_basis/test/test_layerpruefer.py
import unittest
from sn_basis.modules.layerpruefer import Layerpruefer
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
# ---------------------------------------------------------
# Mock-Layer für Wrapper-Tests
# ---------------------------------------------------------
class MockLayer:
def __init__(
self,
exists=True,
visible=True,
layer_type="vector",
geometry_type="Polygon",
feature_count=10,
crs="EPSG:25833",
fields=None,
source="/tmp/test.shp",
editable=True,
):
self.exists = exists
self.visible = visible
self.layer_type = layer_type
self.geometry_type = geometry_type
self.feature_count = feature_count
self.crs = crs
self.fields = fields or []
self.source = source
self.editable = editable
# ---------------------------------------------------------
# Wrapper-Mocks (monkeypatching)
# ---------------------------------------------------------
def mock_layer_exists(layer):
return layer is not None and layer.exists
def mock_is_layer_visible(layer):
return layer.visible
def mock_get_layer_type(layer):
return layer.layer_type
def mock_get_layer_geometry_type(layer):
return layer.geometry_type
def mock_get_layer_feature_count(layer):
return layer.feature_count
def mock_get_layer_crs(layer):
return layer.crs
def mock_get_layer_fields(layer):
return layer.fields
def mock_get_layer_source(layer):
return layer.source
def mock_is_layer_editable(layer):
return layer.editable
# ---------------------------------------------------------
# Testklasse
# ---------------------------------------------------------
class TestLayerpruefer(unittest.TestCase):
def setUp(self):
# Monkeypatching der im Layerpruefer verwendeten Wrapper-Funktionen
import sn_basis.modules.layerpruefer as module
module.layer_exists = mock_layer_exists
module.is_layer_visible = mock_is_layer_visible
module.get_layer_type = mock_get_layer_type
module.get_layer_geometry_type = mock_get_layer_geometry_type
module.get_layer_feature_count = mock_get_layer_feature_count
module.get_layer_crs = mock_get_layer_crs
module.get_layer_fields = mock_get_layer_fields
module.get_layer_source = mock_get_layer_source
module.is_layer_editable = mock_is_layer_editable
# -----------------------------------------------------
# Tests
# -----------------------------------------------------
def test_layer_exists(self):
layer = MockLayer(exists=False)
pruefer = Layerpruefer(layer)
ergebnis = pruefer.pruefe()
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "layer_nicht_gefunden")
def test_layer_unsichtbar(self):
layer = MockLayer(visible=False)
pruefer = Layerpruefer(layer, muss_sichtbar_sein=True)
ergebnis = pruefer.pruefe()
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "layer_unsichtbar")
def test_falscher_layertyp(self):
layer = MockLayer(layer_type="raster")
pruefer = Layerpruefer(layer, erwarteter_layertyp="vector")
ergebnis = pruefer.pruefe()
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "falscher_layertyp")
def test_falscher_geotyp(self):
layer = MockLayer(geometry_type="Point")
pruefer = Layerpruefer(layer, erwarteter_geotyp="Polygon")
ergebnis = pruefer.pruefe()
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "falscher_geotyp")
def test_layer_leer(self):
layer = MockLayer(feature_count=0)
pruefer = Layerpruefer(layer)
ergebnis = pruefer.pruefe()
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "layer_leer")
def test_falsches_crs(self):
layer = MockLayer(crs="EPSG:4326")
pruefer = Layerpruefer(layer, erwartetes_crs="EPSG:25833")
ergebnis = pruefer.pruefe()
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "falsches_crs")
def test_felder_fehlen(self):
layer = MockLayer(fields=["id"])
pruefer = Layerpruefer(layer, erforderliche_felder=["id", "name"])
ergebnis = pruefer.pruefe()
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "felder_fehlen")
def test_datenquelle_unerwartet(self):
layer = MockLayer(source="/tmp/test.shp")
pruefer = Layerpruefer(layer, erlaubte_datenquellen=["/tmp/allowed.shp"])
ergebnis = pruefer.pruefe()
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "datenquelle_unerwartet")
def test_layer_nicht_editierbar(self):
layer = MockLayer(editable=False)
pruefer = Layerpruefer(layer, muss_editierbar_sein=True)
ergebnis = pruefer.pruefe()
self.assertFalse(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "layer_nicht_editierbar")
def test_layer_ok(self):
layer = MockLayer()
pruefer = Layerpruefer(layer)
ergebnis = pruefer.pruefe()
self.assertTrue(ergebnis.ok)
self.assertEqual(ergebnis.aktion, "ok")
if __name__ == "__main__":
unittest.main()

79
tests/test_linkpruefer.py Normal file
View File

@@ -0,0 +1,79 @@
# sn_basis/test/test_linkpruefer.py
import unittest
from pathlib import Path
from unittest.mock import patch
from sn_basis.modules.linkpruefer import Linkpruefer
from sn_basis.functions.qgiscore_wrapper import NetworkReply
class TestLinkpruefer(unittest.TestCase):
# -----------------------------------------------------
# 1. Remote-Link erreichbar
# -----------------------------------------------------
@patch("sn_basis.modules.linkpruefer.network_head")
def test_remote_link_ok(self, mock_head):
mock_head.return_value = NetworkReply(error=0)
lp = Linkpruefer()
result = lp.pruefe("http://example.com")
self.assertTrue(result.ok)
self.assertEqual(result.aktion, "ok")
self.assertEqual(result.kontext, "http://example.com")
# -----------------------------------------------------
# 2. Remote-Link nicht erreichbar
# -----------------------------------------------------
@patch("sn_basis.modules.linkpruefer.network_head")
def test_remote_link_error(self, mock_head):
mock_head.return_value = NetworkReply(error=1)
lp = Linkpruefer()
result = lp.pruefe("http://example.com")
self.assertFalse(result.ok)
self.assertEqual(result.aktion, "url_nicht_erreichbar")
self.assertEqual(result.kontext, "http://example.com")
# -----------------------------------------------------
# 3. Netzwerkfehler (None)
# -----------------------------------------------------
@patch("sn_basis.modules.linkpruefer.network_head", return_value=None)
def test_remote_link_network_error(self, mock_head):
lp = Linkpruefer()
result = lp.pruefe("http://example.com")
self.assertFalse(result.ok)
self.assertEqual(result.aktion, "netzwerkfehler")
self.assertEqual(result.kontext, "http://example.com")
# -----------------------------------------------------
# 4. Lokaler Pfad existiert nicht
# -----------------------------------------------------
@patch("sn_basis.modules.linkpruefer.file_exists", return_value=False)
def test_local_link_not_found(self, mock_exists):
lp = Linkpruefer()
result = lp.pruefe("/path/to/missing/file.shp")
self.assertFalse(result.ok)
self.assertEqual(result.aktion, "pfad_nicht_gefunden")
self.assertEqual(result.kontext, Path("/path/to/missing/file.shp"))
# -----------------------------------------------------
# 5. Lokaler Pfad existiert
# -----------------------------------------------------
@patch("sn_basis.modules.linkpruefer.file_exists", return_value=True)
def test_local_link_ok(self, mock_exists):
lp = Linkpruefer()
result = lp.pruefe("/path/to/file.shp")
self.assertTrue(result.ok)
self.assertEqual(result.aktion, "ok")
self.assertEqual(result.kontext, Path("/path/to/file.shp"))
if __name__ == "__main__":
unittest.main()

146
tests/test_pruefmanager.py Normal file
View File

@@ -0,0 +1,146 @@
# sn_basis/test/test_pruefmanager.py
import unittest
from unittest.mock import patch
from sn_basis.modules.Pruefmanager import Pruefmanager
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
class TestPruefmanager(unittest.TestCase):
def setUp(self):
self.manager = Pruefmanager()
# -----------------------------------------------------
# 1. OK-Ergebnis → keine Interaktion
# -----------------------------------------------------
def test_ok(self):
ergebnis = pruef_ergebnis(True, "Alles gut", "ok", None)
entscheidung = self.manager.verarbeite(ergebnis)
self.assertTrue(entscheidung.ok)
self.assertEqual(entscheidung.aktion, "ok")
# -----------------------------------------------------
# 2. Leere Eingabe erlaubt → Nutzer sagt JA
# -----------------------------------------------------
@patch("sn_basis.modules.Pruefmanager.ask_yes_no", return_value=True)
def test_leereingabe_erlaubt_ja(self, mock_ask):
ergebnis = pruef_ergebnis(False, "Leer?", "leereingabe_erlaubt", None)
entscheidung = self.manager.verarbeite(ergebnis)
self.assertTrue(entscheidung.ok)
self.assertEqual(entscheidung.aktion, "ok")
# -----------------------------------------------------
# 3. Leere Eingabe erlaubt → Nutzer sagt NEIN
# -----------------------------------------------------
@patch("sn_basis.modules.Pruefmanager.ask_yes_no", return_value=False)
def test_leereingabe_erlaubt_nein(self, mock_ask):
ergebnis = pruef_ergebnis(False, "Leer?", "leereingabe_erlaubt", None)
entscheidung = self.manager.verarbeite(ergebnis)
self.assertFalse(entscheidung.ok)
self.assertEqual(entscheidung.aktion, "leereingabe_erlaubt")
# -----------------------------------------------------
# 4. Standarddatei vorschlagen → Nutzer sagt JA
# -----------------------------------------------------
@patch("sn_basis.modules.Pruefmanager.ask_yes_no", return_value=True)
def test_standarddatei_vorschlagen_ja(self, mock_ask):
ergebnis = pruef_ergebnis(
False,
"Standarddatei verwenden?",
"standarddatei_vorschlagen",
"/tmp/std.txt",
)
entscheidung = self.manager.verarbeite(ergebnis)
self.assertTrue(entscheidung.ok)
self.assertEqual(entscheidung.aktion, "ok")
self.assertEqual(entscheidung.kontext, "/tmp/std.txt")
# -----------------------------------------------------
# 5. Standarddatei vorschlagen → Nutzer sagt NEIN
# -----------------------------------------------------
@patch("sn_basis.modules.Pruefmanager.ask_yes_no", return_value=False)
def test_standarddatei_vorschlagen_nein(self, mock_ask):
ergebnis = pruef_ergebnis(
False,
"Standarddatei verwenden?",
"standarddatei_vorschlagen",
"/tmp/std.txt",
)
entscheidung = self.manager.verarbeite(ergebnis)
self.assertFalse(entscheidung.ok)
self.assertEqual(entscheidung.aktion, "standarddatei_vorschlagen")
# -----------------------------------------------------
# 6. Temporäre Datei erzeugen → Nutzer sagt JA
# -----------------------------------------------------
@patch("sn_basis.modules.Pruefmanager.ask_yes_no", return_value=True)
def test_temporaer_erlaubt_ja(self, mock_ask):
ergebnis = pruef_ergebnis(False, "Temporär?", "temporaer_erlaubt", None)
entscheidung = self.manager.verarbeite(ergebnis)
self.assertTrue(entscheidung.ok)
self.assertEqual(entscheidung.aktion, "temporaer_erzeugen")
# -----------------------------------------------------
# 7. Temporäre Datei erzeugen → Nutzer sagt NEIN
# -----------------------------------------------------
@patch("sn_basis.modules.Pruefmanager.ask_yes_no", return_value=False)
def test_temporaer_erlaubt_nein(self, mock_ask):
ergebnis = pruef_ergebnis(False, "Temporär?", "temporaer_erlaubt", None)
entscheidung = self.manager.verarbeite(ergebnis)
self.assertFalse(entscheidung.ok)
self.assertEqual(entscheidung.aktion, "temporaer_erlaubt")
# -----------------------------------------------------
# 8. Layer unsichtbar → Nutzer sagt JA
# -----------------------------------------------------
@patch("sn_basis.modules.Pruefmanager.ask_yes_no", return_value=True)
@patch("sn_basis.modules.Pruefmanager.set_layer_visible")
def test_layer_unsichtbar_ja(self, mock_set, mock_ask):
fake_layer = object()
ergebnis = pruef_ergebnis(False, "Layer unsichtbar", "layer_unsichtbar", fake_layer)
entscheidung = self.manager.verarbeite(ergebnis)
mock_set.assert_called_once_with(fake_layer, True)
self.assertTrue(entscheidung.ok)
self.assertEqual(entscheidung.aktion, "ok")
# -----------------------------------------------------
# 9. Layer unsichtbar → Nutzer sagt NEIN
# -----------------------------------------------------
@patch("sn_basis.modules.Pruefmanager.ask_yes_no", return_value=False)
def test_layer_unsichtbar_nein(self, mock_ask):
fake_layer = object()
ergebnis = pruef_ergebnis(False, "Layer unsichtbar", "layer_unsichtbar", fake_layer)
entscheidung = self.manager.verarbeite(ergebnis)
self.assertFalse(entscheidung.ok)
self.assertEqual(entscheidung.aktion, "layer_unsichtbar")
# -----------------------------------------------------
# 10. Fehlerhafte Aktion → Fallback
# -----------------------------------------------------
@patch("sn_basis.modules.Pruefmanager.warning")
def test_unbekannte_aktion(self, mock_warn):
ergebnis = pruef_ergebnis(False, "???", "unbekannt", None)
entscheidung = self.manager.verarbeite(ergebnis)
mock_warn.assert_called_once()
self.assertFalse(entscheidung.ok)
self.assertEqual(entscheidung.aktion, "unbekannt")
if __name__ == "__main__":
unittest.main()

52
tests/test_qgis.bat Normal file
View File

@@ -0,0 +1,52 @@
@echo off
setlocal
echo BATCH WIRD AUSGEFÜHRT
pause
echo ================================================
echo Starte Tests in QGIS-Python-Umgebung
echo ================================================
REM Pfad zur QGIS-Installation
set QGIS_BIN=D:\OSGeo\bin
REM Prüfen, ob python-qgis.bat existiert
if not exist "%QGIS_BIN%\python-qgis.bat" (
echo.
echo [FEHLER] python-qgis.bat wurde nicht gefunden!
echo Erwarteter Pfad:
echo %QGIS_BIN%\python-qgis.bat
echo.
echo Bitte korrigiere den Pfad in test_qgis.bat.
echo.
pause
exit /b 1
)
echo.
echo [INFO] QGIS-Python gefunden. Starte Tests...
echo.
"%QGIS_BIN%\python-qgis.bat" -m coverage run run_tests.py
if errorlevel 1 (
echo.
echo [FEHLER] Testlauf fehlgeschlagen.
echo.
pause
exit /b 1
)
echo.
echo ================================================
echo Coverage HTML-Bericht wird erzeugt...
echo ================================================
"%QGIS_BIN%\python-qgis.bat" -m coverage html
echo.
echo Fertig!
echo Öffne jetzt: coverage_html\index.html
echo ================================================
pause
endlocal

View File

@@ -0,0 +1,60 @@
# sn_basis/test/test_settings_logic.py
import unittest
from unittest.mock import patch
from sn_basis.functions.settings_logic import SettingsLogic
class TestSettingsLogic(unittest.TestCase):
# -----------------------------------------------------
# Test: load() liest alle Variablen über get_variable()
# -----------------------------------------------------
@patch("sn_basis.functions.settings_logic.get_variable")
def test_load(self, mock_get):
# Mock-Rückgabe für jede Variable
mock_get.side_effect = lambda key, scope="project": f"wert_{key}"
logic = SettingsLogic()
daten = logic.load()
# Alle Variablen müssen enthalten sein
for key in SettingsLogic.VARIABLEN:
self.assertIn(key, daten)
self.assertEqual(daten[key], f"wert_{key}")
# get_variable muss für jede Variable genau einmal aufgerufen werden
self.assertEqual(mock_get.call_count, len(SettingsLogic.VARIABLEN))
# -----------------------------------------------------
# Test: save() ruft set_variable() nur für bekannte Keys auf
# -----------------------------------------------------
@patch("sn_basis.functions.settings_logic.set_variable")
def test_save(self, mock_set):
logic = SettingsLogic()
# Eingabedaten enthalten gültige und ungültige Keys
daten = {
"amt": "A1",
"behoerde": "B1",
"unbekannt": "IGNORIEREN",
"gemeinden": "G1",
}
logic.save(daten)
# set_variable muss nur für gültige Keys aufgerufen werden
expected_calls = 3 # amt, behoerde, gemeinden
self.assertEqual(mock_set.call_count, expected_calls)
# Prüfen, ob die richtigen Keys gespeichert wurden
saved_keys = [call.args[0] for call in mock_set.call_args_list]
self.assertIn("amt", saved_keys)
self.assertIn("behoerde", saved_keys)
self.assertIn("gemeinden", saved_keys)
self.assertNotIn("unbekannt", saved_keys)
if __name__ == "__main__":
unittest.main()

81
tests/test_stilpruefer.py Normal file
View File

@@ -0,0 +1,81 @@
# sn_basis/test/test_stilpruefer.py
import unittest
import tempfile
import os
from pathlib import Path
from unittest.mock import patch
from sn_basis.modules.stilpruefer import Stilpruefer
class TestStilpruefer(unittest.TestCase):
def setUp(self):
self.pruefer = Stilpruefer()
# -----------------------------------------------------
# 1. Keine Datei angegeben
# -----------------------------------------------------
def test_keine_datei_angegeben(self):
result = self.pruefer.pruefe("")
self.assertTrue(result.ok)
self.assertEqual(result.aktion, "ok")
self.assertIn("Kein Stil angegeben", result.meldung)
self.assertIsNone(result.kontext)
# -----------------------------------------------------
# 2. Datei existiert und ist .qml
# -----------------------------------------------------
@patch("sn_basis.modules.stilpruefer.file_exists", return_value=True)
def test_datei_existiert_mit_qml(self, mock_exists):
with tempfile.NamedTemporaryFile(suffix=".qml", delete=False) as tmp:
tmp_path = tmp.name
try:
result = self.pruefer.pruefe(tmp_path)
self.assertTrue(result.ok)
self.assertEqual(result.aktion, "stil_anwendbar")
self.assertEqual(result.kontext, Path(tmp_path))
finally:
os.remove(tmp_path)
# -----------------------------------------------------
# 3. Datei existiert, aber falsche Endung
# -----------------------------------------------------
@patch("sn_basis.modules.stilpruefer.file_exists", return_value=True)
def test_datei_existiert_falsche_endung(self, mock_exists):
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as tmp:
tmp_path = tmp.name
try:
result = self.pruefer.pruefe(tmp_path)
self.assertFalse(result.ok)
self.assertEqual(result.aktion, "falsche_endung")
self.assertIn(".qml", result.meldung)
self.assertEqual(result.kontext, Path(tmp_path))
finally:
os.remove(tmp_path)
# -----------------------------------------------------
# 4. Datei existiert nicht
# -----------------------------------------------------
@patch("sn_basis.modules.stilpruefer.file_exists", return_value=False)
def test_datei_existiert_nicht(self, mock_exists):
fake_path = "/tmp/nichtvorhanden.qml"
result = self.pruefer.pruefe(fake_path)
self.assertFalse(result.ok)
self.assertEqual(result.aktion, "datei_nicht_gefunden")
self.assertIn("nicht gefunden", result.meldung)
self.assertEqual(result.kontext, Path(fake_path))
if __name__ == "__main__":
unittest.main()

View File

@@ -1,28 +1,111 @@
from qgis.PyQt.QtWidgets import QDockWidget, QTabWidget """
sn_basis/ui/base_dockwidget.py
Basis-Dockwidget für alle LNO-Module.
"""
from sn_basis.functions.qt_wrapper import QDockWidget, QTabWidget
from sn_basis.functions.message_wrapper import warning, error
from sn_basis.functions.qt_wrapper import (
QDockWidget,
QTabWidget,
Qt,
DockWidgetMovable,
DockWidgetFloatable,
DockWidgetClosable,
DockAreaLeft,
DockAreaRight,
)
class BaseDockWidget(QDockWidget): class BaseDockWidget(QDockWidget):
"""
Basis-Dockwidget für alle LNO-Module.
- Titel wird automatisch aus base_title + subtitle erzeugt
- Tabs werden dynamisch aus der Klassenvariable 'tabs' erzeugt
- Die zugehörige Toolbar-Action wird beim Schließen zurückgesetzt
"""
base_title = "LNO Sachsen" base_title = "LNO Sachsen"
tabs = [] tabs = [] # Liste von Tab-Klassen
action = None # Referenz auf die Toolbar-Action action = None # Referenz auf die Toolbar-Action
def __init__(self, parent=None, subtitle=""): def __init__(self, parent=None, subtitle=""):
super().__init__(parent) super().__init__(parent)
# -----------------------------------------------------
# Dock-Konfiguration (WICHTIG)
# -----------------------------------------------------
self.setFeatures(
DockWidgetMovable
| DockWidgetFloatable
| DockWidgetClosable
)
# Titel zusammensetzen self.setAllowedAreas(
title = self.base_title if not subtitle else f"{self.base_title} | {subtitle}" DockAreaLeft
self.setWindowTitle(title) | DockAreaRight
)
# Dock fixieren (nur schließen erlaubt) # -----------------------------------------------------
self.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetClosable) # Titel setzen
# -----------------------------------------------------
try:
title = (
self.base_title
if not subtitle
else f"{self.base_title} | {subtitle}"
)
self.setWindowTitle(title)
except Exception as e:
warning("Titel konnte nicht gesetzt werden", str(e))
# Tabs hinzufügen # -----------------------------------------------------
tab_widget = QTabWidget() # Tabs erzeugen
for tab_class in self.tabs: # -----------------------------------------------------
tab_widget.addTab(tab_class(), getattr(tab_class, "tab_title", tab_class.__name__)) try:
self.setWidget(tab_widget) tab_widget = QTabWidget()
for tab_class in self.tabs:
try:
tab_instance = tab_class()
tab_title = getattr(
tab_class,
"tab_title",
tab_class.__name__,
)
tab_widget.addTab(tab_instance, tab_title)
except Exception as e:
error(
"Tab konnte nicht geladen werden",
f"{tab_class}: {e}",
)
self.setWidget(tab_widget)
except Exception as e:
error(
"Tab-Widget konnte nicht initialisiert werden",
str(e),
)
# ---------------------------------------------------------
# Dock schließen
# ---------------------------------------------------------
def closeEvent(self, event): def closeEvent(self, event):
"""Wird aufgerufen, wenn das Dock geschlossen wird.""" """
if self.action: Wird aufgerufen, wenn das Dock geschlossen wird.
self.action.setChecked(False) # Toolbar-Button zurücksetzen Setzt die zugehörige Toolbar-Action zurück.
"""
try:
if self.action:
self.action.setChecked(False)
except Exception as e:
warning(
"Toolbar-Status konnte nicht zurückgesetzt werden",
str(e),
)
super().closeEvent(event) super().closeEvent(event)

View File

@@ -1,21 +1,85 @@
from qgis.PyQt.QtCore import Qt """
from qgis.PyQt.QtWidgets import QDockWidget sn_basis/ui/dockmanager.py
from qgis.utils import iface
Verwaltet das Anzeigen und Ersetzen von DockWidgets.
Stellt sicher, dass immer nur ein sn_basis-Dock gleichzeitig sichtbar ist.
"""
from typing import Any, Optional
from sn_basis.functions import (
add_dock_widget,
remove_dock_widget,
find_dock_widgets,
warning,
error,
)
from sn_basis.functions.qt_wrapper import (
DockAreaRight,
)
class DockManager: class DockManager:
default_area = Qt.DockWidgetArea.RightDockWidgetArea """
Verwaltet das Anzeigen und Ersetzen von DockWidgets.
"""
dock_prefix = "sn_dock_"
@classmethod @classmethod
def show(cls, dock_widget, area=None): def show(cls, dock_widget: Any, area: Optional[Any] = None) -> None:
area = area or cls.default_area """
Zeigt ein DockWidget an und entfernt vorher alle anderen
sn_basis-Docks (erkennbar am Prefix 'sn_dock_').
"""
# Bestehende Plugin-Docks mit Präfix schließen # -----------------------------------------------------
for widget in iface.mainWindow().findChildren(QDockWidget): # Default-Dock-Area (wrapper-konform)
if widget is not dock_widget and widget.objectName().startswith("sn_dock_"): # -----------------------------------------------------
iface.removeDockWidget(widget) if area is None:
widget.deleteLater() area = DockAreaRight
# Neues Dock anzeigen if dock_widget is None:
iface.addDockWidget(area, dock_widget) error("Dock konnte nicht angezeigt werden", "Dock-Widget ist None.")
dock_widget.show() return
try:
# -------------------------------------------------
# Sicherstellen, dass das Dock einen Namen hat
# -------------------------------------------------
if not dock_widget.objectName():
dock_widget.setObjectName(
f"{cls.dock_prefix}{id(dock_widget)}"
)
# -------------------------------------------------
# Vorhandene Plugin-Docks entfernen
# -------------------------------------------------
try:
for widget in find_dock_widgets():
if (
widget is not dock_widget
and widget.objectName().startswith(cls.dock_prefix)
):
remove_dock_widget(widget)
widget.deleteLater()
except Exception as e:
warning(
"Vorherige Docks konnten nicht entfernt werden",
str(e),
)
# -------------------------------------------------
# Neues Dock anzeigen
# -------------------------------------------------
try:
add_dock_widget(area, dock_widget)
dock_widget.show()
except Exception as e:
error(
"Dock konnte nicht angezeigt werden",
str(e),
)
except Exception as e:
error("DockManager-Fehler", str(e))

View File

@@ -1,83 +1,126 @@
from qgis.PyQt.QtWidgets import QAction, QMenu, QToolBar, QActionGroup """
sn_basis/ui/navigation.py
Zentrale Navigation (Menü + Toolbar) für sn_basis.
"""
from typing import Any, List, Tuple
from sn_basis.functions.qt_wrapper import (
QAction,
QMenu,
QToolBar,
QActionGroup,
)
from sn_basis.functions import (
get_main_window,
add_toolbar,
remove_toolbar,
add_menu,
remove_menu,
)
class Navigation: class Navigation:
def __init__(self, iface): def __init__(self):
self.iface = iface
self.actions = [] self.actions = []
self.menu = None
# Menü und Toolbar einmalig anlegen self.toolbar = None
self.menu = QMenu("LNO Sachsen", iface.mainWindow()) self.plugin_group = None
iface.mainWindow().menuBar().addMenu(self.menu)
self.toolbar = QToolBar("LNO Sachsen")
def init_ui(self):
print(">>> Navigation.init_ui() CALLED")
main_window = get_main_window()
if not main_window:
return
# -----------------------------------------
# Vorherige Toolbars entfernen
# -----------------------------------------
for tb in main_window.findChildren(QToolBar):
if tb.objectName() == "LnoSachsenToolbar":
remove_toolbar(tb)
tb.deleteLater()
# -----------------------------------------
# Menü und Toolbar neu erzeugen
# -----------------------------------------
self.menu = QMenu("LNO Sachsen", main_window)
add_menu(self.menu)
self.toolbar = QToolBar("LNO Sachsen", main_window)
self.toolbar.setObjectName("LnoSachsenToolbar") self.toolbar.setObjectName("LnoSachsenToolbar")
iface.addToolBar(self.toolbar) add_toolbar(self.toolbar)
# Gruppe für exklusive Auswahl (nur ein Plugin aktiv) test_action = QAction("TEST ACTION", main_window)
self.plugin_group = QActionGroup(iface.mainWindow()) self.menu.addAction(test_action)
self.toolbar.addAction(test_action)
self.plugin_group = QActionGroup(main_window)
self.plugin_group.setExclusive(True) self.plugin_group.setExclusive(True)
# -----------------------------------------------------
# Actions
# -----------------------------------------------------
def add_action(self, text, callback, tooltip="", priority=100): def add_action(self, text, callback, tooltip="", priority=100):
action = QAction(text, self.iface.mainWindow()) if not self.plugin_group:
return None
action = QAction(text, get_main_window())
action.setToolTip(tooltip) action.setToolTip(tooltip)
action.setCheckable(True) # Button kann aktiv sein action.setCheckable(True)
action.triggered.connect(callback) action.triggered.connect(callback)
# Action in Gruppe aufnehmen
self.plugin_group.addAction(action) self.plugin_group.addAction(action)
# Action mit Priority speichern
self.actions.append((priority, action)) self.actions.append((priority, action))
return action return action
def finalize_menu_and_toolbar(self): def finalize_menu_and_toolbar(self):
# Sortieren nach Priority if not self.menu or not self.toolbar:
return
self.actions.sort(key=lambda x: x[0]) self.actions.sort(key=lambda x: x[0])
# Menüeinträge
self.menu.clear() self.menu.clear()
self.toolbar.clear()
for _, action in self.actions: for _, action in self.actions:
self.menu.addAction(action) self.menu.addAction(action)
# Toolbar-Einträge
self.toolbar.clear()
for _, action in self.actions:
self.toolbar.addAction(action) self.toolbar.addAction(action)
def set_active_plugin(self, active_action): def set_active_plugin(self, active_action):
# Alle zurücksetzen, dann aktives Plugin markieren
for _, action in self.actions: for _, action in self.actions:
action.setChecked(False) action.setChecked(False)
if active_action: if active_action:
active_action.setChecked(True) active_action.setChecked(True)
def remove_all(self): # -----------------------------------------------------
"""Alles entfernen beim Entladen des Basisplugins""" # Cleanup
# Menü entfernen # -----------------------------------------------------
if self.menu:
self.iface.mainWindow().menuBar().removeAction(self.menu.menuAction())
self.menu = None
# Toolbar entfernen
if self.toolbar:
self.iface.mainWindow().removeToolBar(self.toolbar)
self.toolbar = None
# Actions zurücksetzen
self.actions.clear()
# Gruppe leeren
self.plugin_group = None
def remove_action(self, action): def remove_action(self, action):
"""Entfernt eine einzelne Action aus Menü und Toolbar"""
if not action: if not action:
return return
# Menüeintrag entfernen
if self.menu: if self.menu:
self.menu.removeAction(action) self.menu.removeAction(action)
# Toolbar-Eintrag entfernen
if self.toolbar: if self.toolbar:
self.toolbar.removeAction(action) self.toolbar.removeAction(action)
# Aus der internen Liste löschen
self.actions = [(p, a) for p, a in self.actions if a != action] self.actions = [(p, a) for p, a in self.actions if a != action]
def remove_all(self):
if self.menu:
remove_menu(self.menu)
self.menu = None
if self.toolbar:
remove_toolbar(self.toolbar)
self.toolbar = None
self.actions.clear()
self.plugin_group = None

View File

@@ -1,12 +1,18 @@
from qgis.PyQt.QtWidgets import ( #sn_basis/ui/tabs/settings_tab.py
QWidget, QGridLayout, QLabel, QLineEdit, from sn_basis.functions.qt_wrapper import (
QGroupBox, QVBoxLayout, QPushButton QWidget,
QGridLayout,
QLabel,
QLineEdit,
QGroupBox,
QVBoxLayout,
QPushButton,
) )
from sn_basis.logic.settings_logic import SettingsLogic from sn_basis.functions.settings_logic import SettingsLogic
class SettingsTab(QWidget): class SettingsTab(QWidget):
tab_title = "Projekteigenschaften" # Titel für den Tab tab_title = "Projekteigenschaften"
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
@@ -14,58 +20,87 @@ class SettingsTab(QWidget):
main_layout = QVBoxLayout() main_layout = QVBoxLayout()
# -----------------------------
# Definition der Felder # Definition der Felder
# -----------------------------
self.user_fields = { self.user_fields = {
"amt": "Amt:", "amt": "Amt:",
"behoerde": "Behörde:", "behoerde": "Behörde:",
"landkreis_user": "Landkreis:", "landkreis_user": "Landkreis:",
"sachgebiet": "Sachgebiet:" "sachgebiet": "Sachgebiet:",
} }
self.project_fields = { self.project_fields = {
"bezeichnung": "Bezeichnung:", "bezeichnung": "Bezeichnung:",
"verfahrensnummer": "Verfahrensnummer:", "verfahrensnummer": "Verfahrensnummer:",
"gemeinden": "Gemeinde(n):", "gemeinden": "Gemeinde(n):",
"landkreise_proj": "Landkreis(e):" "landkreise_proj": "Landkreis(e):",
} }
# 🟦 Benutzerspezifische Festlegungen # -----------------------------
# Benutzerspezifische Festlegungen
# -----------------------------
user_group = QGroupBox("Benutzerspezifische Festlegungen") user_group = QGroupBox("Benutzerspezifische Festlegungen")
user_layout = QGridLayout() user_layout = QGridLayout()
self.user_inputs = {} self.user_inputs = {}
for row, (key, label) in enumerate(self.user_fields.items()): for row, (key, label) in enumerate(self.user_fields.items()):
self.user_inputs[key] = QLineEdit() input_widget = QLineEdit()
self.user_inputs[key] = input_widget
user_layout.addWidget(QLabel(label), row, 0) user_layout.addWidget(QLabel(label), row, 0)
user_layout.addWidget(self.user_inputs[key], row, 1) user_layout.addWidget(input_widget, row, 1)
user_group.setLayout(user_layout) user_group.setLayout(user_layout)
# 🟨 Projektspezifische Festlegungen # -----------------------------
# Projektspezifische Festlegungen
# -----------------------------
project_group = QGroupBox("Projektspezifische Festlegungen") project_group = QGroupBox("Projektspezifische Festlegungen")
project_layout = QGridLayout() project_layout = QGridLayout()
self.project_inputs = {} self.project_inputs = {}
for row, (key, label) in enumerate(self.project_fields.items()): for row, (key, label) in enumerate(self.project_fields.items()):
self.project_inputs[key] = QLineEdit() input_widget = QLineEdit()
self.project_inputs[key] = input_widget
project_layout.addWidget(QLabel(label), row, 0) project_layout.addWidget(QLabel(label), row, 0)
project_layout.addWidget(self.project_inputs[key], row, 1) project_layout.addWidget(input_widget, row, 1)
project_group.setLayout(project_layout) project_group.setLayout(project_layout)
# 🟩 Speichern-Button # -----------------------------
# Speichern-Button
# -----------------------------
save_button = QPushButton("Speichern") save_button = QPushButton("Speichern")
save_button.clicked.connect(self.save_data) save_button.clicked.connect(self.save_data)
# -----------------------------
# Layout zusammenfügen # Layout zusammenfügen
# -----------------------------
main_layout.addWidget(user_group) main_layout.addWidget(user_group)
main_layout.addWidget(project_group) main_layout.addWidget(project_group)
main_layout.addStretch() main_layout.addStretch()
main_layout.addWidget(save_button) main_layout.addWidget(save_button)
self.setLayout(main_layout) self.setLayout(main_layout)
# Daten laden
self.load_data() self.load_data()
# ---------------------------------------------------------
# Speichern
# ---------------------------------------------------------
def save_data(self): def save_data(self):
# Alle Felder zusammenführen fields = {
fields = {key: widget.text() for key, widget in {**self.user_inputs, **self.project_inputs}.items()} key: widget.text()
for key, widget in {**self.user_inputs, **self.project_inputs}.items()
}
self.logic.save(fields) self.logic.save(fields)
# ---------------------------------------------------------
# Laden
# ---------------------------------------------------------
def load_data(self): def load_data(self):
data = self.logic.load() data = self.logic.load()
for key, widget in {**self.user_inputs, **self.project_inputs}.items(): for key, widget in {**self.user_inputs, **self.project_inputs}.items():