forked from AG_QGIS/Plugin_SN_Basis
Merge pull request 'dev' (#8) from Daniel/Plugin_SN_Basis:dev into main, Basisfunktion für sn_Verfahrensgebiet
Reviewed-on: AG_QGIS/Plugin_SN_Basis#8
This commit is contained in:
12
.coveragerc
Normal file
12
.coveragerc
Normal 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
20
.vscode/launch.json
vendored
Normal 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
35
.vscode/settings.json
vendored
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
from .functions.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)
|
||||||
|
|||||||
3
__pdoc__.py
Normal file
3
__pdoc__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
__pdoc__ = {
|
||||||
|
"main": False,
|
||||||
|
}
|
||||||
102
assets/Dateipruefer_flowchart.svg
Normal file
102
assets/Dateipruefer_flowchart.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 115 KiB |
3
assets/Linkpruefer_flowchart.svg
Normal file
3
assets/Linkpruefer_flowchart.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 160 KiB |
188
assets/Objektstruktur.txt
Normal file
188
assets/Objektstruktur.txt
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# Wrapper‑Architektur – Übersicht
|
||||||
|
Die Wrapper‑Architektur von sn_basis bildet das Fundament für eine robuste, testbare und zukunftssichere QGIS‑Plugin‑Entwicklung.
|
||||||
|
Sie kapselt sämtliche QGIS‑ und Qt‑Abhängigkeiten hinter klar definierten Schnittstellen und ermöglicht dadurch:
|
||||||
|
|
||||||
|
Mock‑fähige Unit‑Tests ohne QGIS
|
||||||
|
|
||||||
|
PyQt5/6‑Kompatibilität ohne Code‑Änderungen
|
||||||
|
|
||||||
|
saubere Trennung von UI, Logik und Infrastruktur
|
||||||
|
|
||||||
|
stabile APIs, die unabhängig von QGIS‑Versionen bleiben
|
||||||
|
|
||||||
|
klare Erweiterbarkeit für zukünftige Module und Plugins
|
||||||
|
|
||||||
|
Die Wrapper‑Schicht ist das zentrale Bindeglied zwischen der Plugin‑Logik und der QGIS‑/Qt‑Umgebung.
|
||||||
|
|
||||||
|
## Ziele der Wrapper‑Architektur
|
||||||
|
🎯 1. Entkopplung von QGIS und Qt
|
||||||
|
Alle direkten Importe wie from qgis.core import ... oder from qgis.PyQt.QtWidgets import ... verschwinden aus der Plugin‑Logik.
|
||||||
|
Stattdessen werden sie über Wrapper‑Module abstrahiert.
|
||||||
|
|
||||||
|
🎯 2. Testbarkeit ohne QGIS
|
||||||
|
Im Mock‑Modus liefern die Wrapper:
|
||||||
|
|
||||||
|
Dummy‑Objekte
|
||||||
|
|
||||||
|
simulierte Rückgabewerte
|
||||||
|
|
||||||
|
speicherbare Zustände (z. B. Variablen, Layer, Nachrichten)
|
||||||
|
|
||||||
|
Damit laufen Tests in jeder CI‑Umgebung.
|
||||||
|
|
||||||
|
🎯 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 Qt‑API, wird nur der Wrapper angepasst, nicht jedes Plugin.
|
||||||
|
|
||||||
|
## Architekturüberblick
|
||||||
|
Die Wrapper‑Schicht besteht aus mehreren Modulen, die jeweils einen klar abgegrenzten Verantwortungsbereich haben.
|
||||||
|
|
||||||
|
### 1. qt_wrapper – Qt‑Abstraktion
|
||||||
|
Kapselt alle Qt‑Widgets, Dialoge und Konstanten:
|
||||||
|
|
||||||
|
QWidget, QDialog, QMessageBox, QToolBar, QMenu, …
|
||||||
|
|
||||||
|
Layouts, Buttons, Labels, LineEdits
|
||||||
|
|
||||||
|
Qt‑Konstanten wie YES, NO, Dock‑Areas
|
||||||
|
|
||||||
|
Mock‑Modus:
|
||||||
|
Stellt Dummy‑Widgets bereit, die keine UI öffnen.
|
||||||
|
|
||||||
|
### 2. qgiscore_wrapper – QGIS‑Core‑Abstraktion
|
||||||
|
Abstraktion für:
|
||||||
|
|
||||||
|
QgsProject
|
||||||
|
|
||||||
|
Layer‑Zugriff
|
||||||
|
|
||||||
|
Projekt‑Metadaten
|
||||||
|
|
||||||
|
Pfade, CRS, Feature‑Zugriff
|
||||||
|
|
||||||
|
Mock‑Modus:
|
||||||
|
Simuliert ein Projekt und Layer‑Container.
|
||||||
|
|
||||||
|
### 3. qgisui_wrapper – QGIS‑UI‑Abstraktion
|
||||||
|
Kapselt UI‑bezogene QGIS‑Funktionen:
|
||||||
|
|
||||||
|
Zugriff auf iface
|
||||||
|
|
||||||
|
Dock‑Management
|
||||||
|
|
||||||
|
Menü‑ und Toolbar‑Integration
|
||||||
|
|
||||||
|
Hauptfenster‑Zugriff
|
||||||
|
|
||||||
|
Mock‑Modus:
|
||||||
|
Stellt ein Dummy‑Interface bereit.
|
||||||
|
|
||||||
|
### 4. variable_wrapper – QGIS‑Variablen
|
||||||
|
Abstraktion für:
|
||||||
|
|
||||||
|
Projektvariablen (projectScope)
|
||||||
|
|
||||||
|
globale Variablen (globalScope)
|
||||||
|
|
||||||
|
Mock‑Speicher für Tests
|
||||||
|
|
||||||
|
Vorteile:
|
||||||
|
|
||||||
|
keine QGIS‑Abhängigkeit in der Logik
|
||||||
|
|
||||||
|
testbare Variablenverwaltung
|
||||||
|
|
||||||
|
einheitliches API
|
||||||
|
|
||||||
|
### 5. message_wrapper – Meldungen & Logging
|
||||||
|
Einheitliche Schnittstelle für:
|
||||||
|
|
||||||
|
Fehlermeldungen
|
||||||
|
|
||||||
|
Warnungen
|
||||||
|
|
||||||
|
Info‑Meldungen
|
||||||
|
|
||||||
|
Logging
|
||||||
|
|
||||||
|
Mock‑Modus:
|
||||||
|
Speichert Nachrichten statt sie an QGIS zu senden.
|
||||||
|
|
||||||
|
### 6. dialog_wrapper – Benutzer‑Dialoge
|
||||||
|
Abstraktion für:
|
||||||
|
|
||||||
|
Ja/Nein‑Dialoge
|
||||||
|
|
||||||
|
spätere Erweiterungen (Eingabedialoge, Dateidialoge, etc.)
|
||||||
|
|
||||||
|
Mock‑Modus:
|
||||||
|
Gibt Default‑Werte zurück, öffnet keine UI.
|
||||||
|
|
||||||
|
### 7. DockManager & Navigation
|
||||||
|
Diese Module nutzen die Wrapper‑Schicht, um:
|
||||||
|
|
||||||
|
DockWidgets sicher zu verwalten
|
||||||
|
|
||||||
|
Toolbars und Menüs zu erzeugen
|
||||||
|
|
||||||
|
Reload‑sichere UI‑Strukturen aufzubauen
|
||||||
|
|
||||||
|
Sie sind keine Wrapper, sondern Wrapper‑Konsumenten.
|
||||||
|
|
||||||
|
## Designprinzipien
|
||||||
|
🧱 1. Single Source of Truth
|
||||||
|
Jede QGIS‑ oder Qt‑Funktionalität wird nur an einer Stelle implementiert.
|
||||||
|
|
||||||
|
🔄 2. Austauschbarkeit
|
||||||
|
Mock‑Modus und Echtmodus sind vollständig austauschbar.
|
||||||
|
|
||||||
|
🧪 3. Testbarkeit
|
||||||
|
Jede Funktion kann ohne QGIS getestet werden.
|
||||||
|
|
||||||
|
🧼 4. Saubere Trennung
|
||||||
|
UI → qt_wrapper
|
||||||
|
|
||||||
|
QGIS‑Core → qgiscore_wrapper
|
||||||
|
|
||||||
|
QGIS‑UI → 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 QGIS‑Abhängigkeiten in der Logik
|
||||||
|
|
||||||
|
IDE‑freundlich (Pylance, Autocomplete, Typing)
|
||||||
|
|
||||||
|
CI‑fähig (Tests ohne QGIS)
|
||||||
|
|
||||||
|
saubere Architektur
|
||||||
|
|
||||||
|
leichte Wartbarkeit
|
||||||
|
|
||||||
|
klare Dokumentation
|
||||||
|
|
||||||
|
## Fazit
|
||||||
|
Die Wrapper‑Architektur ist das Herzstück von sn_basis.
|
||||||
|
Sie ermöglicht eine moderne, modulare und testbare QGIS‑Plugin‑Entwicklung, die unabhängig von QGIS‑Versionen, Qt‑Versionen und Entwicklungsumgebungen funktioniert.
|
||||||
|
|
||||||
|
Sie bildet die Grundlage für:
|
||||||
|
|
||||||
|
stabile APIs
|
||||||
|
|
||||||
|
saubere UI‑Abstraktion
|
||||||
|
|
||||||
|
automatisierte Tests
|
||||||
|
|
||||||
|
nachhaltige Weiterentwicklung
|
||||||
144
assets/Pluginkonzept.md
Normal file
144
assets/Pluginkonzept.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# Wrapper‑Architektur – Übersicht
|
||||||
|
Die Wrapper‑Architektur von sn_basis bildet das Fundament für eine robuste, testbare und zukunftssichere QGIS‑Plugin‑Entwicklung.
|
||||||
|
Sie kapselt sämtliche QGIS‑ und Qt‑Abhängigkeiten hinter klar definierten Schnittstellen und ermöglicht dadurch:
|
||||||
|
|
||||||
|
- Mock‑fähige Unit‑Tests ohne QGIS
|
||||||
|
- PyQt5/6‑Kompatibilität ohne Code‑Änderungen
|
||||||
|
- saubere Trennung von UI, Logik und Infrastruktur
|
||||||
|
- stabile APIs, die unabhängig von QGIS‑Versionen bleiben
|
||||||
|
- klare Erweiterbarkeit für zukünftige Module und Plugins
|
||||||
|
|
||||||
|
Die Wrapper‑Schicht ist das zentrale Bindeglied zwischen der Plugin‑Logik und der QGIS‑/Qt‑Umgebung.
|
||||||
|
|
||||||
|
## Ziele der Wrapper‑Architektur
|
||||||
|
1. Entkopplung von QGIS und Qt
|
||||||
|
Alle direkten Importe wie from qgis.core import ... oder from qgis.PyQt.QtWidgets import ... verschwinden aus der Plugin‑Logik.
|
||||||
|
Stattdessen werden sie über Wrapper‑Module abstrahiert.
|
||||||
|
|
||||||
|
2. Testbarkeit ohne QGIS
|
||||||
|
Im Mock‑Modus liefern die Wrapper:
|
||||||
|
|
||||||
|
- Dummy‑Objekte
|
||||||
|
- simulierte Rückgabewerte
|
||||||
|
- speicherbare Zustände (z. B. Variablen, Layer, Nachrichten)
|
||||||
|
|
||||||
|
Damit laufen Tests in jeder CI‑Umgebung.
|
||||||
|
|
||||||
|
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 Qt‑API, wird nur der Wrapper angepasst, nicht jedes Plugin.
|
||||||
|
|
||||||
|
## Architekturüberblick
|
||||||
|
Die Wrapper‑Schicht besteht aus mehreren Modulen, die jeweils einen klar abgegrenzten Verantwortungsbereich haben.
|
||||||
|
|
||||||
|
### 1. qt_wrapper – Qt‑Abstraktion
|
||||||
|
Kapselt alle Qt‑Widgets, Dialoge und Konstanten:
|
||||||
|
|
||||||
|
- QWidget, QDialog, QMessageBox, QToolBar, QMenu, …
|
||||||
|
- Layouts, Buttons, Labels, LineEdits
|
||||||
|
- Qt‑Konstanten wie YES, NO, Dock‑Areas
|
||||||
|
|
||||||
|
Mock‑Modus:
|
||||||
|
Stellt Dummy‑Widgets bereit, die keine UI öffnen.
|
||||||
|
|
||||||
|
### 2. qgiscore_wrapper – QGIS‑Core‑Abstraktion
|
||||||
|
Abstraktion für:
|
||||||
|
|
||||||
|
- QgsProject
|
||||||
|
- Layer‑Zugriff
|
||||||
|
- Projekt‑Metadaten
|
||||||
|
- Pfade, CRS, Feature‑Zugriff
|
||||||
|
|
||||||
|
Mock‑Modus:
|
||||||
|
Simuliert ein Projekt und Layer‑Container.
|
||||||
|
|
||||||
|
### 3. qgisui_wrapper – QGIS‑UI‑Abstraktion
|
||||||
|
Kapselt UI‑bezogene QGIS‑Funktionen:
|
||||||
|
|
||||||
|
- Zugriff auf iface
|
||||||
|
- Dock‑Management
|
||||||
|
- Menü‑ und Toolbar‑Integration
|
||||||
|
- Hauptfenster‑Zugriff
|
||||||
|
|
||||||
|
Mock‑Modus:
|
||||||
|
Stellt ein Dummy‑Interface bereit.
|
||||||
|
|
||||||
|
### 4. variable_wrapper – QGIS‑Variablen
|
||||||
|
Abstraktion für:
|
||||||
|
|
||||||
|
- Projektvariablen (projectScope)
|
||||||
|
- globale Variablen (globalScope)
|
||||||
|
- Mock‑Speicher für Tests
|
||||||
|
|
||||||
|
Vorteile:
|
||||||
|
|
||||||
|
- keine QGIS‑Abhängigkeit in der Logik
|
||||||
|
- testbare Variablenverwaltung
|
||||||
|
- einheitliches API
|
||||||
|
|
||||||
|
### 5. message_wrapper – Meldungen & Logging
|
||||||
|
Einheitliche Schnittstelle für:
|
||||||
|
|
||||||
|
- Fehlermeldungen
|
||||||
|
- Warnungen
|
||||||
|
- Info‑Meldungen
|
||||||
|
- Logging
|
||||||
|
|
||||||
|
Mock‑Modus:
|
||||||
|
Speichert Nachrichten statt sie an QGIS zu senden.
|
||||||
|
|
||||||
|
### 6. dialog_wrapper – Benutzer‑Dialoge
|
||||||
|
Abstraktion für:
|
||||||
|
|
||||||
|
- Ja/Nein‑Dialoge
|
||||||
|
- spätere Erweiterungen (Eingabedialoge, Dateidialoge, etc.)
|
||||||
|
|
||||||
|
Mock‑Modus:
|
||||||
|
Gibt Default‑Werte zurück, öffnet keine UI.
|
||||||
|
|
||||||
|
### 7. DockManager & Navigation
|
||||||
|
Diese Module nutzen die Wrapper‑Schicht, um:
|
||||||
|
|
||||||
|
- DockWidgets sicher zu verwalten
|
||||||
|
- Toolbars und Menüs zu erzeugen
|
||||||
|
- Reload‑sichere UI‑Strukturen aufzubauen
|
||||||
|
|
||||||
|
Sie sind keine Wrapper, sondern Wrapper‑Konsumenten. Alle Fach-Plugins nutzen den Dockmanager des Basisplugins.
|
||||||
|
|
||||||
|
## Designprinzipien
|
||||||
|
1. Single Source of Truth
|
||||||
|
Jede QGIS‑ oder Qt‑Funktionalität wird nur an einer Stelle implementiert.
|
||||||
|
|
||||||
|
2. Austauschbarkeit
|
||||||
|
Mock‑Modus und Echtmodus sind vollständig austauschbar.
|
||||||
|
|
||||||
|
3. Testbarkeit
|
||||||
|
Jede Funktion kann ohne QGIS getestet werden.
|
||||||
|
|
||||||
|
4. Saubere Trennung
|
||||||
|
- UI → qt_wrapper
|
||||||
|
- QGIS‑Core → qgiscore_wrapper
|
||||||
|
- QGIS‑UI → qgisui_wrapper
|
||||||
|
- Logik → settings_logic, layer_logic, prüfmanager, …
|
||||||
|
|
||||||
|
5. Erweiterbarkeit
|
||||||
|
Neue Wrapper können jederzeit ergänzt werden, ohne bestehende Plugins zu brechen.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1
assets/Stilpruefer_flowchart.svg
Normal file
1
assets/Stilpruefer_flowchart.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/UML_Struktur.png
Normal file
BIN
assets/UML_Struktur.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
BIN
assets/datagrabber.jpeg
Normal file
BIN
assets/datagrabber.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
38
assets/datagrabber.md
Normal file
38
assets/datagrabber.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph Plugin
|
||||||
|
P[sn_plan41 Fachplugin]
|
||||||
|
A[Adapter Plan41LinklistAdapter]
|
||||||
|
PM[Pruefmanager]
|
||||||
|
LP[Layerpruefer]
|
||||||
|
KP[Linkpruefer]
|
||||||
|
SP[Stilpruefer]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Core
|
||||||
|
DG[DataGrabber]
|
||||||
|
NL[normalized entries]
|
||||||
|
LL[Layer Loader Provider Dispatch]
|
||||||
|
SM[Spatial Matcher]
|
||||||
|
ST[Storage GPKG / PostGIS]
|
||||||
|
PR[Project QGIS - addMapLayer]
|
||||||
|
LOG[Log / Ergebnisstruktur]
|
||||||
|
end
|
||||||
|
|
||||||
|
P -->|gibt Adapter, Prüfer, Pruefmanager| DG
|
||||||
|
A -->|load liefert Rohdaten| DG
|
||||||
|
DG -->|adapter.normalize| NL
|
||||||
|
NL --> DG
|
||||||
|
DG -->|für jeden Eintrag: _check_link -> KP.check| KP
|
||||||
|
DG -->|für jeden Eintrag: _check_style -> SP.check| SP
|
||||||
|
DG -->|prüfe vorhandene Layer| LP
|
||||||
|
DG -->|lade Layer via provider| LL
|
||||||
|
LL -->|Features| SM
|
||||||
|
SM -->|Abgleich| DG
|
||||||
|
DG -->|speichern| ST
|
||||||
|
ST --> PR
|
||||||
|
DG --> PR
|
||||||
|
DG -->|Ergebnis/Fehler| LOG
|
||||||
|
LOG --> PM
|
||||||
|
DG --> PM
|
||||||
|
```
|
||||||
BIN
assets/datagrabber.pdf
Normal file
BIN
assets/datagrabber.pdf
Normal file
Binary file not shown.
9
assets/moduluebersicht.md
Normal file
9
assets/moduluebersicht.md
Normal 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
|
||||||
|
```
|
||||||
@@ -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, ask_overwrite_append_cancel_custom
|
||||||
|
|
||||||
|
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)
|
||||||
|
|||||||
84
functions/dialog_wrapper.py
Normal file
84
functions/dialog_wrapper.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
"""
|
||||||
|
sn_basis/functions/dialog_wrapper.py – Benutzer-Dialoge (Qt5/6/Mock-kompatibel)
|
||||||
|
"""
|
||||||
|
from typing import Any
|
||||||
|
from typing import Literal, Optional
|
||||||
|
from sn_basis.functions.qt_wrapper import (
|
||||||
|
QMessageBox, YES, NO, CANCEL, QT_VERSION, exec_dialog, ICON_QUESTION,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
def ask_yes_no(
|
||||||
|
title: str,
|
||||||
|
message: str,
|
||||||
|
default: bool = True,
|
||||||
|
parent: Any = None,
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Stellt Ja/Nein-Frage. Funktioniert in PyQt5/6 UND Mock-Modus.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if QT_VERSION == 0: # Mock-Modus
|
||||||
|
print(f"🔍 Mock-Modus: ask_yes_no('{title}') → {default}")
|
||||||
|
return default
|
||||||
|
|
||||||
|
# ✅ KORREKT: Verwende YES/NO-Aliase aus qt_wrapper!
|
||||||
|
buttons = YES | NO
|
||||||
|
default_button = YES if default else NO
|
||||||
|
|
||||||
|
result = QMessageBox.question(
|
||||||
|
parent, title, message, buttons, default_button
|
||||||
|
)
|
||||||
|
|
||||||
|
# ✅ int(result) == int(YES) funktioniert Qt5/6/Mock
|
||||||
|
print(f"DEBUG ask_yes_no: result={result}, YES={YES}, match={int(result) == int(YES)}")
|
||||||
|
return int(result) == int(YES)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ ask_yes_no Fehler: {e}")
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
OverwriteDecision = Optional[Literal["overwrite", "append", "cancel"]]
|
||||||
|
|
||||||
|
|
||||||
|
def ask_overwrite_append_cancel_custom(
|
||||||
|
parent,
|
||||||
|
title: str,
|
||||||
|
message: str,
|
||||||
|
) -> Literal["overwrite", "append", "cancel"]:
|
||||||
|
"""Zeigt Dialog mit benutzerdefinierten Buttons: Überschreiben/Anhängen/Abbrechen.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
parent :
|
||||||
|
Eltern-Widget oder None.
|
||||||
|
title : str
|
||||||
|
Dialog-Titel.
|
||||||
|
message : str
|
||||||
|
Hauptmeldung mit Erklärung.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Literal["overwrite", "append", "cancel"]
|
||||||
|
Genaue Entscheidung des Nutzers.
|
||||||
|
"""
|
||||||
|
msg = QMessageBox(parent)
|
||||||
|
msg.setIcon(ICON_QUESTION)
|
||||||
|
msg.setWindowTitle(title)
|
||||||
|
msg.setText(message)
|
||||||
|
|
||||||
|
# Eigene Buttons mit exakten Texten
|
||||||
|
overwrite_btn = msg.addButton("Überschreiben", QMessageBox.ButtonRole.AcceptRole)
|
||||||
|
append_btn = msg.addButton("Anhängen", QMessageBox.ButtonRole.ActionRole)
|
||||||
|
cancel_btn = msg.addButton("Abbrechen", QMessageBox.ButtonRole.RejectRole)
|
||||||
|
|
||||||
|
exec_dialog(msg)
|
||||||
|
|
||||||
|
clicked = msg.clickedButton()
|
||||||
|
if clicked == overwrite_btn:
|
||||||
|
return "overwrite"
|
||||||
|
elif clicked == append_btn:
|
||||||
|
return "append"
|
||||||
|
else: # cancel_btn
|
||||||
|
return "cancel"
|
||||||
31
functions/ly_existence_wrapper.py
Normal file
31
functions/ly_existence_wrapper.py
Normal 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)
|
||||||
65
functions/ly_geometry_wrapper.py
Normal file
65
functions/ly_geometry_wrapper.py
Normal 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
|
||||||
97
functions/ly_metadata_wrapper.py
Normal file
97
functions/ly_metadata_wrapper.py
Normal 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
|
||||||
48
functions/ly_style_wrapper.py
Normal file
48
functions/ly_style_wrapper.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# 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
|
||||||
|
from sn_basis.modules.stilpruefer import Stilpruefer
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
def apply_style(layer, style_name: str) -> bool:
|
||||||
|
"""
|
||||||
|
Wendet einen Layerstil an, sofern er gültig ist.
|
||||||
|
|
||||||
|
- Validierung erfolgt ausschließlich über Stilpruefer
|
||||||
|
- Keine eigenen Dateisystem- oder Endungsprüfungen
|
||||||
|
- Keine Seiteneffekte bei ungültigem Stil
|
||||||
|
"""
|
||||||
|
print(">>> apply_style() START")
|
||||||
|
|
||||||
|
if not layer_exists(layer):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Stilpfad zusammensetzen
|
||||||
|
style_path = join_path(get_plugin_root(), "sn_verfahrensgebiet","styles", style_name)
|
||||||
|
|
||||||
|
# Stil prüfen
|
||||||
|
pruefer = Stilpruefer()
|
||||||
|
ergebnis = pruefer.pruefe(style_path)
|
||||||
|
print(">>> Stilprüfung:", ergebnis)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"[Stilprüfung] ok={ergebnis.ok} | "
|
||||||
|
f"aktion={ergebnis.aktion} | "
|
||||||
|
f"meldung={ergebnis.meldung}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if not ergebnis.ok:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Stil anwenden
|
||||||
|
try:
|
||||||
|
ok, _ = layer.loadNamedStyle(str(ergebnis.kontext))
|
||||||
|
if ok:
|
||||||
|
getattr(layer, "triggerRepaint", lambda: None)()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
41
functions/ly_visibility_wrapper.py
Normal file
41
functions/ly_visibility_wrapper.py
Normal 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
|
||||||
84
functions/message_wrapper.py
Normal file
84
functions/message_wrapper.py
Normal 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)
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# sn_basis/functions/messages.py
|
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
from qgis.core import Qgis
|
|
||||||
from qgis.PyQt.QtWidgets import QWidget
|
|
||||||
from qgis.utils import iface
|
|
||||||
|
|
||||||
|
|
||||||
def push_message(
|
|
||||||
level: Qgis.MessageLevel,
|
|
||||||
title: str,
|
|
||||||
text: str,
|
|
||||||
duration: Optional[int] = 5,
|
|
||||||
parent: Optional[QWidget] = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Zeigt eine Meldung in der QGIS-MessageBar.
|
|
||||||
- level: Qgis.Success | Qgis.Info | Qgis.Warning | Qgis.Critical
|
|
||||||
- title: Überschrift links (kurz halten)
|
|
||||||
- text: eigentliche Nachricht
|
|
||||||
- duration: Sekunden bis Auto-Ausblendung; None => bleibt sichtbar (mit Close-Button)
|
|
||||||
- parent: optionales Eltern-Widget (für Kontext), normalerweise nicht nötig
|
|
||||||
Rückgabe: MessageBarItem-Widget (kann später geschlossen/entfernt werden).
|
|
||||||
"""
|
|
||||||
bar = iface.messageBar()
|
|
||||||
# QGIS akzeptiert None als "sticky" Meldung
|
|
||||||
return bar.pushMessage(title, text, level=level, duration=duration)
|
|
||||||
|
|
||||||
|
|
||||||
def success(title: str, text: str, duration: int = 5):
|
|
||||||
return push_message(Qgis.Success, title, text, duration)
|
|
||||||
|
|
||||||
|
|
||||||
def info(title: str, text: str, duration: int = 5):
|
|
||||||
return push_message(Qgis.Info, title, text, duration)
|
|
||||||
|
|
||||||
|
|
||||||
def warning(title: str, text: str, duration: int = 5):
|
|
||||||
return push_message(Qgis.Warning, title, text, duration)
|
|
||||||
|
|
||||||
|
|
||||||
def error(title: str, text: str, duration: Optional[int] = 5):
|
|
||||||
# Fehler evtl. länger sichtbar lassen; setze duration=None falls gewünscht
|
|
||||||
return push_message(Qgis.Critical, title, text, duration)
|
|
||||||
77
functions/os_wrapper.py
Normal file
77
functions/os_wrapper.py
Normal 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
|
||||||
384
functions/qgiscore_wrapper.py
Normal file
384
functions/qgiscore_wrapper.py
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
"""
|
||||||
|
sn_basis/functions/qgiscore_wrapper.py – zentrale QGIS-Core-Abstraktion
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Type, Any, Optional
|
||||||
|
from sn_basis.functions.qt_wrapper import (
|
||||||
|
QUrl,
|
||||||
|
QEventLoop,
|
||||||
|
QNetworkRequest,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# QGIS-Symbole (werden dynamisch gesetzt)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
QgsProject: Type[Any]
|
||||||
|
QgsVectorLayer: Type[Any]
|
||||||
|
QgsRasterLayer: Type[Any]
|
||||||
|
QgsNetworkAccessManager: Type[Any]
|
||||||
|
Qgis: Type[Any]
|
||||||
|
QgsMapLayerProxyModel: Type[Any]
|
||||||
|
QgsVectorFileWriter: Type[Any] # neu: Schreib-API
|
||||||
|
|
||||||
|
QGIS_AVAILABLE = False
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Versuch: QGIS-Core importieren
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
try:
|
||||||
|
from qgis.core import (
|
||||||
|
QgsProject as _QgsProject,
|
||||||
|
QgsVectorLayer as _QgsVectorLayer,
|
||||||
|
QgsRasterLayer as _QgsRasterLayer,
|
||||||
|
QgsNetworkAccessManager as _QgsNetworkAccessManager,
|
||||||
|
Qgis as _Qgis,
|
||||||
|
QgsMapLayerProxyModel as _QgsMaplLayerProxyModel,
|
||||||
|
QgsVectorFileWriter as _QgsVectorFileWriter,
|
||||||
|
QgsFeature as _QgsFeature,
|
||||||
|
QgsField as _QgsField,
|
||||||
|
QgsGeometry as _QgsGeometry,
|
||||||
|
)
|
||||||
|
|
||||||
|
QgsProject = _QgsProject
|
||||||
|
QgsVectorLayer = _QgsVectorLayer
|
||||||
|
QgsRasterLayer = _QgsRasterLayer
|
||||||
|
QgsNetworkAccessManager = _QgsNetworkAccessManager
|
||||||
|
Qgis = _Qgis
|
||||||
|
QgsMapLayerProxyModel = _QgsMaplLayerProxyModel
|
||||||
|
QgsVectorFileWriter = _QgsVectorFileWriter
|
||||||
|
QgsFeature = _QgsFeature
|
||||||
|
QgsField = _QgsField
|
||||||
|
QgsGeometry = _QgsGeometry
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def dataProvider(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
QgsVectorLayer = _MockQgsVectorLayer
|
||||||
|
|
||||||
|
class _MockQgsNetworkAccessManager:
|
||||||
|
@staticmethod
|
||||||
|
def instance():
|
||||||
|
return _MockQgsNetworkAccessManager()
|
||||||
|
|
||||||
|
def head(self, request: Any):
|
||||||
|
return None
|
||||||
|
|
||||||
|
class _MockQgsRasterLayer:
|
||||||
|
"""
|
||||||
|
Minimaler Mock für QgsRasterLayer, ausreichend für Tests und
|
||||||
|
um im Datenabruf ein Raster-Layer-Objekt im pruef_ergebnis kontext mitzugeben.
|
||||||
|
"""
|
||||||
|
def __init__(self, source: str, name: str = "Raster", provider: str = "wms"):
|
||||||
|
self.source = source
|
||||||
|
self._name = name
|
||||||
|
self.provider = provider
|
||||||
|
self._valid = True
|
||||||
|
|
||||||
|
def isValid(self) -> bool:
|
||||||
|
return self._valid
|
||||||
|
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def dataProvider(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
QgsRasterLayer = _MockQgsRasterLayer
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Mock für QgsVectorFileWriter
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
class _MockSaveVectorOptions:
|
||||||
|
"""
|
||||||
|
Minimaler Ersatz für QgsVectorFileWriter.SaveVectorOptions.
|
||||||
|
Felder werden als einfache Attribute bereitgestellt.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.driverName: str = "GPKG"
|
||||||
|
self.layerName: Optional[str] = None
|
||||||
|
self.fileEncoding: str = "UTF-8"
|
||||||
|
# Action-Konstanten werden symbolisch verwendet
|
||||||
|
self.actionOnExistingFile: Optional[int] = None
|
||||||
|
|
||||||
|
class _MockQgsVectorFileWriter:
|
||||||
|
"""
|
||||||
|
Minimaler Mock für QgsVectorFileWriter mit der benötigten API:
|
||||||
|
- SaveVectorOptions (als Klasse)
|
||||||
|
- writeAsVectorFormatV3(layer, path, transformContext, options) -> error_code
|
||||||
|
- NoError (Konstante)
|
||||||
|
- CreateOrOverwriteFile / CreateOrOverwriteLayer (Konstanten)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Fehlerkonstanten (0 = NoError)
|
||||||
|
NoError = 0
|
||||||
|
|
||||||
|
# Action-Konstanten (Werte nur symbolisch)
|
||||||
|
CreateOrOverwriteFile = 1
|
||||||
|
CreateOrOverwriteLayer = 2
|
||||||
|
|
||||||
|
# SaveVectorOptions-Klasse
|
||||||
|
SaveVectorOptions = _MockSaveVectorOptions
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def writeAsVectorFormatV3(layer: Any, path: str, transform_context: Any, options: Any) -> int:
|
||||||
|
"""
|
||||||
|
Mock-Schreibfunktion.
|
||||||
|
|
||||||
|
Verhalten im Mock:
|
||||||
|
- Wenn 'layer' None oder options.layerName fehlt, geben wir NoError zurück,
|
||||||
|
aber schreiben nichts (Tests erwarten nur Rückgabecode).
|
||||||
|
- Diese Implementierung versucht nicht, echte Dateien zu schreiben.
|
||||||
|
- Rückgabewert: 0 (NoError) bei Erfolg, sonst eine positive Fehlernummer.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Sehr einfache Validierung: wenn path leer -> Fehler
|
||||||
|
if not path:
|
||||||
|
return 999
|
||||||
|
# Simuliere Erfolg
|
||||||
|
return _MockQgsVectorFileWriter.NoError
|
||||||
|
except Exception:
|
||||||
|
return 999 # generischer Fehlercode
|
||||||
|
|
||||||
|
QgsVectorFileWriter = _MockQgsVectorFileWriter
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Layer-Geometrie / Extent
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
def get_layer_extent(layer: Any) -> Any:
|
||||||
|
"""
|
||||||
|
Gibt die Ausdehnung (Extent) eines Layers zurück.
|
||||||
|
|
||||||
|
Diese Funktion kapselt den Zugriff auf ``layer.extent()`` und dient als
|
||||||
|
zentrale Abstraktion für alle Stellen, die die Bounding Box eines Layers
|
||||||
|
benötigen (z.B. für räumliche Filter im Datenabruf).
|
||||||
|
|
||||||
|
Verhalten
|
||||||
|
---------
|
||||||
|
- Wenn QGIS verfügbar ist und der Layer eine ``extent()``-Methode besitzt,
|
||||||
|
wird deren Rückgabewert zurückgegeben.
|
||||||
|
- Wenn QGIS nicht verfügbar ist oder der Layer keine ``extent()``-Methode
|
||||||
|
hat, wird ``None`` zurückgegeben.
|
||||||
|
"""
|
||||||
|
if not QGIS_AVAILABLE or layer is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
extent_func = getattr(layer, "extent", None)
|
||||||
|
if callable(extent_func):
|
||||||
|
try:
|
||||||
|
return extent_func()
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Buffer-Layer erzeugen
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
def create_buffer_layer(
|
||||||
|
source_layer: Any,
|
||||||
|
distance_m: float,
|
||||||
|
layer_name: str = "BufferLayer"
|
||||||
|
) -> Optional[Any]:
|
||||||
|
"""
|
||||||
|
Erzeugt einen Pufferlayer um alle Features eines Quelllayers.
|
||||||
|
|
||||||
|
Diese Funktion dient als zentrale Abstraktion für die Erzeugung eines
|
||||||
|
Pufferlayers in QGIS. Sie wird z.B. im Datenabruf verwendet, wenn der
|
||||||
|
Raumfilter ``"Pufferlayer"`` aktiv ist.
|
||||||
|
|
||||||
|
Verhalten
|
||||||
|
---------
|
||||||
|
- Wenn QGIS verfügbar ist und der ``source_layer`` gültig ist, wird ein
|
||||||
|
temporärer Vektorlayer erzeugt, der die gepufferten Geometrien enthält.
|
||||||
|
- Der Puffer wird in Metern angegeben.
|
||||||
|
- Der zurückgegebene Layer ist **nicht gespeichert**, sondern ein
|
||||||
|
temporärer Speicherlayer, der anschließend über den UI‑Wrapper ins
|
||||||
|
Projekt geladen werden kann.
|
||||||
|
- Wenn QGIS nicht verfügbar ist oder ein Fehler auftritt, wird ``None``
|
||||||
|
zurückgegeben.
|
||||||
|
"""
|
||||||
|
if not QGIS_AVAILABLE:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if source_layer is None or not hasattr(source_layer, "getFeatures"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Geometrien puffern
|
||||||
|
buffered_geoms = []
|
||||||
|
for feat in source_layer.getFeatures():
|
||||||
|
geom = feat.geometry()
|
||||||
|
if geom is None:
|
||||||
|
continue
|
||||||
|
buf = geom.buffer(distance_m, 8)
|
||||||
|
if buf is not None:
|
||||||
|
buffered_geoms.append(buf)
|
||||||
|
|
||||||
|
if not buffered_geoms:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Neuen Memory-Layer erzeugen
|
||||||
|
crs = source_layer.crs().authid() if hasattr(source_layer, "crs") else "EPSG:4326"
|
||||||
|
mem_layer = QgsVectorLayer(f"Polygon?crs={crs}", layer_name, "memory")
|
||||||
|
|
||||||
|
prov = mem_layer.dataProvider()
|
||||||
|
prov.addAttributes([])
|
||||||
|
mem_layer.updateFields()
|
||||||
|
|
||||||
|
# Features hinzufügen
|
||||||
|
from qgis.core import QgsFeature
|
||||||
|
for geom in buffered_geoms:
|
||||||
|
f = QgsFeature()
|
||||||
|
f.setGeometry(geom)
|
||||||
|
prov.addFeature(f)
|
||||||
|
|
||||||
|
mem_layer.updateExtents()
|
||||||
|
return mem_layer
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
#Hilfsfunktion, keine qgiscore-Entsprechung
|
||||||
|
|
||||||
|
def layer_exists_in_gpkg(gpkg_path: str, layer_name: str) -> bool:
|
||||||
|
"""
|
||||||
|
Prüft, ob ein Layer mit dem Namen `layer_name` in `gpkg_path` existiert.
|
||||||
|
- bevorzugt: SQLite-Abfrage auf gpkg_contents
|
||||||
|
- fallback: kurzer Versuch, mit QgsVectorLayer zu laden (wenn QGIS verfügbar)
|
||||||
|
"""
|
||||||
|
import os, sqlite3
|
||||||
|
if not gpkg_path or not layer_name or not os.path.exists(gpkg_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 1) SQLite-Check (schnell)
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(gpkg_path)
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT COUNT(1) FROM gpkg_contents WHERE table_name = ?", (layer_name,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
conn.close()
|
||||||
|
if row and row[0] > 0:
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
# falls sqlite fehlschlägt, weiter zum QGIS-Fallback
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 2) QGIS-Fallback: versuche kurz, den Layer zu laden
|
||||||
|
try:
|
||||||
|
if getattr(QgsVectorLayer, "__call__", None) and QGIS_AVAILABLE:
|
||||||
|
uri = f"{gpkg_path}|layername={layer_name}"
|
||||||
|
layer = QgsVectorLayer(uri, layer_name, "ogr")
|
||||||
|
return bool(layer and getattr(layer, "isValid", lambda: False)())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
247
functions/qgisui_wrapper.py
Normal file
247
functions/qgisui_wrapper.py
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
"""
|
||||||
|
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
|
||||||
|
from sn_basis.functions.qgiscore_wrapper import QgsProject, QGIS_AVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# Layer zum Projekt hinzufügen
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
def add_layer_to_project(layer: Any) -> bool:
|
||||||
|
"""
|
||||||
|
Fügt einen Layer dem aktuellen QGIS-Projekt hinzu.
|
||||||
|
|
||||||
|
Diese Funktion kapselt den Zugriff auf ``QgsProject.instance().addMapLayer``
|
||||||
|
und dient als zentrale Abstraktion für alle Stellen, die Layer dynamisch
|
||||||
|
ins Projekt einfügen möchten (z.B. Pufferlayer im Datenabruf).
|
||||||
|
|
||||||
|
Verhalten
|
||||||
|
---------
|
||||||
|
- Wenn QGIS verfügbar ist und der Layer gültig ist, wird er dem Projekt
|
||||||
|
hinzugefügt und ``True`` zurückgegeben.
|
||||||
|
- Wenn QGIS nicht verfügbar ist oder der Layer ungültig ist, wird
|
||||||
|
``False`` zurückgegeben.
|
||||||
|
- Im Mock-Modus wird kein Layer hinzugefügt, aber ``True`` zurückgegeben,
|
||||||
|
damit Tests ohne QGIS nicht fehlschlagen.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
layer:
|
||||||
|
Ein QGIS-Layer (typischerweise ``QgsVectorLayer``), der dem Projekt
|
||||||
|
hinzugefügt werden soll.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
``True`` bei Erfolg oder im Mock-Modus, sonst ``False``.
|
||||||
|
"""
|
||||||
|
if layer is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Mock-Modus: Erfolg simulieren
|
||||||
|
if not QGIS_AVAILABLE:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
project = QgsProject.instance()
|
||||||
|
project.addMapLayer(layer)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
514
functions/qt_wrapper.py
Normal file
514
functions/qt_wrapper.py
Normal file
@@ -0,0 +1,514 @@
|
|||||||
|
"""
|
||||||
|
sn_basis/functions/qt_wrapper.py – zentrale Qt-Abstraktion (PyQt6 primär / PyQt5 Fallback / Mock)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional, Type, Any, Callable
|
||||||
|
|
||||||
|
# Globale Qt-Symbole (werden dynamisch gesetzt)
|
||||||
|
QT_VERSION = 0 # 0 = Mock, 5 = PyQt5, 6 = PyQt6
|
||||||
|
YES: Optional[Any] = None
|
||||||
|
NO: Optional[Any] = None
|
||||||
|
CANCEL: Optional[Any] = None
|
||||||
|
ICON_QUESTION: Optional[Any] = None
|
||||||
|
|
||||||
|
|
||||||
|
# Qt-Klassen (werden dynamisch gesetzt)
|
||||||
|
QDockWidget: Type[Any] = object
|
||||||
|
QMessageBox: Type[Any] = object
|
||||||
|
QFileDialog: Type[Any] = object
|
||||||
|
QEventLoop: Type[Any] = object
|
||||||
|
QUrl: Type[Any] = object
|
||||||
|
QNetworkRequest: Type[Any] = object
|
||||||
|
QNetworkReply: Type[Any] = object
|
||||||
|
QCoreApplication: Type[Any] = object
|
||||||
|
QWidget: Type[Any] = object
|
||||||
|
QGridLayout: Type[Any] = object
|
||||||
|
QLabel: Type[Any] = object
|
||||||
|
QLineEdit: Type[Any] = object
|
||||||
|
QGroupBox: Type[Any] = object
|
||||||
|
QVBoxLayout: Type[Any] = object
|
||||||
|
QPushButton: Type[Any] = object
|
||||||
|
QAction: Type[Any] = object
|
||||||
|
QMenu: Type[Any] = object
|
||||||
|
QToolBar: Type[Any] = object
|
||||||
|
QActionGroup: Type[Any] = object
|
||||||
|
QTabWidget: Type[Any] = object
|
||||||
|
QToolButton: Type[Any] = object
|
||||||
|
QSizePolicy: Type[Any] = object
|
||||||
|
Qt: Type[Any] = object
|
||||||
|
QComboBox: Type[Any] = object
|
||||||
|
QHBoxLayout: Type[Any] = object
|
||||||
|
|
||||||
|
|
||||||
|
def exec_dialog(dialog: Any) -> Any:
|
||||||
|
"""Führt Dialog modal aus (Qt6: exec(), Qt5: exec_(), Mock: YES)"""
|
||||||
|
raise NotImplementedError("Qt nicht initialisiert")
|
||||||
|
|
||||||
|
def debug_qt_status() -> None:
|
||||||
|
"""Debug: Zeigt Qt-Status für Troubleshooting."""
|
||||||
|
print(f"🔍 QT_VERSION: {QT_VERSION}")
|
||||||
|
print(f"🔍 QMessageBox Typ: {getattr(QMessageBox, '__name__', type(QMessageBox).__name__)}")
|
||||||
|
print(f"🔍 YES Wert: {YES} (Typ: {type(YES) if YES is not None else 'None'})")
|
||||||
|
|
||||||
|
if QT_VERSION == 0:
|
||||||
|
print("❌ MOCK-MODUS AKTIV! Keine Dialoge möglich!")
|
||||||
|
elif QT_VERSION == 5:
|
||||||
|
print("✅ PyQt5 geladen (Fallback) – Dialoge sollten funktionieren!")
|
||||||
|
elif QT_VERSION == 6:
|
||||||
|
print("✅ PyQt6 geladen (primär) – Dialoge sollten funktionieren!")
|
||||||
|
else:
|
||||||
|
print("❓ Unbekannte Qt-Version!")
|
||||||
|
|
||||||
|
# --------------------------- PYQT6 PRIMÄR ---------------------------
|
||||||
|
try:
|
||||||
|
from qgis.PyQt.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,
|
||||||
|
QComboBox as _QComboBox,
|
||||||
|
QHBoxLayout as _QHBoxLayout,
|
||||||
|
)
|
||||||
|
from qgis.PyQt.QtCore import (
|
||||||
|
QEventLoop as _QEventLoop,
|
||||||
|
QUrl as _QUrl,
|
||||||
|
QCoreApplication as _QCoreApplication,
|
||||||
|
Qt as _Qt,
|
||||||
|
QVariant as _QVariant
|
||||||
|
)
|
||||||
|
from qgis.PyQt.QtNetwork import (
|
||||||
|
QNetworkRequest as _QNetworkRequest,
|
||||||
|
QNetworkReply as _QNetworkReply,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ✅ ALLE GLOBALS ZUWEISEN
|
||||||
|
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
|
||||||
|
QComboBox = _QComboBox
|
||||||
|
QVariant = _QVariant
|
||||||
|
QHBoxLayout= _QHBoxLayout
|
||||||
|
# ✅ QT6 ENUMS
|
||||||
|
YES = QMessageBox.StandardButton.Yes
|
||||||
|
NO = QMessageBox.StandardButton.No
|
||||||
|
CANCEL = QMessageBox.StandardButton.Cancel
|
||||||
|
ICON_QUESTION = QMessageBox.Icon.Question
|
||||||
|
AcceptRole = QMessageBox.ButtonRole.AcceptRole
|
||||||
|
ActionRole = QMessageBox.ButtonRole.ActionRole
|
||||||
|
RejectRole = QMessageBox.ButtonRole.RejectRole
|
||||||
|
|
||||||
|
# Qt6 Enum-Aliase
|
||||||
|
ToolButtonTextBesideIcon = Qt.ToolButtonStyle.ToolButtonTextBesideIcon
|
||||||
|
ArrowDown = Qt.ArrowType.DownArrow
|
||||||
|
ArrowRight = Qt.ArrowType.RightArrow
|
||||||
|
SizePolicyPreferred = QSizePolicy.Policy.Preferred
|
||||||
|
SizePolicyMaximum = QSizePolicy.Policy.Maximum
|
||||||
|
DockWidgetMovable = QDockWidget.DockWidgetFeature.DockWidgetMovable
|
||||||
|
DockWidgetFloatable = QDockWidget.DockWidgetFeature.DockWidgetFloatable
|
||||||
|
DockWidgetClosable = QDockWidget.DockWidgetFeature.DockWidgetClosable
|
||||||
|
DockAreaLeft = Qt.DockWidgetArea.LeftDockWidgetArea
|
||||||
|
DockAreaRight = Qt.DockWidgetArea.RightDockWidgetArea
|
||||||
|
|
||||||
|
def exec_dialog(dialog: Any) -> Any:
|
||||||
|
return dialog.exec()
|
||||||
|
|
||||||
|
print(f"✅ qt_wrapper: PyQt6 geladen (QT_VERSION={QT_VERSION})")
|
||||||
|
|
||||||
|
# --------------------------- PYQT5 FALLBACK ---------------------------
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
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,
|
||||||
|
QComboBox as _QComboBox,
|
||||||
|
QHBoxLayout as _QHBoxLayout,
|
||||||
|
)
|
||||||
|
from PyQt5.QtCore import (
|
||||||
|
QEventLoop as _QEventLoop,
|
||||||
|
QUrl as _QUrl,
|
||||||
|
QCoreApplication as _QCoreApplication,
|
||||||
|
Qt as _Qt,
|
||||||
|
QVariant as _QVariant
|
||||||
|
)
|
||||||
|
from PyQt5.QtNetwork import (
|
||||||
|
QNetworkRequest as _QNetworkRequest,
|
||||||
|
QNetworkReply as _QNetworkReply,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ✅ ALLE GLOBALS ZUWEISEN
|
||||||
|
QT_VERSION = 5
|
||||||
|
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
|
||||||
|
QComboBox = _QComboBox
|
||||||
|
QVariant = _QVariant
|
||||||
|
QHBoxLayout = _QHBoxLayout
|
||||||
|
|
||||||
|
# ✅ PYQT5 ENUMS
|
||||||
|
YES = QMessageBox.Yes
|
||||||
|
NO = QMessageBox.No
|
||||||
|
CANCEL = QMessageBox.Cancel
|
||||||
|
ICON_QUESTION = QMessageBox.Question
|
||||||
|
AcceptRole = QMessageBox.AcceptRole
|
||||||
|
ActionRole = QMessageBox.ActionRole
|
||||||
|
RejectRole = QMessageBox.RejectRole
|
||||||
|
|
||||||
|
|
||||||
|
# PyQt5 Enum-Aliase
|
||||||
|
ToolButtonTextBesideIcon = Qt.ToolButtonTextBesideIcon
|
||||||
|
ArrowDown = Qt.DownArrow
|
||||||
|
ArrowRight = Qt.RightArrow
|
||||||
|
SizePolicyPreferred = QSizePolicy.Preferred
|
||||||
|
SizePolicyMaximum = QSizePolicy.Maximum
|
||||||
|
DockWidgetMovable = QDockWidget.DockWidgetMovable
|
||||||
|
DockWidgetFloatable = QDockWidget.DockWidgetFloatable
|
||||||
|
DockWidgetClosable = QDockWidget.DockWidgetClosable
|
||||||
|
DockAreaLeft = Qt.LeftDockWidgetArea
|
||||||
|
DockAreaRight = Qt.RightDockWidgetArea
|
||||||
|
|
||||||
|
def exec_dialog(dialog: Any) -> Any:
|
||||||
|
return dialog.exec_()
|
||||||
|
|
||||||
|
print(f"✅ qt_wrapper: PyQt5 Fallback geladen (QT_VERSION={QT_VERSION})")
|
||||||
|
|
||||||
|
# --------------------------- MOCK-MODUS ---------------------------
|
||||||
|
except Exception:
|
||||||
|
QT_VERSION = 0
|
||||||
|
print("⚠️ qt_wrapper: Mock-Modus aktiviert (QT_VERSION=0)")
|
||||||
|
|
||||||
|
# Fake Enum für Bit-Operationen
|
||||||
|
class FakeEnum(int):
|
||||||
|
def __or__(self, other: Any) -> "FakeEnum":
|
||||||
|
return FakeEnum(int(self) | int(other))
|
||||||
|
|
||||||
|
YES = FakeEnum(1)
|
||||||
|
NO = FakeEnum(2)
|
||||||
|
CANCEL = FakeEnum(4)
|
||||||
|
ICON_QUESTION = FakeEnum(8)
|
||||||
|
|
||||||
|
# Im Mock-Block von qt_wrapper.py:
|
||||||
|
class _MockQMessageBox:
|
||||||
|
Yes = YES
|
||||||
|
No = NO
|
||||||
|
Cancel = CANCEL
|
||||||
|
Question = ICON_QUESTION
|
||||||
|
AcceptRole = 0
|
||||||
|
ActionRole = 3
|
||||||
|
RejectRole = 1
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def question(cls, parent, title, message, buttons, default_button):
|
||||||
|
"""Mock: Gibt immer default_button zurück"""
|
||||||
|
print(f"🔍 Mock QMessageBox.question: '{title}' → {default_button}")
|
||||||
|
return default_button
|
||||||
|
|
||||||
|
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: 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
|
||||||
|
QCoreApplication = object()
|
||||||
|
|
||||||
|
class _MockQt:
|
||||||
|
ToolButtonTextBesideIcon = 0
|
||||||
|
ArrowDown = 1
|
||||||
|
ArrowRight = 2
|
||||||
|
LeftDockWidgetArea = 1
|
||||||
|
RightDockWidgetArea = 2
|
||||||
|
|
||||||
|
Qt = _MockQt()
|
||||||
|
ToolButtonTextBesideIcon = Qt.ToolButtonTextBesideIcon
|
||||||
|
ArrowDown = Qt.ArrowDown
|
||||||
|
ArrowRight = Qt.ArrowRight
|
||||||
|
DockAreaLeft = Qt.LeftDockWidgetArea
|
||||||
|
DockAreaRight = Qt.RightDockWidgetArea
|
||||||
|
|
||||||
|
class _MockQDockWidget(_MockWidget):
|
||||||
|
def __init__(self, *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):
|
||||||
|
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:
|
||||||
|
Preferred = 3
|
||||||
|
Maximum = 2
|
||||||
|
|
||||||
|
QSizePolicy = _MockQSizePolicy
|
||||||
|
SizePolicyPreferred = QSizePolicy.Preferred
|
||||||
|
SizePolicyMaximum = QSizePolicy.Maximum
|
||||||
|
DockWidgetMovable = 1
|
||||||
|
DockWidgetFloatable = 2
|
||||||
|
DockWidgetClosable = 4
|
||||||
|
|
||||||
|
class _MockTabWidget:
|
||||||
|
def __init__(self, *args, **kwargs): self._tabs = []
|
||||||
|
def addTab(self, widget, title: str): self._tabs.append((widget, title))
|
||||||
|
|
||||||
|
QTabWidget = _MockTabWidget
|
||||||
|
|
||||||
|
class _MockComboBox:
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
self._items = []
|
||||||
|
self._index = -1
|
||||||
|
self.currentTextChanged = type('Signal', (), {'connect': lambda s, cb: None, 'emit': lambda s, v: None})()
|
||||||
|
def addItem(self, text: str) -> None: self._items.append(text)
|
||||||
|
def addItems(self, items): [self.addItem(it) for it in items]
|
||||||
|
def findText(self, text: str) -> int:
|
||||||
|
return self._items.index(text) if text in self._items else -1
|
||||||
|
def setCurrentIndex(self, idx: int) -> None:
|
||||||
|
if 0 <= idx < len(self._items):
|
||||||
|
self._index = idx
|
||||||
|
self.currentTextChanged.emit(self.currentText())
|
||||||
|
def setCurrentText(self, text: str) -> None:
|
||||||
|
idx = self.findText(text)
|
||||||
|
if idx >= 0: self.setCurrentIndex(idx)
|
||||||
|
def currentText(self) -> str:
|
||||||
|
return self._items[self._index] if 0 <= self._index < len(self._items) else ""
|
||||||
|
|
||||||
|
QComboBox = _MockComboBox
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------
|
||||||
|
# Mock für QVariant
|
||||||
|
# ---------------------------
|
||||||
|
|
||||||
|
class _MockQVariant:
|
||||||
|
"""
|
||||||
|
Minimaler Ersatz für QtCore.QVariant.
|
||||||
|
|
||||||
|
Ziel:
|
||||||
|
- Werte transparent durchreichen
|
||||||
|
- Typ-Konstanten bereitstellen
|
||||||
|
- Keine Qt-Abhängigkeiten
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Typ-Konstanten (symbolisch, Werte egal)
|
||||||
|
Invalid = 0
|
||||||
|
Int = 1
|
||||||
|
Double = 2
|
||||||
|
String = 3
|
||||||
|
Bool = 4
|
||||||
|
Date = 5
|
||||||
|
DateTime = 6
|
||||||
|
|
||||||
|
def __init__(self, value: Any = None):
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
def value(self) -> Any:
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"QVariant({self._value!r})"
|
||||||
|
|
||||||
|
# Optional: automatische Entpackung
|
||||||
|
def __int__(self):
|
||||||
|
return int(self._value)
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return float(self._value)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self._value)
|
||||||
|
QVariant = _MockQVariant
|
||||||
|
|
||||||
|
class _MockQHBoxLayout:
|
||||||
|
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
|
||||||
|
QHBoxLayout = _MockQHBoxLayout
|
||||||
|
def exec_dialog(dialog: Any) -> Any:
|
||||||
|
return YES
|
||||||
|
# --------------------------- TEST ---------------------------
|
||||||
|
if __name__ == "__main__":
|
||||||
|
debug_qt_status()
|
||||||
@@ -1,37 +1,47 @@
|
|||||||
from qgis.core import QgsProject, QgsExpressionContextUtils
|
"""
|
||||||
|
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:
|
class SettingsLogic:
|
||||||
def __init__(self):
|
"""
|
||||||
self.project = QgsProject.instance()
|
Verwaltet das Laden und Speichern der Plugin-Einstellungen.
|
||||||
|
Alle Variablen werden als sn_* Projektvariablen gespeichert.
|
||||||
|
"""
|
||||||
|
|
||||||
# Definition der Variablen-Namen
|
# Alle Variablen, die gespeichert werden sollen
|
||||||
self.global_vars = ["amt", "behoerde", "landkreis_user", "sachgebiet"]
|
VARIABLEN = [
|
||||||
self.project_vars = ["bezeichnung", "verfahrensnummer", "gemeinden", "landkreise_proj"]
|
"amt",
|
||||||
|
"behoerde",
|
||||||
|
"landkreis_user",
|
||||||
|
"sachgebiet",
|
||||||
|
"bezeichnung",
|
||||||
|
"verfahrensnummer",
|
||||||
|
"gemeinden",
|
||||||
|
"landkreise_proj",
|
||||||
|
]
|
||||||
|
|
||||||
def save(self, fields: dict):
|
def load(self) -> dict[str, str]:
|
||||||
"""Speichert Felder als globale und projektbezogene Ausdrucksvariablen."""
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
# Globale Variablen
|
def save(self, daten: dict[str, str]) -> None:
|
||||||
for key in self.global_vars:
|
"""
|
||||||
QgsExpressionContextUtils.setGlobalVariable(f"sn_{key}", fields.get(key, ""))
|
Speichert alle übergebenen Variablen im Projekt.
|
||||||
|
daten: dict mit key → value
|
||||||
# Projektvariablen
|
"""
|
||||||
for key in self.project_vars:
|
for key, value in daten.items():
|
||||||
QgsExpressionContextUtils.setProjectVariable(self.project, f"sn_{key}", fields.get(key, ""))
|
if key in self.VARIABLEN:
|
||||||
|
set_variable(key, value, scope="project")
|
||||||
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
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
# sn_basis/functions/styles.py
|
|
||||||
import os
|
|
||||||
from qgis.core import QgsVectorLayer
|
|
||||||
|
|
||||||
def apply_style(layer: QgsVectorLayer, style_name: str) -> bool:
|
|
||||||
"""
|
|
||||||
Lädt einen QML-Style aus dem styles-Ordner des Plugins und wendet ihn auf den Layer an.
|
|
||||||
style_name: Dateiname ohne Pfad, z.B. 'verfahrensgebiet.qml'
|
|
||||||
Rückgabe: True bei Erfolg, False sonst
|
|
||||||
"""
|
|
||||||
if not layer or not layer.isValid():
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Basis-Pfad: sn_basis/styles
|
|
||||||
base_dir = os.path.dirname(os.path.dirname(__file__)) # geht von functions/ eins hoch
|
|
||||||
style_path = os.path.join(base_dir, "styles", style_name)
|
|
||||||
|
|
||||||
if not os.path.exists(style_path):
|
|
||||||
print(f"Style-Datei nicht gefunden: {style_path}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
ok, error_msg = layer.loadNamedStyle(style_path)
|
|
||||||
if not ok:
|
|
||||||
print(f"Style konnte nicht geladen werden: {error_msg}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
layer.triggerRepaint()
|
|
||||||
return True
|
|
||||||
104
functions/sys_wrapper.py
Normal file
104
functions/sys_wrapper.py
Normal 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
|
||||||
14
functions/test.md
Normal file
14
functions/test.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
mermaid´´´
|
||||||
|
flowchart TD
|
||||||
|
A[Projekt]
|
||||||
|
|
||||||
|
subgraph children[ ]
|
||||||
|
direction TB
|
||||||
|
B[src]
|
||||||
|
C[docs]
|
||||||
|
D[README.md]
|
||||||
|
end
|
||||||
|
|
||||||
|
A --> B
|
||||||
|
A --> C
|
||||||
|
A --> D
|
||||||
@@ -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.")
|
|
||||||
115
functions/variable_wrapper.py
Normal file
115
functions/variable_wrapper.py
Normal 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.")
|
||||||
43
main.py
43
main.py
@@ -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
|
||||||
|
|||||||
172
modules/DataGrabber.py
Normal file
172
modules/DataGrabber.py
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
"""
|
||||||
|
DataGrabber module
|
||||||
|
==================
|
||||||
|
|
||||||
|
UI‑freier Orchestrator für die Prüfung und Klassifikation von Datenquellen.
|
||||||
|
|
||||||
|
Der DataGrabber:
|
||||||
|
- klassifiziert die übergebene Quelle (Datei, Dienst, Datenbank, Excel),
|
||||||
|
- ruft passende Prüfer (Dateipruefer, Linkpruefer, Layerpruefer, Stilpruefer) auf,
|
||||||
|
- sammelt alle rohen ``pruef_ergebnis``‑Objekte,
|
||||||
|
- aggregiert diese zu einem zusammenfassenden Ergebnis,
|
||||||
|
- **löst selbst keinerlei UI‑Interaktion aus**.
|
||||||
|
|
||||||
|
Alle Nutzerinteraktionen (MessageBar, QMessageBox, Logging) erfolgen
|
||||||
|
ausschließlich über den ``Pruefmanager`` im aufrufenden Kontext (UI / Pipeline).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Mapping, Optional, Tuple, Literal
|
||||||
|
|
||||||
|
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
||||||
|
from sn_basis.modules.Pruefmanager import Pruefmanager
|
||||||
|
|
||||||
|
from sn_basis.modules.Dateipruefer import Dateipruefer
|
||||||
|
from sn_basis.modules.linkpruefer import Linkpruefer
|
||||||
|
from sn_basis.modules.layerpruefer import Layerpruefer
|
||||||
|
from sn_basis.modules.stilpruefer import Stilpruefer
|
||||||
|
from sn_basis.modules.excel_importer import ExcelImporter
|
||||||
|
|
||||||
|
|
||||||
|
SourceType = Literal["service", "database", "excel", "unknown"]
|
||||||
|
|
||||||
|
SourceDict = Dict[str, List[Mapping[str, Any]]]
|
||||||
|
|
||||||
|
|
||||||
|
class DataGrabber:
|
||||||
|
"""
|
||||||
|
Analysiert und prüft Datenquellen für den Fachdatenabruf.
|
||||||
|
|
||||||
|
Der DataGrabber ist **UI‑frei**. Er erzeugt ausschließlich rohe
|
||||||
|
``pruef_ergebnis``‑Objekte und überlässt deren Verarbeitung
|
||||||
|
vollständig dem aufrufenden Code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
pruefmanager: Pruefmanager,
|
||||||
|
*,
|
||||||
|
datei_pruefer_cls: type[Dateipruefer] = Dateipruefer,
|
||||||
|
link_pruefer: Optional[Linkpruefer] = None,
|
||||||
|
layer_pruefer: Optional[Layerpruefer] = None,
|
||||||
|
stil_pruefer: Optional[Stilpruefer] = None,
|
||||||
|
excel_importer_cls: type[ExcelImporter] = ExcelImporter,
|
||||||
|
) -> None:
|
||||||
|
self.pruefmanager = pruefmanager
|
||||||
|
self._datei_pruefer_cls = datei_pruefer_cls
|
||||||
|
self.link_pruefer = link_pruefer
|
||||||
|
self.layer_pruefer = layer_pruefer
|
||||||
|
self.stil_pruefer = stil_pruefer
|
||||||
|
self._excel_importer_cls = excel_importer_cls
|
||||||
|
|
||||||
|
self._source: Optional[str] = None
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Öffentliche API
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def set_source(self, source: str) -> None:
|
||||||
|
"""Setzt die aktuell zu untersuchende Rohquelle."""
|
||||||
|
self._source = source
|
||||||
|
|
||||||
|
def analyze_source_type(self, source: str) -> SourceType:
|
||||||
|
"""
|
||||||
|
Klassifiziert die Quelle.
|
||||||
|
|
||||||
|
Aktuell Platzhalter – liefert ``"unknown"``.
|
||||||
|
"""
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
def run(self, source: str) -> Tuple[SourceDict, pruef_ergebnis]:
|
||||||
|
"""
|
||||||
|
Führt die vollständige Quellprüfung aus.
|
||||||
|
|
||||||
|
Diese Methode ist **UI‑frei**. Sie gibt rohe Ergebnisse zurück,
|
||||||
|
die vom Aufrufer über den ``Pruefmanager`` verarbeitet werden.
|
||||||
|
"""
|
||||||
|
self.set_source(source)
|
||||||
|
source_type = self.analyze_source_type(source)
|
||||||
|
|
||||||
|
source_dict: SourceDict = {}
|
||||||
|
partial_results: List[pruef_ergebnis] = []
|
||||||
|
|
||||||
|
if source_type == "excel":
|
||||||
|
source_dict, partial_results = self._process_excel_source(source)
|
||||||
|
elif source_type == "database":
|
||||||
|
source_dict, partial_results = self._process_database_source(source)
|
||||||
|
elif source_type == "service":
|
||||||
|
source_dict, partial_results = self._process_service_source(source)
|
||||||
|
else:
|
||||||
|
partial_results.append(
|
||||||
|
pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung="Quelle konnte nicht klassifiziert werden",
|
||||||
|
aktion="kein_dateipfad",
|
||||||
|
kontext={"source": source},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
summary = self._aggregate_results(source, source_dict, partial_results)
|
||||||
|
return source_dict, summary
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Excel‑Quellen
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _process_excel_source(
|
||||||
|
self, filepath: str
|
||||||
|
) -> Tuple[SourceDict, List[pruef_ergebnis]]:
|
||||||
|
source_dict: SourceDict = {}
|
||||||
|
results: List[pruef_ergebnis] = []
|
||||||
|
return source_dict, results
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Datenbank‑Quellen
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _process_database_source(
|
||||||
|
self, db_path: str
|
||||||
|
) -> Tuple[SourceDict, List[pruef_ergebnis]]:
|
||||||
|
source_dict: SourceDict = {}
|
||||||
|
results: List[pruef_ergebnis] = []
|
||||||
|
return source_dict, results
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Dienst‑Quellen
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _process_service_source(
|
||||||
|
self, link: str
|
||||||
|
) -> Tuple[SourceDict, List[pruef_ergebnis]]:
|
||||||
|
source_dict: SourceDict = {}
|
||||||
|
results: List[pruef_ergebnis] = []
|
||||||
|
return source_dict, results
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Aggregation
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _aggregate_results(
|
||||||
|
self,
|
||||||
|
source: str,
|
||||||
|
source_dict: SourceDict,
|
||||||
|
partial_results: List[pruef_ergebnis],
|
||||||
|
) -> pruef_ergebnis:
|
||||||
|
"""
|
||||||
|
Aggregiert Einzelprüfungen zu einem Gesamt‑``pruef_ergebnis``.
|
||||||
|
|
||||||
|
**Keine UI‑Interaktion.**
|
||||||
|
"""
|
||||||
|
if source_dict:
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=True,
|
||||||
|
meldung="Quelle erfolgreich geprüft",
|
||||||
|
aktion="ok",
|
||||||
|
kontext={
|
||||||
|
"source": source,
|
||||||
|
"valid_entries": sum(len(v) for v in source_dict.values()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung="Keine gültigen Einträge in der Quelle gefunden",
|
||||||
|
aktion="read_error",
|
||||||
|
kontext={"source": source},
|
||||||
|
)
|
||||||
208
modules/Dateipruefer.py
Normal file
208
modules/Dateipruefer.py
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
"""
|
||||||
|
sn_basis/modules/Dateipruefer.py
|
||||||
|
|
||||||
|
Erweiterter Dateiprüfer für Verfahrens-DB-Workflows mit vollständiger Unterstützung
|
||||||
|
der Anforderungen 1-2.e (leerer Pfad, fehlende Datei, bestehende Datei).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from sn_basis.functions.sys_wrapper import join_path, file_exists
|
||||||
|
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis, PruefAktion
|
||||||
|
|
||||||
|
|
||||||
|
class Dateipruefer:
|
||||||
|
"""
|
||||||
|
Prüft Dateieingaben für Verfahrens-DB-Workflows und liefert :class:`pruef_ergebnis`.
|
||||||
|
|
||||||
|
**Funktionsweise (deine Anforderungen 1-2.e):**
|
||||||
|
|
||||||
|
+---------------------+------------------------------------------+---------------+
|
||||||
|
| **Fall** | **Ergebnis** | **ok** |
|
||||||
|
+=====================+==========================================+===============+
|
||||||
|
| 1. Leerer Pfad | ``temporaer_erlaubt`` | False |
|
||||||
|
+---------------------+------------------------------------------+---------------+
|
||||||
|
| 2.a Leerer Pfad | Pruefmanager fragt → ``temporaer_erzeugen`` | True |
|
||||||
|
+---------------------+------------------------------------------+---------------+
|
||||||
|
| 2.b Datei existiert | ``ok`` | True |
|
||||||
|
+---------------------+------------------------------------------+---------------+
|
||||||
|
| 2.c Ungültiger Pfad | ``datei_nicht_gefunden`` | False |
|
||||||
|
+---------------------+------------------------------------------+---------------+
|
||||||
|
| **2.d Datei fehlt** | **``datei_wird_erzeugt``** | **True** |
|
||||||
|
+---------------------+------------------------------------------+---------------+
|
||||||
|
| **2.e Datei da** | **``datei_existiert``** | **False** |
|
||||||
|
+---------------------+------------------------------------------+---------------+
|
||||||
|
|
||||||
|
Der Dateiprüfer führt **keine UI-Interaktion** durch.
|
||||||
|
Entscheidungen werden ausschließlich vom :class:`Pruefmanager` getroffen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
pfad: Optional[str],
|
||||||
|
basis_pfad: str = "",
|
||||||
|
leereingabe_erlaubt: bool = False,
|
||||||
|
standarddatei: Optional[str] = None,
|
||||||
|
temporaer_erlaubt: bool = False,
|
||||||
|
*,
|
||||||
|
verfahrens_db_modus: bool = True, # 🆕 Verfahrens-DB-spezifische Logik
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
pfad : Optional[str]
|
||||||
|
Vom UI gelieferter Dateipfad (kann leer oder Whitespace sein).
|
||||||
|
basis_pfad : str, optional
|
||||||
|
Basisverzeichnis für relative Pfade (default: "").
|
||||||
|
leereingabe_erlaubt : bool, optional
|
||||||
|
Ob leere Eingabe grundsätzlich erlaubt ist (default: False).
|
||||||
|
standarddatei : Optional[str], optional
|
||||||
|
Optionaler Standardpfad (default: None).
|
||||||
|
temporaer_erlaubt : bool, optional
|
||||||
|
Ob bei leerer Eingabe temporäre Layer erlaubt sind (default: False).
|
||||||
|
verfahrens_db_modus : bool, optional
|
||||||
|
Aktiviert Verfahrens-DB-spezifische Logik (2.d, 2.e) (default: True).
|
||||||
|
"""
|
||||||
|
self.pfad = pfad
|
||||||
|
self.basis_pfad = basis_pfad
|
||||||
|
self.leereingabe_erlaubt = leereingabe_erlaubt
|
||||||
|
self.standarddatei = standarddatei
|
||||||
|
self.temporaer_erlaubt = temporaer_erlaubt
|
||||||
|
self.verfahrens_db_modus = verfahrens_db_modus
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Hilfsfunktionen
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _pfad(self, relativer_pfad: str) -> Path:
|
||||||
|
"""Erzeugt OS-unabhängigen Pfad relativ zum Basisverzeichnis."""
|
||||||
|
return join_path(self.basis_pfad, relativer_pfad)
|
||||||
|
|
||||||
|
def _ist_leer(self) -> bool:
|
||||||
|
"""
|
||||||
|
Prüft robust, ob Eingabe als „leer" zu behandeln ist.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True bei None, leerem String oder reinem Whitespace.
|
||||||
|
"""
|
||||||
|
if self.pfad is None:
|
||||||
|
return True
|
||||||
|
if not isinstance(self.pfad, str):
|
||||||
|
return True
|
||||||
|
return not self.pfad.strip()
|
||||||
|
|
||||||
|
def _ist_gueltiger_gpkg_pfad(self, pfad: Path) -> bool:
|
||||||
|
"""
|
||||||
|
Prüft, ob Pfad für GPKG geeignet ist (Endung + Schreibrechte).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True wenn `.gpkg`-Endung und Verzeichnis beschreibbar.
|
||||||
|
"""
|
||||||
|
if not str(pfad).lower().endswith('.gpkg'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Verzeichnis muss beschreibbar sein
|
||||||
|
return pfad.parent.exists() and pfad.parent.is_dir()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Hauptlogik: deine Anforderungen 1-2.e
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def pruefe(self) -> pruef_ergebnis:
|
||||||
|
"""
|
||||||
|
🆕 Prüft Dateieingabe gemäß Anforderungen 1-2.e.
|
||||||
|
|
||||||
|
**Workflow:**
|
||||||
|
1. **Leere Eingabe** → ``temporaer_erlaubt`` (Pruefmanager fragt)
|
||||||
|
2. **Pfad prüfen**:
|
||||||
|
- **Ungültig** → 2.c ``datei_nicht_gefunden``
|
||||||
|
- **Gültig, fehlt** → **2.d** ``datei_wird_erzeugt`` (ok=True!)
|
||||||
|
- **Gültig, existiert** → **2.e** ``datei_existiert`` (Pruefmanager fragt)
|
||||||
|
3. **Datei OK** → 2.b ``ok``
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
pruef_ergebnis
|
||||||
|
Mit korrekter Aktion für jeden Fall.
|
||||||
|
"""
|
||||||
|
# 1. 🎯 ANFORDERUNG 1: Leere Eingabe
|
||||||
|
if self._ist_leer():
|
||||||
|
return self._handle_leere_eingabe()
|
||||||
|
|
||||||
|
# 2. Pfad normalisieren
|
||||||
|
pfad = self._pfad(self.pfad.strip())
|
||||||
|
|
||||||
|
# 🆕 2.c: Ungültiger GPKG-Pfad?
|
||||||
|
if not self.verfahrens_db_modus or not self._ist_gueltiger_gpkg_pfad(pfad):
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung=f"Der Pfad '{self.pfad}' ist kein gültiger GPKG-Pfad.",
|
||||||
|
aktion="datei_nicht_gefunden",
|
||||||
|
kontext=pfad,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 🆕 2.d: Gültiger Pfad, Datei fehlt → DIREKT WEITER (ok=True!)
|
||||||
|
if not file_exists(pfad):
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=True, # 🎯 WICHTIG: Pipeline fortsetzen!
|
||||||
|
meldung=f"Datei '{self.pfad}' wird erzeugt.",
|
||||||
|
aktion="datei_wird_erzeugt",
|
||||||
|
kontext=pfad,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 🆕 2.e: Datei existiert → Pruefmanager fragt Überschreiben/etc.
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=False, # 🎯 Pruefmanager soll 4-Optionen-Dialog zeigen
|
||||||
|
meldung=f"Datei '{self.pfad}' existiert bereits.",
|
||||||
|
aktion="datei_existiert",
|
||||||
|
kontext=pfad,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2.b: Wird nicht erreicht (durch 2.e abgefangen)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Leere Eingabe (ANFORDERUNG 1, 2.a)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _handle_leere_eingabe(self) -> pruef_ergebnis:
|
||||||
|
"""
|
||||||
|
Behandelt leere Eingaben (Priorität: leereingabe → Standard → temporär → Fehler).
|
||||||
|
"""
|
||||||
|
if self.leereingabe_erlaubt:
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung="Das Dateifeld ist leer. Soll ohne Datei fortgefahren werden?",
|
||||||
|
aktion="leereingabe_erlaubt",
|
||||||
|
kontext=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.standarddatei:
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung=(
|
||||||
|
"Es wurde keine Datei angegeben. "
|
||||||
|
f"Soll die Standarddatei '{self.standarddatei}' verwendet werden?"
|
||||||
|
),
|
||||||
|
aktion="standarddatei_vorschlagen",
|
||||||
|
kontext=self._pfad(self.standarddatei),
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.temporaer_erlaubt:
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung=(
|
||||||
|
"Es wurde keine Datei angegeben. "
|
||||||
|
"Sollen temporäre Layer erzeugt werden?"
|
||||||
|
),
|
||||||
|
aktion="temporaer_erlaubt",
|
||||||
|
kontext=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung="Es wurde keine Datei angegeben.",
|
||||||
|
aktion="leereingabe_nicht_erlaubt",
|
||||||
|
kontext=None,
|
||||||
|
)
|
||||||
405
modules/Datenabruf.py
Normal file
405
modules/Datenabruf.py
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
# sn_basis/modules/Datenabruf.py
|
||||||
|
"""
|
||||||
|
Modul ``datenabruf``
|
||||||
|
|
||||||
|
Enthält die Klasse :class:`Datenabruf`, die für eine Menge bereits
|
||||||
|
validierter Links (aus ``validate_rows``) die Fachdaten abruft und
|
||||||
|
aggregierte Prüfergebnisse liefert.
|
||||||
|
|
||||||
|
Designprinzipien
|
||||||
|
----------------
|
||||||
|
- Die BBOX wird serverseitig angewendet: wenn ein Raumfilter aktiv ist,
|
||||||
|
wird die BBOX in die Abruf-URL eingebettet (außer bei WMS).
|
||||||
|
- Alle QGIS-Interaktionen laufen über die Wrapper `qgiscore_wrapper` und
|
||||||
|
`qgisui_wrapper`.
|
||||||
|
- Fehler werden als kurze Strings zurückgegeben und zentral in `log_fehler`
|
||||||
|
gesammelt; erfolgreiche Aufrufe werden in `log_geladen` protokolliert.
|
||||||
|
- Die Methode ist pdoc-kompatibel dokumentiert und bewusst einfach gehalten.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Mapping, Optional, Tuple
|
||||||
|
|
||||||
|
from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse
|
||||||
|
import json
|
||||||
|
|
||||||
|
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
||||||
|
from sn_basis.functions import qgiscore_wrapper as qgiscore
|
||||||
|
from sn_basis.functions import qgisui_wrapper as qgisui
|
||||||
|
from sn_basis.functions import qt_wrapper as qt
|
||||||
|
|
||||||
|
DataDict = Dict[str, List[Mapping[str, Any]]]
|
||||||
|
|
||||||
|
|
||||||
|
class Datenabruf:
|
||||||
|
"""
|
||||||
|
Führt den eigentlichen Fachdatenabruf für eine Menge validierter Links durch.
|
||||||
|
|
||||||
|
Erwartet ein ``DataDict`` der Form ``{"rows": [row1, row2, ...]}``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, pruefmanager: Any) -> None:
|
||||||
|
"""
|
||||||
|
Initialisiert eine neue Instanz des Datenabrufs.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
pruefmanager:
|
||||||
|
Instanz des Pruefmanagers, der :class:`pruef_ergebnis` verarbeitet.
|
||||||
|
"""
|
||||||
|
self.pruefmanager = pruefmanager
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Öffentliche API
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
def datenabruf(
|
||||||
|
self,
|
||||||
|
result_dict: DataDict,
|
||||||
|
raumfilter: str,
|
||||||
|
verfahrensgebiet_layer: Any,
|
||||||
|
speicherort: str,
|
||||||
|
pruef_ergebnisse: Optional[List[Any]] = None,
|
||||||
|
) -> Tuple[Dict[str, Any], List[Any]]:
|
||||||
|
"""
|
||||||
|
Ruft für alle Zeilen in ``result_dict["rows"]`` die Fachdaten ab und
|
||||||
|
liefert ein Daten‑Dict sowie die Liste verarbeiteter Pruefergebnisse.
|
||||||
|
|
||||||
|
Logging / Aggregation
|
||||||
|
---------------------
|
||||||
|
Am Ende enthält das zusammenfassende PruefErgebnis im Kontext:
|
||||||
|
- geladen: dict(dienst -> anzahl geladen)
|
||||||
|
- fehler: dict(dienst -> fehlermeldung)
|
||||||
|
- relevant: dict(dienst -> anzahl relevant)
|
||||||
|
- ausserhalb: dict(dienst -> anzahl geladen, aber ausserhalb)
|
||||||
|
"""
|
||||||
|
if pruef_ergebnisse is None:
|
||||||
|
processed_results: List[Any] = []
|
||||||
|
else:
|
||||||
|
processed_results = list(pruef_ergebnisse)
|
||||||
|
|
||||||
|
rows = result_dict.get("rows", [])
|
||||||
|
daten: Dict[str, List[Any]] = {}
|
||||||
|
|
||||||
|
# 1) Räumliche Filtergeometrie bestimmen (BBox oder None)
|
||||||
|
bbox_geom = self._determine_spatial_filter(raumfilter, verfahrensgebiet_layer)
|
||||||
|
|
||||||
|
# Globale Logs über alle Dienste hinweg
|
||||||
|
log_geladen: Dict[str, int] = {}
|
||||||
|
log_fehler: Dict[str, str] = {}
|
||||||
|
log_relevant: Dict[str, int] = {}
|
||||||
|
log_ausserhalb: Dict[str, int] = {}
|
||||||
|
|
||||||
|
# 2) Über alle Zeilen iterieren
|
||||||
|
for row in rows:
|
||||||
|
ident = row.get("ident")
|
||||||
|
link = row.get("Link")
|
||||||
|
provider = row.get("Provider")
|
||||||
|
|
||||||
|
if not ident or not link or not provider:
|
||||||
|
pe = pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung="Ungültige Zeile im Datenabruf (fehlende Pflichtfelder)",
|
||||||
|
aktion="pflichtfelder_fehlen",
|
||||||
|
kontext=row,
|
||||||
|
)
|
||||||
|
processed_results.append(self.pruefmanager.verarbeite(pe))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Lesbarer Dienstname für Logs
|
||||||
|
thema = row.get("Inhalt") or row.get("Thema") or row.get("Titel") or str(ident)
|
||||||
|
|
||||||
|
# 2a) Provider-spezifische URL zusammenbauen
|
||||||
|
# Wenn Raumfilter aktiv ist, übergeben wir bbox_geom an _build_provider_url,
|
||||||
|
# außer bei WMS (WMS bleibt unverändert).
|
||||||
|
use_bbox = (raumfilter != "ohne") and (str(provider).upper() != "WMS")
|
||||||
|
url = self._build_provider_url(link=link, provider=str(provider), bbox_geom=bbox_geom if use_bbox else None)
|
||||||
|
|
||||||
|
# 2b) Fachdaten abrufen
|
||||||
|
features, error_msg = self._fetch_features(url=url, provider=str(provider))
|
||||||
|
|
||||||
|
# 2c) Logs und Aggregation
|
||||||
|
if error_msg:
|
||||||
|
# Fehler beim Abruf
|
||||||
|
log_fehler[thema] = error_msg
|
||||||
|
pe_err = pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung=f"Fehler beim Abruf von {thema}: {error_msg}",
|
||||||
|
aktion="url_nicht_erreichbar",
|
||||||
|
kontext={"ident": ident, "thema": thema, "url": url, "error": error_msg},
|
||||||
|
)
|
||||||
|
processed_results.append(self.pruefmanager.verarbeite(pe_err))
|
||||||
|
# daten[ident] bleibt nicht gesetzt oder leer
|
||||||
|
daten[str(ident)] = []
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Erfolgreich aufgerufen (auch wenn features == [])
|
||||||
|
anzahl_geladen = len(features)
|
||||||
|
log_geladen[thema] = anzahl_geladen
|
||||||
|
|
||||||
|
# Da die BBOX serverseitig angewendet wurde:
|
||||||
|
# - anzahl_geladen > 0 -> relevant
|
||||||
|
# - anzahl_geladen == 0 -> ausserhalb
|
||||||
|
if anzahl_geladen > 0:
|
||||||
|
log_relevant[thema] = anzahl_geladen
|
||||||
|
daten[str(ident)] = features
|
||||||
|
else:
|
||||||
|
log_ausserhalb[thema] = 0
|
||||||
|
daten[str(ident)] = []
|
||||||
|
|
||||||
|
# 2d) Kurzes Prüfergebnis pro Zeile
|
||||||
|
pe_row = pruef_ergebnis(
|
||||||
|
ok=True,
|
||||||
|
meldung=(
|
||||||
|
f"Datenabruf für ident={ident}: {anzahl_geladen} geladene Objekte"
|
||||||
|
),
|
||||||
|
aktion="datenabruf",
|
||||||
|
kontext={
|
||||||
|
"ident": ident,
|
||||||
|
"thema": thema,
|
||||||
|
"anzahl_gesamt": anzahl_geladen,
|
||||||
|
"url": url,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
processed_results.append(self.pruefmanager.verarbeite(pe_row))
|
||||||
|
|
||||||
|
# 3) Zusammenfassendes Prüfergebnis (wie alter DataGrabber)
|
||||||
|
summary_kontext = {
|
||||||
|
"geladen": log_geladen,
|
||||||
|
"fehler": log_fehler,
|
||||||
|
"relevant": log_relevant,
|
||||||
|
"ausserhalb": log_ausserhalb,
|
||||||
|
}
|
||||||
|
|
||||||
|
pe_summary = pruef_ergebnis(
|
||||||
|
ok=(len(log_fehler) == 0),
|
||||||
|
meldung=(
|
||||||
|
f"Datenabruf abgeschlossen: {len(log_geladen)} Dienste geladen, "
|
||||||
|
f"{len(log_fehler)} Fehler"
|
||||||
|
),
|
||||||
|
aktion="datenabruf",
|
||||||
|
kontext=summary_kontext,
|
||||||
|
)
|
||||||
|
processed_results.append(self.pruefmanager.verarbeite(pe_summary))
|
||||||
|
|
||||||
|
daten_dict: Dict[str, Any] = {
|
||||||
|
"speicherort": speicherort,
|
||||||
|
"daten": daten,
|
||||||
|
}
|
||||||
|
return daten_dict, processed_results
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Hilfsmethoden: räumlicher Filter
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
def _determine_spatial_filter(self, raumfilter: str, verfahrensgebiet_layer: Any) -> Optional[Any]:
|
||||||
|
"""
|
||||||
|
Bestimmt die räumliche Filtergeometrie (BBox) abhängig vom Raumfilter.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Optional[Any]
|
||||||
|
Eine Geometrie/Extent (z. B. QgsRectangle) oder ``None``.
|
||||||
|
"""
|
||||||
|
if raumfilter == "ohne":
|
||||||
|
return None
|
||||||
|
|
||||||
|
if verfahrensgebiet_layer is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if raumfilter == "Verfahrensgebiet":
|
||||||
|
return qgiscore.get_layer_extent(verfahrensgebiet_layer)
|
||||||
|
|
||||||
|
if raumfilter == "Pufferlayer":
|
||||||
|
buffer_layer = qgiscore.create_buffer_layer(
|
||||||
|
source_layer=verfahrensgebiet_layer,
|
||||||
|
distance_m=1000.0,
|
||||||
|
layer_name="Verfahrensgebiet_Puffer_1km",
|
||||||
|
)
|
||||||
|
if buffer_layer is not None:
|
||||||
|
qgisui.add_layer_to_project(buffer_layer)
|
||||||
|
return qgiscore.get_layer_extent(buffer_layer)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Hilfsmethoden: Provider-URL und Datenabruf
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
def _build_provider_url(self, link: str, provider: str, bbox_geom: Optional[Any]) -> str:
|
||||||
|
"""
|
||||||
|
Baut eine Provider-spezifische Abruf-URL. Wenn `bbox_geom` übergeben
|
||||||
|
wird, wird sie in die URL eingebettet (außer bei WMS).
|
||||||
|
|
||||||
|
Erwartet: provider ist gesetzt (z. B. "WFS", "REST", "OGR", "WMS").
|
||||||
|
"""
|
||||||
|
provider_norm = (provider or "").upper()
|
||||||
|
base_link = link or ""
|
||||||
|
|
||||||
|
# WMS: niemals BBOX anhängen
|
||||||
|
if provider_norm == "WMS":
|
||||||
|
return base_link
|
||||||
|
|
||||||
|
if bbox_geom is None:
|
||||||
|
return base_link
|
||||||
|
|
||||||
|
# Versuche bbox-String zu erzeugen (nutzt qgiscore.extent_to_bbox_string wenn vorhanden)
|
||||||
|
bbox_str: Optional[str] = None
|
||||||
|
try:
|
||||||
|
extent_to_bbox = getattr(__import__("sn_basis.functions.qgiscore_wrapper", fromlist=["qgiscore_wrapper"]), "extent_to_bbox_string", None)
|
||||||
|
if callable(extent_to_bbox):
|
||||||
|
bbox_str = extent_to_bbox(bbox_geom)
|
||||||
|
else:
|
||||||
|
# Fallback: einfache xmin/ymin/xmax/ymax-Extraktion (duck-typing)
|
||||||
|
if hasattr(bbox_geom, "xmin") and callable(getattr(bbox_geom, "xmin")):
|
||||||
|
bbox_str = f"{bbox_geom.xmin()},{bbox_geom.ymin()},{bbox_geom.xmax()},{bbox_geom.ymax()}"
|
||||||
|
elif isinstance(bbox_geom, (tuple, list)) and len(bbox_geom) == 4:
|
||||||
|
bbox_str = f"{bbox_geom[0]},{bbox_geom[1]},{bbox_geom[2]},{bbox_geom[3]}"
|
||||||
|
else:
|
||||||
|
bbox_str = str(bbox_geom)
|
||||||
|
except Exception:
|
||||||
|
bbox_str = None
|
||||||
|
|
||||||
|
if not bbox_str:
|
||||||
|
return base_link
|
||||||
|
|
||||||
|
parsed = urlparse(base_link)
|
||||||
|
query_params = dict(parse_qsl(parsed.query, keep_blank_values=True))
|
||||||
|
|
||||||
|
if provider_norm == "WFS":
|
||||||
|
query_params.setdefault("BBOX", bbox_str)
|
||||||
|
new_query = urlencode(query_params, doseq=True)
|
||||||
|
rebuilt = parsed._replace(query=new_query)
|
||||||
|
return urlunparse(rebuilt)
|
||||||
|
|
||||||
|
if provider_norm in ("REST", "ARCGIS", "ARCGISFEATURESERVER", "ARCGIS_FEATURESERVER"):
|
||||||
|
query_params.setdefault("geometry", bbox_str)
|
||||||
|
query_params.setdefault("geometryType", "esriGeometryEnvelope")
|
||||||
|
query_params.setdefault("spatialRel", "esriSpatialRelIntersects")
|
||||||
|
query_params.setdefault("f", query_params.get("f", "json"))
|
||||||
|
new_query = urlencode(query_params, doseq=True)
|
||||||
|
rebuilt = parsed._replace(query=new_query)
|
||||||
|
return urlunparse(rebuilt)
|
||||||
|
|
||||||
|
# Default: generischer bbox-Parameter
|
||||||
|
query_params.setdefault("bbox", bbox_str)
|
||||||
|
new_query = urlencode(query_params, doseq=True)
|
||||||
|
rebuilt = parsed._replace(query=new_query)
|
||||||
|
return urlunparse(rebuilt)
|
||||||
|
|
||||||
|
def _fetch_features(self, url: str, provider: str) -> Tuple[List[Any], Optional[str]]:
|
||||||
|
"""
|
||||||
|
Führt den eigentlichen Abruf der Fachdaten durch.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple[List[Any], Optional[str]]
|
||||||
|
- features: Liste der geladenen Features (ggf. leer)
|
||||||
|
- error_msg: None bei Erfolg, sonst kurzer Fehlertext
|
||||||
|
"""
|
||||||
|
features: List[Any] = []
|
||||||
|
prov = str(provider).upper()
|
||||||
|
|
||||||
|
# WMS: kein Featureabruf; caller behandelt WMS separat (hier defensiv)
|
||||||
|
if prov == "WMS":
|
||||||
|
return [], None
|
||||||
|
|
||||||
|
# OGR / lokale Dateien: versuche QGIS-Layer (wenn QGIS verfügbar)
|
||||||
|
if prov in ("OGR", "GPKG", "SHP", "GEOJSON"):
|
||||||
|
if getattr(qgiscore, "QGIS_AVAILABLE", False):
|
||||||
|
try:
|
||||||
|
layer = qgiscore.QgsVectorLayer(url, "tmp", "ogr")
|
||||||
|
if not layer or not getattr(layer, "isValid", lambda: False)():
|
||||||
|
return [], "Layer ungültig oder konnte nicht geladen werden"
|
||||||
|
for feat in layer.getFeatures():
|
||||||
|
features.append(feat)
|
||||||
|
return features, None
|
||||||
|
except FileNotFoundError:
|
||||||
|
return [], "Lokale Datei nicht gefunden"
|
||||||
|
except Exception as exc:
|
||||||
|
return [], f"Fehler beim Laden der OGR-Quelle: {exc}"
|
||||||
|
else:
|
||||||
|
# Mock: falls GeoJSON-Datei vorhanden, versuche lokale Datei zu lesen
|
||||||
|
try:
|
||||||
|
if url.lower().endswith(".geojson"):
|
||||||
|
with open(url, "r", encoding="utf-8") as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
if isinstance(data, dict) and data.get("type") == "FeatureCollection":
|
||||||
|
return data.get("features", []), None
|
||||||
|
return [], "Keine QGIS-Umgebung und keine lesbare lokale GeoJSON"
|
||||||
|
except FileNotFoundError:
|
||||||
|
return [], "Lokale Datei nicht gefunden"
|
||||||
|
except Exception as exc:
|
||||||
|
return [], f"Fehler beim Lesen lokaler GeoJSON (Mock): {exc}"
|
||||||
|
|
||||||
|
# HTTP-basierte Dienste (WFS, REST/ArcGIS, generisch)
|
||||||
|
response_text: Optional[str] = None
|
||||||
|
http_error: Optional[str] = None
|
||||||
|
|
||||||
|
# QGIS NetworkAccessManager bevorzugen
|
||||||
|
if getattr(qgiscore, "QGIS_AVAILABLE", False) and getattr(qgiscore, "QgsNetworkAccessManager", None) is not None:
|
||||||
|
try:
|
||||||
|
manager = qgiscore.QgsNetworkAccessManager.instance()
|
||||||
|
QUrl = getattr(__import__("sn_basis.functions.qt_wrapper", fromlist=["qt_wrapper"]), "QUrl", None)
|
||||||
|
QNetworkRequest = getattr(__import__("sn_basis.functions.qt_wrapper", fromlist=["qt_wrapper"]), "QNetworkRequest", None)
|
||||||
|
QEventLoop = getattr(__import__("sn_basis.functions.qt_wrapper", fromlist=["qt_wrapper"]), "QEventLoop", None)
|
||||||
|
if QUrl is not None and QNetworkRequest is not None:
|
||||||
|
req = QNetworkRequest(QUrl(url))
|
||||||
|
reply = manager.get(req)
|
||||||
|
if QEventLoop is not None:
|
||||||
|
loop = QEventLoop()
|
||||||
|
reply.finished.connect(loop.quit)
|
||||||
|
loop.exec()
|
||||||
|
try:
|
||||||
|
raw = reply.readAll()
|
||||||
|
data_bytes = bytes(raw) if hasattr(raw, "__bytes__") else raw
|
||||||
|
response_text = data_bytes.decode("utf-8", errors="replace")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
response_text = reply.text()
|
||||||
|
except Exception:
|
||||||
|
response_text = None
|
||||||
|
except Exception as exc:
|
||||||
|
http_error = f"QgsNetworkAccessManager error: {exc}"
|
||||||
|
response_text = None
|
||||||
|
|
||||||
|
# Fallback: requests
|
||||||
|
if response_text is None:
|
||||||
|
try:
|
||||||
|
import requests # lokal import, keine harte Abhängigkeit
|
||||||
|
r = requests.get(url, timeout=30)
|
||||||
|
r.raise_for_status()
|
||||||
|
response_text = r.text
|
||||||
|
except Exception as exc:
|
||||||
|
http_error = f"requests error: {exc}"
|
||||||
|
response_text = None
|
||||||
|
|
||||||
|
if response_text is None:
|
||||||
|
return [], http_error or "keine Antwort vom Server"
|
||||||
|
|
||||||
|
# Versuche JSON/GeoJSON zu parsen
|
||||||
|
try:
|
||||||
|
parsed = json.loads(response_text)
|
||||||
|
if isinstance(parsed, dict) and parsed.get("type") == "FeatureCollection":
|
||||||
|
return parsed.get("features", []), None
|
||||||
|
if isinstance(parsed, dict) and "features" in parsed:
|
||||||
|
return parsed.get("features", []), None
|
||||||
|
# Sonst: gib das gesamte JSON als einzelnes Objekt zurück
|
||||||
|
return [parsed], None
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# Nicht-JSON-Antwort (z. B. GML). Wenn QGIS verfügbar, versuche GML via temporärer Datei + OGR
|
||||||
|
if getattr(qgiscore, "QGIS_AVAILABLE", False):
|
||||||
|
try:
|
||||||
|
import tempfile
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".gml", delete=False, mode="w", encoding="utf-8") as fh:
|
||||||
|
fh.write(response_text)
|
||||||
|
tmp_path = fh.name
|
||||||
|
layer = qgiscore.QgsVectorLayer(tmp_path, "tmp_gml", "ogr")
|
||||||
|
if layer and getattr(layer, "isValid", lambda: False)():
|
||||||
|
for feat in layer.getFeatures():
|
||||||
|
features.append(feat)
|
||||||
|
return features, None
|
||||||
|
return [], "GML-Antwort konnte nicht als Layer geladen werden"
|
||||||
|
except Exception as exc:
|
||||||
|
return [], f"Fehler beim Parsen von GML: {exc}"
|
||||||
|
# Wenn alles fehlschlägt:
|
||||||
|
return [], "Antwort konnte nicht als JSON oder GML geparst werden"
|
||||||
1
modules/Datenbankpruefer.py
Normal file
1
modules/Datenbankpruefer.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#Datenbankpruefer.py
|
||||||
435
modules/Datenschreiber.py
Normal file
435
modules/Datenschreiber.py
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
# sn_basis/modules/Datenschreiber.py
|
||||||
|
"""
|
||||||
|
Modul Datenschreiber
|
||||||
|
|
||||||
|
Enthält die Klasse Datenschreiber mit drei Hauptmethoden:
|
||||||
|
|
||||||
|
- schreibe_Daten: schreibt die abgerufenen Daten in die Ziel-GPKG/Dateien,
|
||||||
|
fragt bei vorhandenen Layern nach Überschreiben/Anhängen/Abbrechen und
|
||||||
|
legt Stile in der Datenbank ab.
|
||||||
|
- lade_Layer: lädt die erzeugten/aktualisierten Layer ins Projekt und
|
||||||
|
wendet die Vorgabestile an; sortiert abschließend die Layer.
|
||||||
|
- schreibe_log: schreibt die verarbeiteten Pruefergebnisse strukturiert in
|
||||||
|
eine Log-Datei im angegebenen Speicherort.
|
||||||
|
|
||||||
|
Die Implementierung verwendet die Wrapper-APIs:
|
||||||
|
- qgiscore_wrapper als qgiscore
|
||||||
|
- qgisui_wrapper als qgisui (nur wenn nötig)
|
||||||
|
- qt_wrapper als qt
|
||||||
|
|
||||||
|
Wichtig
|
||||||
|
------
|
||||||
|
Alle Nutzerinteraktionen (z. B. Überschreiben / Anhängen / Abbrechen) werden
|
||||||
|
zentral über den Pruefmanager gebündelt. Die Methode `ask_overwrite_append_cancel`
|
||||||
|
des Pruefmanagers wird verwendet, damit UI-Interaktionen an einer Stelle
|
||||||
|
konsolidiert und testbar sind.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from sn_basis.functions import qgiscore_wrapper as qgiscore
|
||||||
|
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
||||||
|
|
||||||
|
|
||||||
|
class Datenschreiber:
|
||||||
|
"""
|
||||||
|
Schreibt abgerufene Fachdaten in die Zieldatenbank/Dateien und lädt
|
||||||
|
die Layer ins Projekt.
|
||||||
|
|
||||||
|
Konstruktor
|
||||||
|
----------
|
||||||
|
pruefmanager:
|
||||||
|
Instanz des Pruefmanagers; wird verwendet, um Pruefergebnisse zu
|
||||||
|
verarbeiten und Nutzerinteraktionen zu zentralisieren.
|
||||||
|
gpkg_path:
|
||||||
|
Pfad zur Ziel-GPKG-Datei (oder Verzeichnis). Wenn None, muss der
|
||||||
|
Aufrufer einen Speicherort übergeben.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, pruefmanager: Any, gpkg_path: Optional[str] = None) -> None:
|
||||||
|
self.pruefmanager = pruefmanager
|
||||||
|
self.gpkg_path = gpkg_path
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Schreibe Daten
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
def schreibe_Daten(
|
||||||
|
self,
|
||||||
|
daten_dict: Dict[str, Any],
|
||||||
|
processed_results: List[Any],
|
||||||
|
speicherort: str,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Schreibt die abgerufenen Daten in die Zieldatenbank/Dateien.
|
||||||
|
|
||||||
|
Ablauf
|
||||||
|
------
|
||||||
|
Für jede Zeile (ident) in ``daten_dict["daten"]``:
|
||||||
|
1. Bestimme Ziel-Layername (z. B. Thema oder ident).
|
||||||
|
2. Prüfe, ob ein Layer mit diesem Namen bereits existiert (Wrapper).
|
||||||
|
3. Falls vorhanden, frage den Benutzer (Überschreiben / Anhängen / Abbrechen)
|
||||||
|
über die zentrale Pruefmanager-Methode `ask_overwrite_append_cancel`.
|
||||||
|
4. Führe die gewählte Operation aus oder schreibe den Layer, wenn er noch nicht existiert.
|
||||||
|
5. Schreibe ggf. den Stil in die GPKG und setze ihn als Vorgabe.
|
||||||
|
6. Sammle und gib eine Liste der angelegten/geänderten Layer zurück.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
List[Dict[str, Any]]
|
||||||
|
Liste von Dicts mit Informationen zu jedem angelegten/geänderten Layer.
|
||||||
|
"""
|
||||||
|
if not speicherort:
|
||||||
|
raise ValueError("Ein gültiger Speicherort (speicherort) muss übergeben werden.")
|
||||||
|
|
||||||
|
# Setze gpkg_path falls noch nicht vorhanden
|
||||||
|
if not self.gpkg_path:
|
||||||
|
self.gpkg_path = speicherort
|
||||||
|
|
||||||
|
results: List[Dict[str, Any]] = []
|
||||||
|
daten_map: Dict[str, List[Any]] = daten_dict.get("daten", {})
|
||||||
|
|
||||||
|
# Iteriere über alle Einträge
|
||||||
|
for ident, features in daten_map.items():
|
||||||
|
# Thema/Name ableiten (falls vorhanden in processed_results oder ident)
|
||||||
|
thema = None
|
||||||
|
for pe in processed_results:
|
||||||
|
try:
|
||||||
|
kontext = getattr(pe, "kontext", None) or {}
|
||||||
|
if kontext and kontext.get("ident") == ident:
|
||||||
|
thema = kontext.get("thema")
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if not thema:
|
||||||
|
thema = str(ident)
|
||||||
|
|
||||||
|
layer_name = thema
|
||||||
|
|
||||||
|
# Prüfe, ob Layer bereits existiert in der Ziel-GPKG
|
||||||
|
layer_exists = False
|
||||||
|
try:
|
||||||
|
layer_exists_fn = getattr(qgiscore, "layer_exists_in_gpkg", None)
|
||||||
|
if callable(layer_exists_fn):
|
||||||
|
layer_exists = layer_exists_fn(self.gpkg_path, layer_name)
|
||||||
|
else:
|
||||||
|
# Fallback: QGIS-Fallback-Check via QgsVectorLayer
|
||||||
|
if getattr(qgiscore, "QgsVectorLayer", None) is not None and qgiscore.QGIS_AVAILABLE:
|
||||||
|
uri = f"{self.gpkg_path}|layername={layer_name}"
|
||||||
|
layer = qgiscore.QgsVectorLayer(uri, layer_name, "ogr")
|
||||||
|
layer_exists = bool(layer and getattr(layer, "isValid", lambda: False)())
|
||||||
|
except Exception:
|
||||||
|
layer_exists = False
|
||||||
|
|
||||||
|
operation = "created"
|
||||||
|
|
||||||
|
if layer_exists:
|
||||||
|
# Zentrale Nutzerabfrage über Pruefmanager
|
||||||
|
# Erwartet Rückgabe: "overwrite" | "append" | "cancel"
|
||||||
|
try:
|
||||||
|
user_choice = self.pruefmanager.ask_overwrite_append_cancel(layer_name)
|
||||||
|
except Exception:
|
||||||
|
# Fallback: overwrite, falls Pruefmanager nicht verfügbar
|
||||||
|
user_choice = "overwrite"
|
||||||
|
|
||||||
|
if user_choice == "cancel":
|
||||||
|
operation = "skipped"
|
||||||
|
results.append({
|
||||||
|
"ident": ident,
|
||||||
|
"thema": thema,
|
||||||
|
"operation": operation,
|
||||||
|
"layer_path": f"{self.gpkg_path}|layername={layer_name}",
|
||||||
|
"feature_count": 0,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
if user_choice == "overwrite":
|
||||||
|
write_err = self._write_layer_to_gpkg(layer_name, features, mode="overwrite")
|
||||||
|
if write_err:
|
||||||
|
pe_err = pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung=f"Fehler beim Überschreiben von {layer_name}: {write_err}",
|
||||||
|
aktion="save_exception",
|
||||||
|
kontext={"ident": ident, "thema": thema, "error": write_err},
|
||||||
|
)
|
||||||
|
self.pruefmanager.verarbeite(pe_err)
|
||||||
|
operation = "skipped"
|
||||||
|
results.append({
|
||||||
|
"ident": ident,
|
||||||
|
"thema": thema,
|
||||||
|
"operation": operation,
|
||||||
|
"layer_path": f"{self.gpkg_path}|layername={layer_name}",
|
||||||
|
"feature_count": 0,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
operation = "overwritten"
|
||||||
|
|
||||||
|
elif user_choice == "append":
|
||||||
|
write_err = self._write_layer_to_gpkg(layer_name, features, mode="append")
|
||||||
|
if write_err:
|
||||||
|
pe_err = pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung=f"Fehler beim Anhängen an {layer_name}: {write_err}",
|
||||||
|
aktion="save_exception",
|
||||||
|
kontext={"ident": ident, "thema": thema, "error": write_err},
|
||||||
|
)
|
||||||
|
self.pruefmanager.verarbeite(pe_err)
|
||||||
|
operation = "skipped"
|
||||||
|
results.append({
|
||||||
|
"ident": ident,
|
||||||
|
"thema": thema,
|
||||||
|
"operation": operation,
|
||||||
|
"layer_path": f"{self.gpkg_path}|layername={layer_name}",
|
||||||
|
"feature_count": 0,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
operation = "appended"
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Layer existiert nicht -> neu anlegen
|
||||||
|
write_err = self._write_layer_to_gpkg(layer_name, features, mode="create")
|
||||||
|
if write_err:
|
||||||
|
pe_err = pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung=f"Fehler beim Erstellen von {layer_name}: {write_err}",
|
||||||
|
aktion="save_exception",
|
||||||
|
kontext={"ident": ident, "thema": thema, "error": write_err},
|
||||||
|
)
|
||||||
|
self.pruefmanager.verarbeite(pe_err)
|
||||||
|
operation = "skipped"
|
||||||
|
results.append({
|
||||||
|
"ident": ident,
|
||||||
|
"thema": thema,
|
||||||
|
"operation": operation,
|
||||||
|
"layer_path": f"{self.gpkg_path}|layername={layer_name}",
|
||||||
|
"feature_count": 0,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
operation = "created"
|
||||||
|
|
||||||
|
# Stilbehandlung (falls in processed_results referenziert)
|
||||||
|
style_written = False
|
||||||
|
style_path = None
|
||||||
|
for pe in processed_results:
|
||||||
|
try:
|
||||||
|
kontext = getattr(pe, "kontext", None) or {}
|
||||||
|
if kontext and kontext.get("ident") == ident:
|
||||||
|
style_path = kontext.get("stildatei") or kontext.get("Stildatei")
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if style_path:
|
||||||
|
if not os.path.isabs(style_path):
|
||||||
|
base_dir = os.path.dirname(__file__)
|
||||||
|
style_path = os.path.join(base_dir, style_path)
|
||||||
|
write_style_fn = getattr(qgiscore, "write_style_to_gpkg", None)
|
||||||
|
if callable(write_style_fn):
|
||||||
|
try:
|
||||||
|
write_style_fn(self.gpkg_path, style_path, layer_name)
|
||||||
|
style_written = True
|
||||||
|
except Exception:
|
||||||
|
style_written = False
|
||||||
|
|
||||||
|
feature_count = len(features) if isinstance(features, list) else 0
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"ident": ident,
|
||||||
|
"thema": thema,
|
||||||
|
"operation": operation,
|
||||||
|
"layer_path": f"{self.gpkg_path}|layername={layer_name}",
|
||||||
|
"feature_count": feature_count,
|
||||||
|
"style_written": style_written,
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Lade Layer ins Projekt
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
def lade_Layer(self, layer_infos: List[Dict[str, Any]]) -> None:
|
||||||
|
"""
|
||||||
|
Lädt die in schreibe_Daten erzeugten/aktualisierten Layer ins Projekt
|
||||||
|
und wendet die Vorgabestile an.
|
||||||
|
"""
|
||||||
|
loaded_layers = []
|
||||||
|
|
||||||
|
for info in layer_infos:
|
||||||
|
layer_path = info.get("layer_path")
|
||||||
|
thema = info.get("thema")
|
||||||
|
if not layer_path:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
layer = qgiscore.QgsVectorLayer(layer_path, thema, "ogr")
|
||||||
|
if not layer or not getattr(layer, "isValid", lambda: False)():
|
||||||
|
pe_err = pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung=f"Layer {thema} konnte nicht geladen werden",
|
||||||
|
aktion="layer_nicht_gefunden",
|
||||||
|
kontext={"layer_path": layer_path},
|
||||||
|
)
|
||||||
|
self.pruefmanager.verarbeite(pe_err)
|
||||||
|
continue
|
||||||
|
except Exception as exc:
|
||||||
|
pe_err = pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung=f"Fehler beim Erzeugen des Layers {thema}: {exc}",
|
||||||
|
aktion="layer_nicht_gefunden",
|
||||||
|
kontext={"layer_path": layer_path, "error": str(exc)},
|
||||||
|
)
|
||||||
|
self.pruefmanager.verarbeite(pe_err)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
apply_style_fn = getattr(qgiscore, "apply_default_style_from_gpkg", None)
|
||||||
|
if callable(apply_style_fn):
|
||||||
|
apply_style_fn(self.gpkg_path, layer)
|
||||||
|
except Exception:
|
||||||
|
pe_warn = pruef_ergebnis(
|
||||||
|
ok=True,
|
||||||
|
meldung=f"Style konnte für {thema} nicht automatisch angewendet werden",
|
||||||
|
aktion="stil_not_implemented",
|
||||||
|
kontext={"thema": thema},
|
||||||
|
)
|
||||||
|
self.pruefmanager.verarbeite(pe_warn)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# qgisui wrapper wird hier nicht direkt für die Abfrage verwendet;
|
||||||
|
# qgisui.add_layer_to_project sollte aber vorhanden sein.
|
||||||
|
from sn_basis.functions import qgisui_wrapper as qgisui
|
||||||
|
add_fn = getattr(qgisui, "add_layer_to_project", None)
|
||||||
|
if callable(add_fn):
|
||||||
|
add_fn(layer)
|
||||||
|
else:
|
||||||
|
# Fallback: falls wrapper nicht vorhanden, versuche QGIS-API direkt
|
||||||
|
if getattr(qgiscore, "QgsProject", None) is not None and qgiscore.QGIS_AVAILABLE:
|
||||||
|
qgiscore.QgsProject.instance().addMapLayer(layer)
|
||||||
|
loaded_layers.append(layer)
|
||||||
|
except Exception:
|
||||||
|
pe_err = pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung=f"Layer {thema} konnte nicht ins Projekt geladen werden",
|
||||||
|
aktion="layer_nicht_gefunden",
|
||||||
|
kontext={"thema": thema},
|
||||||
|
)
|
||||||
|
self.pruefmanager.verarbeite(pe_err)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Sortiere Layer im Projekt nach ID (Wrapper-Funktion bevorzugt)
|
||||||
|
sort_fn = getattr(qgiscore, "sort_layers_by_id", None)
|
||||||
|
if callable(sort_fn):
|
||||||
|
try:
|
||||||
|
sort_fn()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Schreibe Log
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
def schreibe_log(self, processed_results: List[Any], speicherort: str) -> str:
|
||||||
|
"""
|
||||||
|
Schreibt die verarbeiteten Pruefergebnisse strukturiert in eine Log-Datei.
|
||||||
|
"""
|
||||||
|
if not speicherort:
|
||||||
|
raise ValueError("Ein gültiger Speicherort muss übergeben werden.")
|
||||||
|
|
||||||
|
log_dir = speicherort
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
log_path = os.path.join(log_dir, f"datenabruf_log_{timestamp}.json")
|
||||||
|
|
||||||
|
serializable: List[Dict[str, Any]] = []
|
||||||
|
for pe in processed_results:
|
||||||
|
try:
|
||||||
|
entry = {}
|
||||||
|
entry["ok"] = getattr(pe, "ok", None) if hasattr(pe, "ok") else None
|
||||||
|
entry["meldung"] = getattr(pe, "meldung", None) if hasattr(pe, "meldung") else None
|
||||||
|
kontext = getattr(pe, "kontext", None) if hasattr(pe, "kontext") else None
|
||||||
|
entry["kontext"] = kontext
|
||||||
|
serializable.append(entry)
|
||||||
|
except Exception:
|
||||||
|
serializable.append({"raw": str(pe)})
|
||||||
|
|
||||||
|
with open(log_path, "w", encoding="utf-8") as fh:
|
||||||
|
json.dump(serializable, fh, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
pe_log = pruef_ergebnis(
|
||||||
|
ok=True,
|
||||||
|
meldung=f"Log geschrieben: {os.path.basename(log_path)}",
|
||||||
|
aktion="standarddatei_vorschlagen",
|
||||||
|
kontext={"log_path": log_path},
|
||||||
|
)
|
||||||
|
self.pruefmanager.verarbeite(pe_log)
|
||||||
|
|
||||||
|
return log_path
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Hilfsfunktionen intern
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
def _write_layer_to_gpkg(self, layer_name: str, features: List[Any], mode: str = "create") -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Interne Hilfsfunktion zum Schreiben eines Layers in das GPKG.
|
||||||
|
|
||||||
|
Erwartete qgiscore-Funktion:
|
||||||
|
qgiscore.write_features_to_gpkg(gpkg_path, layer_name, features, mode)
|
||||||
|
"""
|
||||||
|
write_fn = getattr(qgiscore, "write_features_to_gpkg", None)
|
||||||
|
if callable(write_fn):
|
||||||
|
try:
|
||||||
|
write_fn(self.gpkg_path, layer_name, features, mode)
|
||||||
|
return None
|
||||||
|
except Exception as exc:
|
||||||
|
return str(exc)
|
||||||
|
|
||||||
|
# Fallback: Verwende QgsVectorFileWriter, falls QGIS verfügbar
|
||||||
|
if getattr(qgiscore, "QGIS_AVAILABLE", False) and getattr(qgiscore, "QgsVectorFileWriter", None) is not None:
|
||||||
|
try:
|
||||||
|
# Minimaler Fallback: erwarte, dass 'features' eine Liste von QgsFeature ist
|
||||||
|
if not features:
|
||||||
|
# Erstelle leeren Layer-Eintrag (GPKG erlaubt leere Layer)
|
||||||
|
# Hier vereinfachen wir: writeAsVectorFormatV3 benötigt ein Layer-Objekt.
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Versuche, ein Memory-Layer aus dem ersten Feature zu ermitteln
|
||||||
|
first = features[0]
|
||||||
|
mem_layer = None
|
||||||
|
if hasattr(first, "fields") and hasattr(first, "geometry"):
|
||||||
|
# Wenn Features QgsFeature sind, versuchen wir, das zugehörige Layer zu nutzen
|
||||||
|
try:
|
||||||
|
mem_layer = first.layer() if hasattr(first, "layer") else None
|
||||||
|
except Exception:
|
||||||
|
mem_layer = None
|
||||||
|
|
||||||
|
if mem_layer is None:
|
||||||
|
return "Keine Feld-/Geometrie-Informationen zum Schreiben vorhanden"
|
||||||
|
|
||||||
|
opts = qgiscore.QgsVectorFileWriter.SaveVectorOptions()
|
||||||
|
opts.driverName = "GPKG"
|
||||||
|
opts.layerName = layer_name
|
||||||
|
opts.fileEncoding = "UTF-8"
|
||||||
|
if mode == "overwrite":
|
||||||
|
opts.actionOnExistingFile = qgiscore.QgsVectorFileWriter.CreateOrOverwriteFile
|
||||||
|
else:
|
||||||
|
opts.actionOnExistingFile = qgiscore.QgsVectorFileWriter.CreateOrOverwriteLayer
|
||||||
|
|
||||||
|
err = qgiscore.QgsVectorFileWriter.writeAsVectorFormatV3(
|
||||||
|
mem_layer,
|
||||||
|
self.gpkg_path,
|
||||||
|
qgiscore.QgsProject.instance().transformContext(),
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
if err != qgiscore.QgsVectorFileWriter.NoError:
|
||||||
|
return f"Fehler beim Schreiben (Code {err})"
|
||||||
|
return None
|
||||||
|
except Exception as exc:
|
||||||
|
return str(exc)
|
||||||
|
|
||||||
|
return "Keine Schreib-Funktion verfügbar (Wrapper nicht implementiert)"
|
||||||
218
modules/Pruefmanager.py
Normal file
218
modules/Pruefmanager.py
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
"""
|
||||||
|
sn_basis/modules/Pruefmanager.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import Optional, Any
|
||||||
|
|
||||||
|
from sn_basis.functions import ask_yes_no, info, warning, error, ask_overwrite_append_cancel_custom
|
||||||
|
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis, PruefAktion
|
||||||
|
print("DEBUG: Pruefmanager DATEI GELADEN:", __file__)
|
||||||
|
|
||||||
|
class Pruefmanager:
|
||||||
|
def __init__(self, ui_modus: str = "qgis", parent: Optional[Any] = None) -> None:
|
||||||
|
self.ui_modus = ui_modus
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Meldungen / Zusammenfassungen
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def report_error(
|
||||||
|
self,
|
||||||
|
thema: str,
|
||||||
|
meldung: str,
|
||||||
|
*,
|
||||||
|
aktion: Optional[PruefAktion] = None,
|
||||||
|
kontext: Optional[Any] = None,
|
||||||
|
) -> None:
|
||||||
|
critical_actions = {
|
||||||
|
"netzwerkfehler", "pruefe_exception", "save_exception",
|
||||||
|
"layer_create_failed", "read_error", "open_error",
|
||||||
|
}
|
||||||
|
warn_actions = {
|
||||||
|
"datei_nicht_gefunden", "pfad_nicht_gefunden", "url_nicht_erreichbar",
|
||||||
|
"falsche_endung", "kein_header", "kein_arbeitsblatt",
|
||||||
|
}
|
||||||
|
|
||||||
|
if aktion in critical_actions:
|
||||||
|
error(thema, meldung)
|
||||||
|
return
|
||||||
|
if aktion in warn_actions:
|
||||||
|
warning(thema, meldung)
|
||||||
|
return
|
||||||
|
warning(thema, meldung)
|
||||||
|
|
||||||
|
def report_summary(self, summary: dict) -> None:
|
||||||
|
geladen = summary.get("geladen", [])
|
||||||
|
fehler = summary.get("fehler", {})
|
||||||
|
ausserhalb = summary.get("ausserhalb", [])
|
||||||
|
relevant = summary.get("relevant", [])
|
||||||
|
|
||||||
|
message = (
|
||||||
|
f"Geladene Dienste: {len(geladen)}\n"
|
||||||
|
f"Relevante Dienste: {len(relevant)}\n"
|
||||||
|
f"Dienste ausserhalb: {len(ausserhalb)}\n"
|
||||||
|
f"Fehler: {len(fehler)}"
|
||||||
|
)
|
||||||
|
info("DataGrabber Zusammenfassung", message)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# VERFAHRENS-DB-spezifische Entscheidungen
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _handle_datei_existiert(self, ergebnis: pruef_ergebnis) -> pruef_ergebnis:
|
||||||
|
"""Handhabt das Szenario, dass die Ziel-Verfahrens-DB bereits existiert.
|
||||||
|
|
||||||
|
Zeigt einen einzigen Dialog mit drei Optionen an:
|
||||||
|
- **Überschreiben**: Bestehende Layer ersetzen (entspricht YES)
|
||||||
|
- **Anhängen**: Neue Layer zur Datei hinzufügen (entspricht NO)
|
||||||
|
- **Abbrechen**: Vorgang beenden (entspricht CANCEL)
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
ergebnis : pruef_ergebnis
|
||||||
|
Eingabe-Ergebnis mit Dateipfad im ``kontext``-Attribut.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
pruef_ergebnis
|
||||||
|
Ergebnis mit Aktion:
|
||||||
|
- ``datei_existiert_ueberschreiben``
|
||||||
|
- ``datei_existiert_anhaengen``
|
||||||
|
- ``datei_existiert_ueberspringen`` (für Cancel-Fall)
|
||||||
|
"""
|
||||||
|
if self.ui_modus != "qgis":
|
||||||
|
return ergebnis
|
||||||
|
|
||||||
|
pfad = ergebnis.kontext
|
||||||
|
pfad_str = str(pfad) if pfad else "unbekannt"
|
||||||
|
|
||||||
|
titel = "Verfahrens-DB existiert bereits"
|
||||||
|
meldung = (
|
||||||
|
f"Die Datei '{pfad_str}' existiert bereits.\n\n"
|
||||||
|
"Was soll geschehen?\n\n"
|
||||||
|
"• **Überschreiben**: Bestehende Layer ersetzen\n"
|
||||||
|
"• **Anhängen**: Neue Layer hinzufügen\n"
|
||||||
|
"• **Abbrechen**: Vorgang beenden"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Einzelner Dialog mit drei Optionen
|
||||||
|
entscheidung = ask_overwrite_append_cancel_custom(
|
||||||
|
parent=self.parent,
|
||||||
|
title=titel,
|
||||||
|
message=meldung
|
||||||
|
)
|
||||||
|
|
||||||
|
if entscheidung == "overwrite":
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=True,
|
||||||
|
aktion="datei_existiert_ueberschreiben",
|
||||||
|
kontext=ergebnis.kontext,
|
||||||
|
)
|
||||||
|
elif entscheidung == "append":
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=True,
|
||||||
|
aktion="datei_existiert_anhaengen",
|
||||||
|
kontext=ergebnis.kontext,
|
||||||
|
)
|
||||||
|
else: # cancel
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=True,
|
||||||
|
aktion="datei_existiert_ueberspringen",
|
||||||
|
kontext=ergebnis.kontext,
|
||||||
|
)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Basis-Entscheidungen (KORREKT: → pruef_ergebnis)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _handle_basic_decision(self, ergebnis: pruef_ergebnis) -> pruef_ergebnis:
|
||||||
|
"""Basis-Entscheidung für einfache Ja/Nein-Fragen."""
|
||||||
|
print(f"DEBUG _handle_basic_decision: aktion='{ergebnis.aktion}', ui_modus='{self.ui_modus}'")
|
||||||
|
|
||||||
|
if self.ui_modus != "qgis":
|
||||||
|
print("DEBUG: Nicht QGIS → ergebnis unverändert")
|
||||||
|
return ergebnis
|
||||||
|
|
||||||
|
title_map = {
|
||||||
|
"leereingabe_erlaubt": "Ohne Eingabe fortfahren",
|
||||||
|
"standarddatei_vorschlagen": "Standarddatei verwenden",
|
||||||
|
"temporaer_erlaubt": "Temporäre Layer erzeugen",
|
||||||
|
"layer_unsichtbar": "Layer einblenden",
|
||||||
|
}
|
||||||
|
|
||||||
|
title = title_map.get(ergebnis.aktion, "Entscheidung erforderlich")
|
||||||
|
meldung = ergebnis.meldung or ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"DEBUG ask_yes_no: title='{title}', meldung='{meldung[:50]}...'")
|
||||||
|
yes = ask_yes_no(title, meldung, default=False, parent=self.parent)
|
||||||
|
print(f"DEBUG ask_yes_no: yes={yes}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"DEBUG ask_yes_no Exception: {e}")
|
||||||
|
return ergebnis
|
||||||
|
|
||||||
|
if not yes:
|
||||||
|
print("DEBUG: Nutzer sagte Nein → ok=False")
|
||||||
|
return ergebnis
|
||||||
|
|
||||||
|
# Nutzer sagte Ja
|
||||||
|
if ergebnis.aktion == "temporaer_erlaubt":
|
||||||
|
print("DEBUG: temporaer_erlaubt bestätigt → ok=True")
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=True,
|
||||||
|
aktion="temporaer_erlaubt",
|
||||||
|
kontext=ergebnis.kontext
|
||||||
|
)
|
||||||
|
|
||||||
|
print("DEBUG: Andere Aktion bestätigt → ok=True, aktion='ok'")
|
||||||
|
return pruef_ergebnis(
|
||||||
|
ok=True,
|
||||||
|
aktion="ok",
|
||||||
|
kontext=ergebnis.kontext
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Hauptlogik: verarbeite() (KORRIGIERT!)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def verarbeite(self, ergebnis: pruef_ergebnis) -> pruef_ergebnis:
|
||||||
|
print("🔥 verarbeite() START")
|
||||||
|
print("DEBUG Pruefmanager:", ergebnis.ok, ergebnis.aktion)
|
||||||
|
print("DEBUG ergebnis.aktion TYPE:", type(ergebnis.aktion), repr(ergebnis.aktion))
|
||||||
|
|
||||||
|
# 1. Erfolg → direkt weiter
|
||||||
|
print("🔍 Schritt 1: Prüfe ergebnis.ok =", ergebnis.ok)
|
||||||
|
if ergebnis.ok:
|
||||||
|
print("✅ Schritt 1: ok=True → return")
|
||||||
|
return ergebnis
|
||||||
|
|
||||||
|
# 2. VERFAHRENS-DB: Bestehende Datei
|
||||||
|
print("🔍 Schritt 2: Prüfe datei_existiert =", ergebnis.aktion == "datei_existiert")
|
||||||
|
if ergebnis.aktion == "datei_existiert":
|
||||||
|
print("✅ Schritt 2: _handle_datei_existiert")
|
||||||
|
return self._handle_datei_existiert(ergebnis)
|
||||||
|
|
||||||
|
# 3. Basis interaktive Aktionen
|
||||||
|
print("🔍 Schritt 3: Definiere interactive_actions")
|
||||||
|
interactive_actions = {
|
||||||
|
"leereingabe_erlaubt",
|
||||||
|
"standarddatei_vorschlagen",
|
||||||
|
"temporaer_erlaubt",
|
||||||
|
"layer_unsichtbar",
|
||||||
|
}
|
||||||
|
print("DEBUG interactive_actions:", repr(interactive_actions))
|
||||||
|
print("DEBUG ergebnis.aktion in interactive_actions?", ergebnis.aktion in interactive_actions)
|
||||||
|
|
||||||
|
if ergebnis.aktion in interactive_actions:
|
||||||
|
print("✅ Schritt 3: Interaktive Aktion → _handle_basic_decision")
|
||||||
|
decision = self._handle_basic_decision(ergebnis)
|
||||||
|
print(f"DEBUG: _handle_basic_decision Ergebnis: ok={decision.ok}, aktion='{decision.aktion}'")
|
||||||
|
return decision
|
||||||
|
|
||||||
|
# 4. Fehler behandeln
|
||||||
|
print("❌ Schritt 4: FEHLER BEHANDELN")
|
||||||
|
self.report_error(
|
||||||
|
thema=ergebnis.aktion or "pruefung",
|
||||||
|
meldung=ergebnis.meldung or "",
|
||||||
|
aktion=ergebnis.aktion,
|
||||||
|
kontext=ergebnis.kontext,
|
||||||
|
)
|
||||||
|
print("🔥 verarbeite() ENDE mit ok=False")
|
||||||
|
return ergebnis
|
||||||
0
modules/__init__py
Normal file
0
modules/__init__py
Normal file
91
modules/excel_importer.py
Normal file
91
modules/excel_importer.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# sn_plan41/modules/excel_importer.py
|
||||||
|
import os
|
||||||
|
from typing import Optional, Iterable, Mapping, Any, List, cast
|
||||||
|
|
||||||
|
from openpyxl import load_workbook
|
||||||
|
from openpyxl.workbook.workbook import Workbook
|
||||||
|
from openpyxl.worksheet.worksheet import Worksheet
|
||||||
|
|
||||||
|
from sn_basis.modules.Dateipruefer import Dateipruefer
|
||||||
|
from sn_basis.modules.Pruefmanager import Pruefmanager
|
||||||
|
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
||||||
|
|
||||||
|
|
||||||
|
class ExcelImporter:
|
||||||
|
"""
|
||||||
|
Excel-Importer für Linklisten, verwendet Dateipruefer und Pruefmanager zur Meldungsbehandlung.
|
||||||
|
|
||||||
|
- Der Aufrufer übergibt einen konkreten Dateipfad.
|
||||||
|
- Vor dem Öffnen wird der Pfad mit Dateipruefer geprüft.
|
||||||
|
- Link- und Stilprüfungen erfolgen nicht hier, sondern im DataGrabber.
|
||||||
|
- Nach dem Ladevorgang wird die Arbeitsmappe geschlossen, damit die Datei vom OS freigegeben wird.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, filepath: str, pruefmanager: Pruefmanager):
|
||||||
|
if not filepath:
|
||||||
|
raise ValueError("ExcelImporter benötigt einen gültigen Dateipfad.")
|
||||||
|
if pruefmanager is None:
|
||||||
|
raise ValueError("ExcelImporter benötigt einen Pruefmanager.")
|
||||||
|
self.filepath = filepath
|
||||||
|
self.pruefmanager = pruefmanager
|
||||||
|
|
||||||
|
def import_xlsx(self) -> List[Mapping[str, Any]]:
|
||||||
|
"""
|
||||||
|
Liest die Excel-Datei und gibt eine Liste von Dicts (Zeilen) zurück.
|
||||||
|
Bei Prüf- oder Leseproblemen wird der Pruefmanager zur Verarbeitung des pruef_ergebnis aufgerufen.
|
||||||
|
Im Fehlerfall wird eine leere Liste zurückgegeben.
|
||||||
|
"""
|
||||||
|
# 1) Dateiprüfung über Dateipruefer
|
||||||
|
datei_pruefer = Dateipruefer(pfad=self.filepath, temporaer_erlaubt=False)
|
||||||
|
ergebnis: pruef_ergebnis = datei_pruefer.pruefe()
|
||||||
|
ergebnis = self.pruefmanager.verarbeite(ergebnis)
|
||||||
|
|
||||||
|
if not ergebnis.ok:
|
||||||
|
return []
|
||||||
|
|
||||||
|
workbook: Optional[Workbook] = None
|
||||||
|
try:
|
||||||
|
workbook = load_workbook(filename=self.filepath, data_only=True)
|
||||||
|
|
||||||
|
# workbook.active kann typmäßig als Optional angesehen werden; cast/prüfen, damit Pylance weiß, dass sheet ein Worksheet ist
|
||||||
|
sheet = workbook.active
|
||||||
|
if sheet is None:
|
||||||
|
pe = pruef_ergebnis(ok=False, meldung=f"Kein aktives Blatt in der Arbeitsmappe: {self.filepath}", aktion="kein_arbeitsblatt", kontext=self.filepath)
|
||||||
|
self.pruefmanager.verarbeite(pe)
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Typengranularität für den Linter
|
||||||
|
sheet = cast(Worksheet, sheet)
|
||||||
|
|
||||||
|
# Header aus erster Zeile (als Werte)
|
||||||
|
header_row = next(sheet.iter_rows(min_row=1, max_row=1, values_only=True), None)
|
||||||
|
if not header_row:
|
||||||
|
pe = pruef_ergebnis(ok=False, meldung=f"Excel-Datei enthält keine Header-Zeile: {self.filepath}", aktion="kein_header", kontext=self.filepath)
|
||||||
|
self.pruefmanager.verarbeite(pe)
|
||||||
|
return []
|
||||||
|
|
||||||
|
header = list(header_row)
|
||||||
|
if not header or all(h is None for h in header):
|
||||||
|
pe = pruef_ergebnis(ok=False, meldung=f"Excel-Header ist leer oder ungültig: {self.filepath}", aktion="kein_header", kontext=self.filepath)
|
||||||
|
self.pruefmanager.verarbeite(pe)
|
||||||
|
return []
|
||||||
|
|
||||||
|
ergebnis_list: List[Mapping[str, Any]] = []
|
||||||
|
# Werte-only lesen für Performance und Einfachheit
|
||||||
|
for row in sheet.iter_rows(min_row=2, values_only=True):
|
||||||
|
if row is None:
|
||||||
|
continue
|
||||||
|
# zip stoppt bei kürzerer Länge; das ist beabsichtigt
|
||||||
|
attributes = dict(zip(header, row))
|
||||||
|
ergebnis_list.append(attributes)
|
||||||
|
|
||||||
|
return ergebnis_list
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
pe = pruef_ergebnis(ok=False, meldung=f"Fehler beim Lesen der Excel-Datei '{self.filepath}': {exc}", aktion="read_error", kontext=self.filepath)
|
||||||
|
self.pruefmanager.verarbeite(pe)
|
||||||
|
return []
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if workbook is not None:
|
||||||
|
workbook.close()
|
||||||
182
modules/layerpruefer.py
Normal file
182
modules/layerpruefer.py
Normal 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 typing import Optional, Any
|
||||||
|
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:Optional[Any]=None,
|
||||||
|
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
134
modules/linkpruefer.py
Normal 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 OS-unabhä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)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# URL‑Prüfung
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
def _pruefe_url(self, url: str) -> pruef_ergebnis:
|
||||||
|
"""
|
||||||
|
Prüft eine URL über einen HEAD-Request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
179
modules/pruef_ergebnis.py
Normal file
179
modules/pruef_ergebnis.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
"""
|
||||||
|
sn_basis/modules/pruef_ergebnis.py
|
||||||
|
|
||||||
|
Erweitertes Ergebnisobjekt für Dateiprüfungen mit Verfahrens-DB-spezifischen Aktionen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, Optional, Literal
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Erweiterte PruefAktionen für Verfahrens-DB-Workflow
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
PruefAktion = Literal[
|
||||||
|
# Basis-Aktionen (bestehend)
|
||||||
|
"ok",
|
||||||
|
"leer",
|
||||||
|
"leereingabe_erlaubt",
|
||||||
|
"leereingabe_nicht_erlaubt",
|
||||||
|
"standarddatei_vorschlagen",
|
||||||
|
"temporaer_erlaubt",
|
||||||
|
"temporaer_erzeugen",
|
||||||
|
"datei_nicht_gefunden",
|
||||||
|
"kein_dateipfad",
|
||||||
|
"pfad_nicht_gefunden",
|
||||||
|
"url_nicht_erreichbar",
|
||||||
|
"netzwerkfehler",
|
||||||
|
|
||||||
|
# Layer-spezifisch
|
||||||
|
"layer_nicht_gefunden",
|
||||||
|
"layer_unsichtbar",
|
||||||
|
"falscher_geotyp",
|
||||||
|
"layer_leer",
|
||||||
|
"falscher_layertyp",
|
||||||
|
"falsches_crs",
|
||||||
|
"felder_fehlen",
|
||||||
|
"datenquelle_unerwartet",
|
||||||
|
"layer_nicht_editierbar",
|
||||||
|
|
||||||
|
# Dateiendung/Format
|
||||||
|
"falsche_endung",
|
||||||
|
"pflichtfelder_fehlen",
|
||||||
|
|
||||||
|
# Excel/Import
|
||||||
|
"kein_header",
|
||||||
|
"kein_arbeitsblatt",
|
||||||
|
"read_error",
|
||||||
|
"open_error",
|
||||||
|
"datenabruf",
|
||||||
|
|
||||||
|
# 🆕 VERFAHRENS-DB SPEZIFISCH (deine Anforderungen 2.d, 2.e)
|
||||||
|
"datei_wird_erzeugt", # 2.d: Pfad gültig, Datei fehlt → weiter
|
||||||
|
"datei_existiert", # Datei vorhanden → Layer-Entscheidung
|
||||||
|
"datei_existiert_ueberschreiben", # 2.e: Nutzer wählt "Überschreiben"
|
||||||
|
"datei_existiert_anhaengen", # 2.e: Nutzer wählt "Anhängen"
|
||||||
|
"datei_existiert_ueberspringen", # 2.e: Nutzer wählt "Überspringen"
|
||||||
|
|
||||||
|
# Generisch
|
||||||
|
"pruefe_exception",
|
||||||
|
"save_exception",
|
||||||
|
"save_not_implemented",
|
||||||
|
"stil_not_implemented",
|
||||||
|
"datei_unbekannt",
|
||||||
|
"needs_user_action",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class pruef_ergebnis:
|
||||||
|
"""
|
||||||
|
Einheitliches Ergebnisobjekt für Prüfer im Verfahrens-DB-Workflow.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
ok : bool
|
||||||
|
True wenn Prüfung bestanden und Pipeline fortgesetzt werden kann.
|
||||||
|
False signalisiert Fehler oder Nutzerentscheidung erforderlich.
|
||||||
|
meldung : Optional[str], optional
|
||||||
|
Menschenlesbare Meldung für UI-Dialoge (BY: Pruefmanager).
|
||||||
|
aktion : Optional[PruefAktion], optional
|
||||||
|
Maschinenlesbarer Aktionscode für nachfolgende Pipeline-Schritte.
|
||||||
|
kontext : Optional[Any], optional
|
||||||
|
Zusatzkontext: meist `pathlib.Path` für Dateipfade oder Layer-Objekte.
|
||||||
|
|
||||||
|
Verfahrens-DB-spezifische Aktionen:
|
||||||
|
|
||||||
|
+-----------------------------+-------------------------------------------------+
|
||||||
|
| Aktion | Bedeutung |
|
||||||
|
+=============================+=================================================+
|
||||||
|
| ``datei_wird_erzeugt`` | 2.d: Neues GPKG wird angelegt (Pfad gültig) |
|
||||||
|
+-----------------------------+-------------------------------------------------+
|
||||||
|
| ``datei_existiert`` | Datei vorhanden → Layer-Überschreibung prüfen |
|
||||||
|
+-----------------------------+-------------------------------------------------+
|
||||||
|
| ``datei_existiert_*`` | 2.e: Nutzerentscheidung für bestehende Datei |
|
||||||
|
+-----------------------------+-------------------------------------------------+
|
||||||
|
"""
|
||||||
|
|
||||||
|
ok: bool
|
||||||
|
meldung: Optional[str] = None
|
||||||
|
aktion: Optional[PruefAktion] = None
|
||||||
|
kontext: Optional[Any] = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
ok: bool,
|
||||||
|
meldung: Optional[str] = None,
|
||||||
|
aktion: Optional[PruefAktion] = None,
|
||||||
|
kontext: Optional[Any] = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Erstellt ein neues Prüfergebnis.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
ok : bool
|
||||||
|
True für "weiter mit Pipeline", False für "Entscheidung/Fehler".
|
||||||
|
meldung : Optional[str]
|
||||||
|
UI-Text für Nutzerdialoge.
|
||||||
|
aktion : Optional[PruefAktion]
|
||||||
|
Maschinenaktion für nachfolgende Verarbeitung.
|
||||||
|
kontext : Optional[Any]
|
||||||
|
Typischerweise `pathlib.Path` (Dateipfad) oder `QgsVectorLayer`.
|
||||||
|
"""
|
||||||
|
self.ok = ok
|
||||||
|
self.meldung = meldung
|
||||||
|
self.aktion = aktion
|
||||||
|
self.kontext = kontext
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ist_verfahrens_db_aktion(self) -> bool:
|
||||||
|
"""
|
||||||
|
Prüft, ob es sich um eine Verfahrens-DB-spezifische Aktion handelt.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True für ``datei_wird_erzeugt`` oder ``datei_existiert*``.
|
||||||
|
"""
|
||||||
|
return self.aktion in {
|
||||||
|
"datei_wird_erzeugt",
|
||||||
|
"datei_existiert",
|
||||||
|
"datei_existiert_ueberschreiben",
|
||||||
|
"datei_existiert_anhaengen",
|
||||||
|
"datei_existiert_ueberspringen",
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dateipfad(self) -> Optional[Path]:
|
||||||
|
"""
|
||||||
|
Extrahiert den Dateipfad aus dem Kontext (falls vorhanden).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Optional[Path]
|
||||||
|
`Path`-Objekt oder None.
|
||||||
|
"""
|
||||||
|
if isinstance(self.kontext, Path):
|
||||||
|
return self.kontext
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def erlaubte_persistierung(self) -> bool:
|
||||||
|
"""
|
||||||
|
Prüft, ob die Pipeline Daten persistieren darf.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True für ``datei_wird_erzeugt``, ``datei_existiert_ueberschreiben``,
|
||||||
|
``datei_existiert_anhaengen``.
|
||||||
|
"""
|
||||||
|
return self.aktion in {
|
||||||
|
"datei_wird_erzeugt",
|
||||||
|
"datei_existiert_ueberschreiben",
|
||||||
|
"datei_existiert_anhaengen",
|
||||||
|
}
|
||||||
75
modules/stilpruefer.py
Normal file
75
modules/stilpruefer.py
Normal 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.sys_wrapper 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,
|
||||||
|
)
|
||||||
609
styles/GIS_63000F_Objekt_Denkmalschutz.qml
Normal file
609
styles/GIS_63000F_Objekt_Denkmalschutz.qml
Normal 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=""gml_id"">
|
||||||
|
<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
225
styles/GIS_Biotope_F.qml
Normal 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>
|
||||||
349
styles/GIS_Flst_Beschriftung_ALKIS_NAS.qml
Normal file
349
styles/GIS_Flst_Beschriftung_ALKIS_NAS.qml
Normal 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=""flurstuecksnummer_ax_flurstuecksnummer_nenner" 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="<" 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="<symbol name="symbol" type="line" 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="{3356d22c-5f69-4911-8e91-fcf32e8243fa}" class="SimpleLine" locked="0" enabled="1" pass="0"><Option type="Map"><Option name="align_dash_pattern" type="QString" value="0"/><Option name="capstyle" type="QString" value="square"/><Option name="customdash" type="QString" value="5;2"/><Option name="customdash_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/><Option name="customdash_unit" type="QString" value="MM"/><Option name="dash_pattern_offset" type="QString" value="0"/><Option name="dash_pattern_offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/><Option name="dash_pattern_offset_unit" type="QString" value="MM"/><Option name="draw_inside_polygon" type="QString" value="0"/><Option name="joinstyle" type="QString" value="bevel"/><Option name="line_color" type="QString" value="60,60,60,255,rgb:0.23529411764705882,0.23529411764705882,0.23529411764705882,1"/><Option name="line_style" type="QString" value="solid"/><Option name="line_width" type="QString" value="0.3"/><Option name="line_width_unit" type="QString" value="MM"/><Option name="offset" type="QString" value="0"/><Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/><Option name="offset_unit" type="QString" value="MM"/><Option name="ring_filter" type="QString" value="0"/><Option name="trim_distance_end" type="QString" value="0"/><Option name="trim_distance_end_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/><Option name="trim_distance_end_unit" type="QString" value="MM"/><Option name="trim_distance_start" type="QString" value="0"/><Option name="trim_distance_start_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/><Option name="trim_distance_start_unit" type="QString" value="MM"/><Option name="tweak_dash_pattern_on_corners" type="QString" value="0"/><Option name="use_custom_dash" type="QString" value="0"/><Option name="width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/></Option><data_defined_properties><Option type="Map"><Option name="name" type="QString" value=""/><Option name="properties"/><Option name="type" type="QString" value="collection"/></Option></data_defined_properties></layer></symbol>"/>
|
||||||
|
<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=""flurstuecksnummer_ax_flurstuecksnummer_nenner" 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=""flurstuecksnummer_ax_flurstuecksnummer_zaehler" || '\n' || "flurstuecksnummer_ax_flurstuecksnummer_nenner" " 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="<" 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="<symbol name="symbol" type="line" 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="{0a6da4bc-e8f1-4ec2-8062-844ead072d33}" class="SimpleLine" locked="0" enabled="1" pass="0"><Option type="Map"><Option name="align_dash_pattern" type="QString" value="0"/><Option name="capstyle" type="QString" value="square"/><Option name="customdash" type="QString" value="5;2"/><Option name="customdash_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/><Option name="customdash_unit" type="QString" value="MM"/><Option name="dash_pattern_offset" type="QString" value="0"/><Option name="dash_pattern_offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/><Option name="dash_pattern_offset_unit" type="QString" value="MM"/><Option name="draw_inside_polygon" type="QString" value="0"/><Option name="joinstyle" type="QString" value="bevel"/><Option name="line_color" type="QString" value="60,60,60,255,rgb:0.23529411764705882,0.23529411764705882,0.23529411764705882,1"/><Option name="line_style" type="QString" value="solid"/><Option name="line_width" type="QString" value="0.3"/><Option name="line_width_unit" type="QString" value="MM"/><Option name="offset" type="QString" value="0"/><Option name="offset_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/><Option name="offset_unit" type="QString" value="MM"/><Option name="ring_filter" type="QString" value="0"/><Option name="trim_distance_end" type="QString" value="0"/><Option name="trim_distance_end_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/><Option name="trim_distance_end_unit" type="QString" value="MM"/><Option name="trim_distance_start" type="QString" value="0"/><Option name="trim_distance_start_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/><Option name="trim_distance_start_unit" type="QString" value="MM"/><Option name="tweak_dash_pattern_on_corners" type="QString" value="0"/><Option name="use_custom_dash" type="QString" value="0"/><Option name="width_map_unit_scale" type="QString" value="3x:0,0,0,0,0,0"/></Option><data_defined_properties><Option type="Map"><Option name="name" type="QString" value=""/><Option name="properties"/><Option name="type" type="QString" value="collection"/></Option></data_defined_properties></layer></symbol>"/>
|
||||||
|
<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
371
styles/GIS_LfULG_LSG.qml
Normal 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>
|
||||||
@@ -1,25 +1,83 @@
|
|||||||
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
|
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
|
||||||
<qgis maxScale="0" simplifyDrawingHints="1" simplifyDrawingTol="1" styleCategories="AllStyleCategories" version="3.10.8-A Coruña" simplifyMaxScale="1" simplifyLocal="1" readOnly="0" hasScaleBasedVisibilityFlag="0" minScale="1e+08" simplifyAlgorithm="0" labelsEnabled="1">
|
<qgis version="3.40.7-Bratislava" styleCategories="Symbology">
|
||||||
<flags>
|
<renderer-v2 referencescale="-1" forceraster="0" enableorderby="0" type="singleSymbol" symbollevels="0">
|
||||||
<Identifiable>1</Identifiable>
|
|
||||||
<Removable>1</Removable>
|
|
||||||
<Searchable>1</Searchable>
|
|
||||||
</flags>
|
|
||||||
<renderer-v2 type="singleSymbol" forceraster="0" symbollevels="0" enableorderby="0">
|
|
||||||
<symbols>
|
<symbols>
|
||||||
<symbol alpha="1" type="fill" clip_to_extent="1" name="0" force_rhr="0">
|
<symbol is_animated="0" frame_rate="10" clip_to_extent="1" type="fill" alpha="1" force_rhr="0" name="0">
|
||||||
<layer class="SimpleFill" locked="0" pass="0" enabled="1">
|
<data_defined_properties>
|
||||||
<prop k="border_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
<Option type="Map">
|
||||||
<prop k="color" v="255,255,153,173"/>
|
<Option type="QString" value="" name="name"/>
|
||||||
<prop k="joinstyle" v="miter"/>
|
<Option name="properties"/>
|
||||||
<prop k="offset" v="0,0"/>
|
<Option type="QString" value="collection" name="type"/>
|
||||||
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
</Option>
|
||||||
<prop k="offset_unit" v="MM"/>
|
</data_defined_properties>
|
||||||
<prop k="outline_color" v="161,2,213,255"/>
|
<layer locked="0" id="{feca00b2-500a-4c9a-b285-67ba2d99d8f6}" enabled="1" class="SimpleLine" pass="0">
|
||||||
<prop k="outline_style" v="solid"/>
|
<Option type="Map">
|
||||||
<prop k="outline_width" v="1"/>
|
<Option type="QString" value="0" name="align_dash_pattern"/>
|
||||||
<prop k="outline_width_unit" v="MM"/>
|
<Option type="QString" value="square" name="capstyle"/>
|
||||||
<prop k="style" v="solid"/>
|
<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>
|
<data_defined_properties>
|
||||||
<Option type="Map">
|
<Option type="Map">
|
||||||
<Option type="QString" value="" name="name"/>
|
<Option type="QString" value="" name="name"/>
|
||||||
@@ -32,285 +90,51 @@
|
|||||||
</symbols>
|
</symbols>
|
||||||
<rotation/>
|
<rotation/>
|
||||||
<sizescale/>
|
<sizescale/>
|
||||||
</renderer-v2>
|
<data-defined-properties>
|
||||||
<labeling type="simple">
|
|
||||||
<settings calloutType="simple">
|
|
||||||
<text-style fontFamily="MS Shell Dlg 2" fontSize="8.25" previewBkgrdColor="255,255,255,255" textOrientation="horizontal" fieldName="Name" textColor="0,0,0,255" fontItalic="0" fontUnderline="0" fontSizeUnit="Point" fontCapitals="0" isExpression="0" fontStrikeout="0" fontWeight="50" fontLetterSpacing="0" fontSizeMapUnitScale="3x:0,0,0,0,0,0" fontWordSpacing="0" textOpacity="1" useSubstitutions="0" fontKerning="1" blendMode="0" namedStyle="Standard" multilineHeight="1">
|
|
||||||
<text-buffer bufferSizeMapUnitScale="3x:0,0,0,0,0,0" bufferJoinStyle="128" bufferSizeUnits="MM" bufferSize="1" bufferDraw="0" bufferColor="255,255,255,255" bufferNoFill="0" bufferBlendMode="0" bufferOpacity="1"/>
|
|
||||||
<background shapeRadiiMapUnitScale="3x:0,0,0,0,0,0" shapeRotation="0" shapeSizeType="0" shapeOffsetX="0" shapeBlendMode="0" shapeFillColor="255,255,255,255" shapeBorderColor="128,128,128,255" shapeRadiiX="0" shapeRadiiUnit="MM" shapeDraw="0" shapeJoinStyle="64" shapeOffsetUnit="MM" shapeBorderWidthUnit="MM" shapeSizeX="0" shapeSizeUnit="MM" shapeSizeY="0" shapeRadiiY="0" shapeOpacity="1" shapeOffsetY="0" shapeSVGFile="" shapeType="0" shapeBorderWidth="0" shapeRotationType="0" shapeOffsetMapUnitScale="3x:0,0,0,0,0,0" shapeBorderWidthMapUnitScale="3x:0,0,0,0,0,0" shapeSizeMapUnitScale="3x:0,0,0,0,0,0">
|
|
||||||
<symbol alpha="1" type="marker" clip_to_extent="1" name="markerSymbol" force_rhr="0">
|
|
||||||
<layer class="SimpleMarker" locked="0" pass="0" enabled="1">
|
|
||||||
<prop k="angle" v="0"/>
|
|
||||||
<prop k="color" v="213,180,60,255"/>
|
|
||||||
<prop k="horizontal_anchor_point" v="1"/>
|
|
||||||
<prop k="joinstyle" v="bevel"/>
|
|
||||||
<prop k="name" v="circle"/>
|
|
||||||
<prop k="offset" v="0,0"/>
|
|
||||||
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
|
||||||
<prop k="offset_unit" v="MM"/>
|
|
||||||
<prop k="outline_color" v="35,35,35,255"/>
|
|
||||||
<prop k="outline_style" v="solid"/>
|
|
||||||
<prop k="outline_width" v="0"/>
|
|
||||||
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
|
||||||
<prop k="outline_width_unit" v="MM"/>
|
|
||||||
<prop k="scale_method" v="diameter"/>
|
|
||||||
<prop k="size" v="2"/>
|
|
||||||
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
|
||||||
<prop k="size_unit" v="MM"/>
|
|
||||||
<prop k="vertical_anchor_point" v="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>
|
|
||||||
</symbol>
|
|
||||||
</background>
|
|
||||||
<shadow shadowUnder="0" shadowOffsetUnit="MM" shadowScale="100" shadowColor="0,0,0,255" shadowBlendMode="6" shadowOffsetAngle="135" shadowOffsetDist="1" shadowRadiusUnit="MM" shadowOffsetMapUnitScale="3x:0,0,0,0,0,0" shadowRadiusMapUnitScale="3x:0,0,0,0,0,0" shadowOpacity="0.7" shadowDraw="0" shadowRadiusAlphaOnly="0" shadowOffsetGlobal="1" shadowRadius="1.5"/>
|
|
||||||
<dd_properties>
|
|
||||||
<Option type="Map">
|
|
||||||
<Option type="QString" value="" name="name"/>
|
|
||||||
<Option name="properties"/>
|
|
||||||
<Option type="QString" value="collection" name="type"/>
|
|
||||||
</Option>
|
|
||||||
</dd_properties>
|
|
||||||
<substitutions/>
|
|
||||||
</text-style>
|
|
||||||
<text-format autoWrapLength="0" placeDirectionSymbol="0" leftDirectionSymbol="<" rightDirectionSymbol=">" decimals="3" wrapChar="" useMaxLineLengthForAutoWrap="1" plussign="0" formatNumbers="0" addDirectionSymbol="0" reverseDirectionSymbol="0" multilineAlign="4294967295"/>
|
|
||||||
<placement xOffset="0" distMapUnitScale="3x:0,0,0,0,0,0" dist="0" repeatDistanceUnits="MM" predefinedPositionOrder="TR,TL,BR,BL,R,L,TSR,BSR" centroidWhole="0" rotationAngle="0" overrunDistanceUnit="MM" placement="1" repeatDistance="0" geometryGeneratorEnabled="0" layerType="PolygonGeometry" offsetUnits="MapUnit" centroidInside="0" offsetType="0" yOffset="0" labelOffsetMapUnitScale="3x:0,0,0,0,0,0" overrunDistance="0" geometryGenerator="" geometryGeneratorType="PointGeometry" placementFlags="10" preserveRotation="1" distUnits="MM" fitInPolygonOnly="0" maxCurvedCharAngleOut="-25" repeatDistanceMapUnitScale="3x:0,0,0,0,0,0" quadOffset="4" maxCurvedCharAngleIn="25" priority="5" overrunDistanceMapUnitScale="3x:0,0,0,0,0,0"/>
|
|
||||||
<rendering fontLimitPixelSize="0" upsidedownLabels="0" zIndex="0" drawLabels="1" fontMinPixelSize="3" displayAll="0" scaleMax="10000000" labelPerPart="0" fontMaxPixelSize="10000" mergeLines="0" obstacle="1" minFeatureSize="0" obstacleType="0" obstacleFactor="1" scaleMin="1" limitNumLabels="0" scaleVisibility="0" maxNumLabels="2000"/>
|
|
||||||
<dd_properties>
|
|
||||||
<Option type="Map">
|
|
||||||
<Option type="QString" value="" name="name"/>
|
|
||||||
<Option name="properties"/>
|
|
||||||
<Option type="QString" value="collection" name="type"/>
|
|
||||||
</Option>
|
|
||||||
</dd_properties>
|
|
||||||
<callout type="simple">
|
|
||||||
<Option type="Map">
|
|
||||||
<Option type="QString" value="pole_of_inaccessibility" name="anchorPoint"/>
|
|
||||||
<Option type="Map" name="ddProperties">
|
|
||||||
<Option type="QString" value="" name="name"/>
|
|
||||||
<Option name="properties"/>
|
|
||||||
<Option type="QString" value="collection" name="type"/>
|
|
||||||
</Option>
|
|
||||||
<Option type="bool" value="false" name="drawToAllParts"/>
|
|
||||||
<Option type="QString" value="0" name="enabled"/>
|
|
||||||
<Option type="QString" value="<symbol alpha="1" type="line" clip_to_extent="1" name="symbol" force_rhr="0"><layer class="SimpleLine" locked="0" pass="0" enabled="1"><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="draw_inside_polygon" v="0"/><prop k="joinstyle" v="bevel"/><prop k="line_color" v="60,60,60,255"/><prop k="line_style" v="solid"/><prop k="line_width" v="0.3"/><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="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 type="QString" value="" name="name"/><Option name="properties"/><Option type="QString" value="collection" name="type"/></Option></data_defined_properties></layer></symbol>" name="lineSymbol"/>
|
|
||||||
<Option type="double" value="0" name="minLength"/>
|
|
||||||
<Option type="QString" value="3x:0,0,0,0,0,0" name="minLengthMapUnitScale"/>
|
|
||||||
<Option type="QString" value="MM" name="minLengthUnit"/>
|
|
||||||
<Option type="double" value="0" name="offsetFromAnchor"/>
|
|
||||||
<Option type="QString" value="3x:0,0,0,0,0,0" name="offsetFromAnchorMapUnitScale"/>
|
|
||||||
<Option type="QString" value="MM" name="offsetFromAnchorUnit"/>
|
|
||||||
<Option type="double" value="0" name="offsetFromLabel"/>
|
|
||||||
<Option type="QString" value="3x:0,0,0,0,0,0" name="offsetFromLabelMapUnitScale"/>
|
|
||||||
<Option type="QString" value="MM" name="offsetFromLabelUnit"/>
|
|
||||||
</Option>
|
|
||||||
</callout>
|
|
||||||
</settings>
|
|
||||||
</labeling>
|
|
||||||
<customproperties>
|
|
||||||
<property value="0" key="embeddedWidgets/count"/>
|
|
||||||
<property key="variableNames"/>
|
|
||||||
<property key="variableValues"/>
|
|
||||||
</customproperties>
|
|
||||||
<blendMode>0</blendMode>
|
|
||||||
<featureBlendMode>0</featureBlendMode>
|
|
||||||
<layerOpacity>1</layerOpacity>
|
|
||||||
<SingleCategoryDiagramRenderer attributeLegend="1" diagramType="Histogram">
|
|
||||||
<DiagramCategory minimumSize="0" enabled="0" scaleBasedVisibility="0" labelPlacementMethod="XHeight" rotationOffset="270" backgroundColor="#ffffff" width="15" backgroundAlpha="255" sizeType="MM" penWidth="0" penColor="#000000" lineSizeScale="3x:0,0,0,0,0,0" maxScaleDenominator="1e+08" sizeScale="3x:0,0,0,0,0,0" scaleDependency="Area" barWidth="5" lineSizeType="MM" minScaleDenominator="0" height="15" opacity="1" diagramOrientation="Up" penAlpha="255">
|
|
||||||
<fontProperties description="MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0" style=""/>
|
|
||||||
</DiagramCategory>
|
|
||||||
</SingleCategoryDiagramRenderer>
|
|
||||||
<DiagramLayerSettings placement="1" showAll="1" priority="0" zIndex="0" obstacle="0" linePlacementFlags="18" dist="0">
|
|
||||||
<properties>
|
|
||||||
<Option type="Map">
|
<Option type="Map">
|
||||||
<Option type="QString" value="" name="name"/>
|
<Option type="QString" value="" name="name"/>
|
||||||
<Option name="properties"/>
|
<Option name="properties"/>
|
||||||
<Option type="QString" value="collection" name="type"/>
|
<Option type="QString" value="collection" name="type"/>
|
||||||
</Option>
|
</Option>
|
||||||
</properties>
|
</data-defined-properties>
|
||||||
</DiagramLayerSettings>
|
</renderer-v2>
|
||||||
<geometryOptions removeDuplicateNodes="0" geometryPrecision="0">
|
<selection mode="Default">
|
||||||
<activeChecks/>
|
<selectionColor invalid="1"/>
|
||||||
<checkConfiguration type="Map">
|
<selectionSymbol>
|
||||||
<Option type="Map" name="QgsGeometryGapCheck">
|
<symbol is_animated="0" frame_rate="10" clip_to_extent="1" type="fill" alpha="1" force_rhr="0" name="">
|
||||||
<Option type="double" value="0" name="allowedGapsBuffer"/>
|
<data_defined_properties>
|
||||||
<Option type="bool" value="false" name="allowedGapsEnabled"/>
|
<Option type="Map">
|
||||||
<Option type="QString" value="" name="allowedGapsLayer"/>
|
<Option type="QString" value="" name="name"/>
|
||||||
</Option>
|
<Option name="properties"/>
|
||||||
</checkConfiguration>
|
<Option type="QString" value="collection" name="type"/>
|
||||||
</geometryOptions>
|
</Option>
|
||||||
<fieldConfiguration>
|
</data_defined_properties>
|
||||||
<field name="id">
|
<layer locked="0" id="{f18003f5-220a-487f-8a6d-b24facc4c1a5}" enabled="1" class="SimpleFill" pass="0">
|
||||||
<editWidget type="TextEdit">
|
<Option type="Map">
|
||||||
<config>
|
<Option type="QString" value="3x:0,0,0,0,0,0" name="border_width_map_unit_scale"/>
|
||||||
<Option/>
|
<Option type="QString" value="0,0,255,255,rgb:0,0,1,1" name="color"/>
|
||||||
</config>
|
<Option type="QString" value="bevel" name="joinstyle"/>
|
||||||
</editWidget>
|
<Option type="QString" value="0,0" name="offset"/>
|
||||||
</field>
|
<Option type="QString" value="3x:0,0,0,0,0,0" name="offset_map_unit_scale"/>
|
||||||
<field name="Name">
|
<Option type="QString" value="MM" name="offset_unit"/>
|
||||||
<editWidget type="TextEdit">
|
<Option type="QString" value="35,35,35,255,rgb:0.13725490196078433,0.13725490196078433,0.13725490196078433,1" name="outline_color"/>
|
||||||
<config>
|
<Option type="QString" value="solid" name="outline_style"/>
|
||||||
<Option/>
|
<Option type="QString" value="0.26" name="outline_width"/>
|
||||||
</config>
|
<Option type="QString" value="MM" name="outline_width_unit"/>
|
||||||
</editWidget>
|
<Option type="QString" value="solid" name="style"/>
|
||||||
</field>
|
</Option>
|
||||||
<field name="Nummer">
|
<data_defined_properties>
|
||||||
<editWidget type="Range">
|
<Option type="Map">
|
||||||
<config>
|
<Option type="QString" value="" name="name"/>
|
||||||
<Option/>
|
<Option name="properties"/>
|
||||||
</config>
|
<Option type="QString" value="collection" name="type"/>
|
||||||
</editWidget>
|
</Option>
|
||||||
</field>
|
</data_defined_properties>
|
||||||
<field name="Referat">
|
</layer>
|
||||||
<editWidget type="TextEdit">
|
</symbol>
|
||||||
<config>
|
</selectionSymbol>
|
||||||
<Option/>
|
</selection>
|
||||||
</config>
|
<blendMode>0</blendMode>
|
||||||
</editWidget>
|
<featureBlendMode>0</featureBlendMode>
|
||||||
</field>
|
|
||||||
<field name="Ansprechpartner">
|
|
||||||
<editWidget type="TextEdit">
|
|
||||||
<config>
|
|
||||||
<Option/>
|
|
||||||
</config>
|
|
||||||
</editWidget>
|
|
||||||
</field>
|
|
||||||
<field name="Telefon">
|
|
||||||
<editWidget type="TextEdit">
|
|
||||||
<config>
|
|
||||||
<Option/>
|
|
||||||
</config>
|
|
||||||
</editWidget>
|
|
||||||
</field>
|
|
||||||
<field name="E-Mail">
|
|
||||||
<editWidget type="TextEdit">
|
|
||||||
<config>
|
|
||||||
<Option/>
|
|
||||||
</config>
|
|
||||||
</editWidget>
|
|
||||||
</field>
|
|
||||||
<field name="Letzte Änderung">
|
|
||||||
<editWidget type="DateTime">
|
|
||||||
<config>
|
|
||||||
<Option/>
|
|
||||||
</config>
|
|
||||||
</editWidget>
|
|
||||||
</field>
|
|
||||||
</fieldConfiguration>
|
|
||||||
<aliases>
|
|
||||||
<alias index="0" name="" field="id"/>
|
|
||||||
<alias index="1" name="" field="Name"/>
|
|
||||||
<alias index="2" name="" field="Nummer"/>
|
|
||||||
<alias index="3" name="" field="Referat"/>
|
|
||||||
<alias index="4" name="" field="Ansprechpartner"/>
|
|
||||||
<alias index="5" name="" field="Telefon"/>
|
|
||||||
<alias index="6" name="" field="E-Mail"/>
|
|
||||||
<alias index="7" name="" field="Letzte Änderung"/>
|
|
||||||
</aliases>
|
|
||||||
<excludeAttributesWMS/>
|
|
||||||
<excludeAttributesWFS/>
|
|
||||||
<defaults>
|
|
||||||
<default expression="" applyOnUpdate="0" field="id"/>
|
|
||||||
<default expression="" applyOnUpdate="0" field="Name"/>
|
|
||||||
<default expression="" applyOnUpdate="0" field="Nummer"/>
|
|
||||||
<default expression="" applyOnUpdate="0" field="Referat"/>
|
|
||||||
<default expression="" applyOnUpdate="0" field="Ansprechpartner"/>
|
|
||||||
<default expression="" applyOnUpdate="0" field="Telefon"/>
|
|
||||||
<default expression="" applyOnUpdate="0" field="E-Mail"/>
|
|
||||||
<default expression="" applyOnUpdate="0" field="Letzte Änderung"/>
|
|
||||||
</defaults>
|
|
||||||
<constraints>
|
|
||||||
<constraint exp_strength="0" notnull_strength="1" unique_strength="1" field="id" constraints="3"/>
|
|
||||||
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Name" constraints="0"/>
|
|
||||||
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Nummer" constraints="0"/>
|
|
||||||
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Referat" constraints="0"/>
|
|
||||||
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Ansprechpartner" constraints="0"/>
|
|
||||||
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Telefon" constraints="0"/>
|
|
||||||
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="E-Mail" constraints="0"/>
|
|
||||||
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Letzte Änderung" constraints="0"/>
|
|
||||||
</constraints>
|
|
||||||
<constraintExpressions>
|
|
||||||
<constraint desc="" exp="" field="id"/>
|
|
||||||
<constraint desc="" exp="" field="Name"/>
|
|
||||||
<constraint desc="" exp="" field="Nummer"/>
|
|
||||||
<constraint desc="" exp="" field="Referat"/>
|
|
||||||
<constraint desc="" exp="" field="Ansprechpartner"/>
|
|
||||||
<constraint desc="" exp="" field="Telefon"/>
|
|
||||||
<constraint desc="" exp="" field="E-Mail"/>
|
|
||||||
<constraint desc="" exp="" field="Letzte Änderung"/>
|
|
||||||
</constraintExpressions>
|
|
||||||
<expressionfields/>
|
|
||||||
<attributeactions>
|
|
||||||
<defaultAction value="{00000000-0000-0000-0000-000000000000}" key="Canvas"/>
|
|
||||||
</attributeactions>
|
|
||||||
<attributetableconfig sortOrder="0" actionWidgetStyle="dropDown" sortExpression="">
|
|
||||||
<columns>
|
|
||||||
<column type="actions" hidden="1" width="-1"/>
|
|
||||||
<column type="field" hidden="0" width="-1" name="Nummer"/>
|
|
||||||
<column type="field" hidden="0" width="-1" name="Name"/>
|
|
||||||
<column type="field" hidden="0" width="-1" name="E-Mail"/>
|
|
||||||
<column type="field" hidden="0" width="-1" name="Letzte Änderung"/>
|
|
||||||
<column type="field" hidden="0" width="-1" name="id"/>
|
|
||||||
<column type="field" hidden="0" width="-1" name="Referat"/>
|
|
||||||
<column type="field" hidden="0" width="-1" name="Ansprechpartner"/>
|
|
||||||
<column type="field" hidden="0" width="-1" name="Telefon"/>
|
|
||||||
</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="Ansprechpartner" editable="1"/>
|
|
||||||
<field name="E-Mail" editable="1"/>
|
|
||||||
<field name="Letzte Änderung" editable="1"/>
|
|
||||||
<field name="Name" editable="1"/>
|
|
||||||
<field name="Nummer" editable="1"/>
|
|
||||||
<field name="Referat" editable="1"/>
|
|
||||||
<field name="Telefon" editable="1"/>
|
|
||||||
<field name="id" editable="1"/>
|
|
||||||
</editable>
|
|
||||||
<labelOnTop>
|
|
||||||
<field labelOnTop="0" name="Ansprechpartner"/>
|
|
||||||
<field labelOnTop="0" name="E-Mail"/>
|
|
||||||
<field labelOnTop="0" name="Letzte Änderung"/>
|
|
||||||
<field labelOnTop="0" name="Name"/>
|
|
||||||
<field labelOnTop="0" name="Nummer"/>
|
|
||||||
<field labelOnTop="0" name="Referat"/>
|
|
||||||
<field labelOnTop="0" name="Telefon"/>
|
|
||||||
<field labelOnTop="0" name="id"/>
|
|
||||||
</labelOnTop>
|
|
||||||
<widgets/>
|
|
||||||
<previewExpression>COALESCE( "name", '<NULL>' )</previewExpression>
|
|
||||||
<mapTip></mapTip>
|
|
||||||
<layerGeometryType>2</layerGeometryType>
|
<layerGeometryType>2</layerGeometryType>
|
||||||
</qgis>
|
</qgis>
|
||||||
|
|||||||
1
tests/__init__.py
Normal file
1
tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#Testordner
|
||||||
154
tests/run_tests.py
Normal file
154
tests/run_tests.py
Normal 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())
|
||||||
9
tests/start_osgeo4w_qgis.bat
Normal file
9
tests/start_osgeo4w_qgis.bat
Normal 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
2
tests/test_bootstrap.py
Normal 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
104
tests/test_dateipruefer.py
Normal 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
171
tests/test_layerpruefer.py
Normal 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
79
tests/test_linkpruefer.py
Normal 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
146
tests/test_pruefmanager.py
Normal 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
52
tests/test_qgis.bat
Normal 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
|
||||||
60
tests/test_settings_logic.py
Normal file
60
tests/test_settings_logic.py
Normal 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
81
tests/test_stilpruefer.py
Normal 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()
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
133
ui/navigation.py
133
ui/navigation.py
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.functions.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():
|
||||||
|
|||||||
Reference in New Issue
Block a user