881 lines
36 KiB
Python
881 lines
36 KiB
Python
import os.path
|
|
from typing import Callable
|
|
|
|
from PyQt5.QtWidgets import QAction, QCheckBox
|
|
from qgis._core import QgsVectorLayer, QgsProject, QgsLayerTreeLayer, QgsPalLayerSettings, QgsVectorLayerSimpleLabeling, QgsStyle, QgsSymbol, QgsRendererCategory, QgsCategorizedSymbolRenderer
|
|
|
|
from .map_tips import WATERLEVELS_MAP_TIPS, STATIONS_MAP_TIPS, BASEMAP_MAP_TIPS
|
|
from .pegelonline_dockwidget import PegelonlineDockWidget
|
|
from .pegelonline_dockwidget_graph import PegelonlineDockWidgetGraph
|
|
from .po_modules.po_station_reader import PoStationReader
|
|
from .po_modules.po_stations_reader_qgs import PoStationReaderQgs
|
|
from .po_modules.po_waterlevels_reader_qgs import PoWaterlevelReaderQgs
|
|
|
|
STATIONS_QML = "styles/stations.qml"
|
|
|
|
WATERLEVELS_QML = "styles/waterlevels.qml"
|
|
|
|
|
|
class PoRunner(object):
|
|
|
|
def __init__(self, ui: PegelonlineDockWidget, graph: PegelonlineDockWidgetGraph, iface):
|
|
"""
|
|
Initialisiert ein neues PoRunner-Objekt:
|
|
- Initialisiert Objekt-Variablen
|
|
- Verbindet UI-Actions
|
|
- Verbindet UI-Signale
|
|
:param ui: Haupt-Widget
|
|
:type ui: PegelonlineDockWidget
|
|
:param graph: Pegelverlauf-Widget
|
|
:type graph: PegelonlineDockWidgetGraph
|
|
:param iface: QGIS-Interface
|
|
:type iface: QgisInterface
|
|
"""
|
|
# Widget-Referenzen
|
|
self.ui = ui
|
|
self.graph = graph
|
|
|
|
# QGIS-Referenz
|
|
self.iface = iface
|
|
|
|
self.local_dir = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
# Layer
|
|
self.stations = None
|
|
self.waterlevels = None
|
|
self.lines = None
|
|
self.areas = None
|
|
|
|
# Standard Aktionen verbinden
|
|
self.ui.btn_zoom_full_extent.setDefaultAction(iface.actionZoomFullExtent())
|
|
self.ui.btn_zoom_to_layer.setDefaultAction(iface.actionZoomToLayer())
|
|
self.ui.btn_zoom_to_selected.setDefaultAction(iface.actionZoomToSelected())
|
|
self.ui.btn_select_rectangle.setDefaultAction(iface.actionSelectRectangle())
|
|
self.ui.btn_unselect_all.setDefaultAction(iface.mainWindow().findChild(QAction, "mActionDeselectAll"))
|
|
self.ui.btn_unselect_layer.setDefaultAction(iface.mainWindow().findChild(QAction, "mActionDeselectActiveLayer"))
|
|
self.ui.btn_map_tips.setDefaultAction(iface.actionMapTips())
|
|
|
|
# Signale verbinden
|
|
self._basemap_connect_signals()
|
|
self._stations_connect_signals()
|
|
self._waterlevels_connect_signals()
|
|
self._graph_connect_signals()
|
|
|
|
# basemap -----------------------------------------------------------------
|
|
|
|
def _basemap_create(self, path, name, disconnect: Callable[[], None], map_tips, checkbox) -> None | QgsVectorLayer:
|
|
"""
|
|
Erzeugt einen neuen Basemap-Layer (ogr),
|
|
verbindet UI Signale damit und fügt den erzeugten Layer
|
|
ans untere Ende der Layer-Liste an.
|
|
:param path: Pfad zu den Layer-Daten
|
|
:type path: str
|
|
:param disconnect: Methode die Aufgerufen wird, wenn der Layer gelöscht wird
|
|
:type disconnect: Callable[[], None]
|
|
:param map_tips: HTML Code für die QGIS-Map-Tipps
|
|
:param checkbox: Sichtbarkeit-Checkbox um sie via Signal upzudaten
|
|
:type checkbox: QCheckBox
|
|
"""
|
|
print("_basemap_create: %s" % (name,))
|
|
path = os.path.join(self.local_dir, "basemap", path)
|
|
basemap = QgsVectorLayer(path, name, "ogr")
|
|
|
|
if not basemap.isValid():
|
|
# Fehler beim Erzeugen des Layers → Abbruch
|
|
print("_basemapCreate: QgsVectorLayer nicht gültig: path=%s, name=%s" % (path, name))
|
|
return None
|
|
|
|
# disconnect setzen
|
|
basemap.willBeDeleted.connect(disconnect)
|
|
|
|
# map-tips setzen
|
|
basemap.setMapTipTemplate(map_tips)
|
|
|
|
# zur Instanz hinzufügen
|
|
QgsProject.instance().addMapLayer(basemap, False)
|
|
|
|
# zum LayerTree hinzufügen
|
|
layer_tree = self.iface.layerTreeCanvasBridge().rootGroup()
|
|
layer_tree.insertChildNode(-1, QgsLayerTreeLayer(basemap)) # am unteren Ende anhängen → liegt somit unter stations/waterlevels
|
|
|
|
# Signal zur Erkennung von Sichtbarkeitsänderungen verbinden
|
|
self._connect_layer_list_visibility_signal(basemap, checkbox)
|
|
|
|
return basemap
|
|
|
|
def _basemap_connect_signals(self):
|
|
"""
|
|
Verbindet alle Basemap-Signale mit der UI
|
|
"""
|
|
print("_connect_basemap_signals")
|
|
self.ui.cb_basemap_lines.toggled.connect(self._cb_basemap_lines_toggled)
|
|
self.ui.cb_basemap_areas.toggled.connect(self._cb_basemap_areas_toggled)
|
|
|
|
def _cb_basemap_lines_toggled(self):
|
|
"""
|
|
Behandelt die Sichbarkeitsänderung der Flüsse durch die UI-Checkbox
|
|
"""
|
|
checked = self.ui.cb_basemap_lines.isChecked()
|
|
print("_cb_basemap_lines_toggled: %s" % (checked,))
|
|
|
|
if self.lines is None and checked:
|
|
# Flüsse sind noch nicht als Layer geladen → Laden
|
|
self.lines = self._basemap_create(
|
|
"waters.gpkg|layername=water_l",
|
|
"Flüsse",
|
|
self._basemap_disconnect_lines,
|
|
BASEMAP_MAP_TIPS,
|
|
self.ui.cb_basemap_lines,
|
|
)
|
|
|
|
if self.lines is not None:
|
|
# Flüsse sind (bereits) angelegt → setze Sichtbarkeit wie gefordert
|
|
self._layer_set_visible(self.lines, checked)
|
|
self._layer_refresh(self.lines)
|
|
|
|
def _cb_basemap_areas_toggled(self):
|
|
"""
|
|
Behandelt die Sichtbarkeitsänderung der Flächen durch die UI-Checkbox
|
|
"""
|
|
checked = self.ui.cb_basemap_areas.isChecked()
|
|
print("_cb_basemap_areas_toggled: %s" % (checked,))
|
|
|
|
if self.areas is None and checked:
|
|
# Flächen sind noch nicht als Layer geladen → Laden
|
|
self.areas = self._basemap_create(
|
|
"waters.gpkg|layername=water_f",
|
|
"Flächen",
|
|
self._basemap_disconnect_areas,
|
|
BASEMAP_MAP_TIPS,
|
|
self.ui.cb_basemap_areas,
|
|
)
|
|
|
|
if self.areas is not None:
|
|
# Flächen sind (bereits) angelegt → setze Sichtbarkeit wie gefordert
|
|
self._layer_set_visible(self.areas, checked)
|
|
self._layer_refresh(self.areas)
|
|
|
|
def _basemap_disconnect_lines(self):
|
|
"""
|
|
Löscht die Flüsse-Layer-Referenz, nachdem der Layer aus QGIS gelöscht wurde und passt die Checkbox an
|
|
"""
|
|
print("_basemap_disconnect_lines")
|
|
self.lines = None
|
|
self.ui.cb_basemap_lines.setChecked(False)
|
|
|
|
def _basemap_disconnect_areas(self):
|
|
"""
|
|
Löscht die Flächen-Layer-Referenz, nachdem der Layer aus QGIS gelöscht wurde und passt die Checkbox an
|
|
"""
|
|
print("_basemap_disconnect_areas")
|
|
self.areas = None
|
|
self.ui.cb_basemap_areas.setChecked(False)
|
|
|
|
# stations ----------------------------------------------------------------
|
|
|
|
def _stations_connect_signals(self):
|
|
"""
|
|
Verbindet alle Stations-Signale mit der UI
|
|
"""
|
|
print("_connect_stations_signals")
|
|
self.ui.cb_stations_visible.toggled.connect(self._cb_stations_visible_toggled)
|
|
self.ui.cb_stations_name.toggled.connect(self._cb_stations_name_toggled)
|
|
self.ui.cb_stations_number.toggled.connect(self._cb_stations_number_toggled)
|
|
self.ui.cb_stations_agency.toggled.connect(self._cb_stations_agency_toggled)
|
|
self.ui.cb_stations_km.toggled.connect(self._cb_stations_km_toggled)
|
|
self.ui.cb_stations_water.toggled.connect(self._cb_stations_water_toggled)
|
|
self.ui.cb_stations_style.buttonClicked.connect(self._cb_stations_style_clicked)
|
|
|
|
def _stations_apply_style(self):
|
|
"""
|
|
Wendet eingestellte Styles auf den Stations-Layer an
|
|
"""
|
|
button = self.ui.cb_stations_style.checkedButton()
|
|
self._cb_stations_style_clicked(button)
|
|
|
|
def _cb_stations_style_clicked(self, button):
|
|
"""
|
|
Behandlung eines Klicks auf einen der Stations-Style-Radio-Buttons
|
|
→ Wendet Styles auf Stations-Layer an
|
|
:param button: geklickter Radio-Button
|
|
:type button: QRadioButton
|
|
"""
|
|
print("_cb_stations_style_clicked: %s" % (button.objectName(),))
|
|
|
|
if self.stations is None:
|
|
# Stations-Layer nicht geladen → Keine Änderung
|
|
return
|
|
|
|
if self.ui.rb_stations_style_preset == button:
|
|
# Vorgabe ausgewählt → lade aus Datei
|
|
self._layer_apply_style_from_file(self.stations, STATIONS_QML)
|
|
return
|
|
|
|
if self.ui.rb_stations_style_agency == button:
|
|
# Behörde ausgewählt
|
|
field = "agency"
|
|
elif self.ui.rb_stations_style_water == button:
|
|
# Gewässer ausgewählt
|
|
field = "water"
|
|
else:
|
|
# Programmierfehler: Unbekannter Button
|
|
print("_cb_stations_style_clicked: Style nicht implementiert: %s" % (button.objectName(),))
|
|
self._layer_apply_style_from_file(self.stations, STATIONS_QML)
|
|
return
|
|
|
|
self._stations_update_labels()
|
|
self.stations.setMapTipTemplate(STATIONS_MAP_TIPS)
|
|
self._layer_apply_style_per_category(self.stations, field, STATIONS_QML)
|
|
|
|
def _cb_stations_visible_toggled(self):
|
|
"""
|
|
Schaltet Sichtbarkeit des Stations-Layers um (und erstellt ihn, falls nötig)
|
|
"""
|
|
visible = self.ui.cb_stations_visible.isChecked()
|
|
print("_cb_stations_visible_toggled: %s" % (visible,))
|
|
|
|
if self.stations is None and visible:
|
|
# Stationen sind noch nicht als Layer geladen → Laden
|
|
reader = PoStationReaderQgs()
|
|
features = reader.get_features()
|
|
self.stations = self._layer_create_from_features(reader.fields, reader.crs, features, "Stationen")
|
|
self._layer_add_to_instance(self.stations, self._stations_disconnect, self.ui.cb_stations_visible)
|
|
|
|
if self.stations is not None:
|
|
# Stationen sind (bereits) angelegt → setze Sichtbarkeit wie gefordert
|
|
self._layer_set_visible(self.stations, visible)
|
|
if visible:
|
|
self._stations_apply_style()
|
|
|
|
def _cb_stations_name_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Stations-Attributs 'Name' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_stations_name.isChecked()
|
|
print("_cb_stations_name_toggled: %s" % (checked,))
|
|
self._stations_update_labels()
|
|
|
|
def _cb_stations_number_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Stations-Attributs 'Number' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_stations_number.isChecked()
|
|
print("_cb_stations_number_toggled: %s" % (checked,))
|
|
self._stations_update_labels()
|
|
|
|
def _cb_stations_agency_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Stations-Attributs 'Agency' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_stations_agency.isChecked()
|
|
print("_cb_stations_agency_toggled: %s" % (checked,))
|
|
self._stations_update_labels()
|
|
|
|
def _cb_stations_km_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Stations-Attributs 'Km' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_stations_km.isChecked()
|
|
print("_cb_stations_km_toggled: %s" % (checked,))
|
|
self._stations_update_labels()
|
|
|
|
def _cb_stations_water_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Stations-Attributs 'Water' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_stations_water.isChecked()
|
|
print("_cb_stations_water_toggled: %s" % (checked,))
|
|
self._stations_update_labels()
|
|
|
|
def _stations_disconnect(self):
|
|
"""
|
|
Löscht die Stations-Layer-Referenz, nachdem der Layer aus QGIS gelöscht wurde und passt die Checkbox an
|
|
"""
|
|
print("_stations_disconnect")
|
|
self.stations = None
|
|
self.ui.cb_stations_visible.setChecked(False)
|
|
|
|
def _stations_update_labels(self):
|
|
"""
|
|
Führt Änderungen an Stations-Labels durch
|
|
"""
|
|
print("_stations_update_labels")
|
|
if self.stations is None:
|
|
return
|
|
|
|
fields = []
|
|
if self.ui.cb_stations_number.isChecked():
|
|
# Attribut 'number' ausgewählt
|
|
fields.append('\'#\', "number"')
|
|
if self.ui.cb_stations_name.isChecked():
|
|
# Attribut 'shortname' ausgewählt
|
|
fields.append('"shortname"')
|
|
if self.ui.cb_stations_agency.isChecked():
|
|
# Attribut 'agency' ausgewählt
|
|
fields.append('"agency"')
|
|
if self.ui.cb_stations_water.isChecked():
|
|
# Attribut 'water' ausgewählt
|
|
fields.append('"water"')
|
|
if self.ui.cb_stations_km.isChecked():
|
|
# Attribut 'km' ausgewählt
|
|
fields.append('"km", \' km\'') # 2 Teile anhängen: km, " km"
|
|
|
|
self._layer_update_labels(self.stations, fields)
|
|
|
|
# waterlevels -------------------------------------------------------------
|
|
|
|
def _waterlevels_connect_signals(self):
|
|
"""
|
|
Verbindet alle Pegelstand-Signale mit der UI
|
|
"""
|
|
print("_waterlevels_connect_signals")
|
|
self.ui.cb_waterlevels_visible.toggled.connect(self._cb_waterlevels_visible_toggled)
|
|
self.ui.cb_waterlevels_name.toggled.connect(self._cb_waterlevels_name_toggled)
|
|
self.ui.cb_waterlevels_number.toggled.connect(self._cb_waterlevels_number_toggled)
|
|
self.ui.cb_waterlevels_agency.toggled.connect(self._cb_waterlevels_agency_toggled)
|
|
self.ui.cb_waterlevels_timestamp.toggled.connect(self._cb_waterlevels_timestamp_toggled)
|
|
self.ui.cb_waterlevels_value.toggled.connect(self._cb_waterlevels_value_toggled)
|
|
self.ui.cb_waterlevels_mean.toggled.connect(self._cb_waterlevels_mean_toggled)
|
|
self.ui.cb_waterlevels_absolute.toggled.connect(self._cb_waterlevels_absolute_toggled)
|
|
self.ui.cb_waterlevels_water.toggled.connect(self._cb_waterlevels_water_toggled)
|
|
self.ui.cb_waterlevels_style.buttonClicked.connect(self._cb_waterlevels_style_clicked)
|
|
|
|
def _waterlevels_apply_style(self):
|
|
"""
|
|
Wendet eingestellte Styles auf den Pegelstand-Layer an
|
|
"""
|
|
button = self.ui.cb_waterlevels_style.checkedButton()
|
|
self._cb_waterlevels_style_clicked(button)
|
|
|
|
def _cb_waterlevels_style_clicked(self, button):
|
|
"""
|
|
Behandlung eines Klicks auf einen der Pegelstand-Style-Radio-Buttons
|
|
→ Wendet Styles auf Pegelstand-Layer an
|
|
:param button: geklickter Radio-Button
|
|
:type button: QRadioButton
|
|
"""
|
|
print("_cb_waterlevels_style_clicked: %s" % (button.objectName(),))
|
|
|
|
if self.waterlevels is None:
|
|
# Pegelstand-Layer nicht geladen → Keine Änderung
|
|
return
|
|
|
|
if self.ui.rb_waterlevels_style_preset == button:
|
|
# Vorgabe ausgewählt → lade aus Datei
|
|
self._layer_apply_style_from_file(self.waterlevels, WATERLEVELS_QML)
|
|
return
|
|
|
|
if self.ui.rb_waterlevels_style_agency == button:
|
|
# Behörde ausgewählt
|
|
field = "agency"
|
|
elif self.ui.rb_waterlevels_style_water == button:
|
|
# Gewässer ausgewählt
|
|
field = "water"
|
|
elif self.ui.rb_waterlevels_style_mean == button:
|
|
# MNW, MHW ausgewählt
|
|
field = "mean"
|
|
elif self.ui.rb_waterlevels_style_absolute == button:
|
|
# NSW, HSW ausgewählt
|
|
field = "absolute"
|
|
else:
|
|
# Programmierfehler: Unbekannter Button
|
|
print("_cb_waterlevels_style_clicked: Style nicht implementiert: %s" % (button.objectName(),))
|
|
self._layer_apply_style_from_file(self.waterlevels, WATERLEVELS_QML)
|
|
return
|
|
|
|
self._waterlevels_update_labels()
|
|
self.waterlevels.setMapTipTemplate(WATERLEVELS_MAP_TIPS)
|
|
self._layer_apply_style_per_category(self.waterlevels, field, WATERLEVELS_QML)
|
|
|
|
def _cb_waterlevels_visible_toggled(self):
|
|
"""
|
|
Schaltet Sichtbarkeit des Pegelstand-Layers um (und erstellt ihn, falls nötig)
|
|
"""
|
|
visible = self.ui.cb_waterlevels_visible.isChecked()
|
|
print("_cb_waterlevels_visible_toggled: %s" % (visible,))
|
|
|
|
if self.waterlevels is None and visible:
|
|
# Pegelstände sind noch nicht als Layer geladen → Laden
|
|
reader = PoWaterlevelReaderQgs()
|
|
features = reader.get_features()
|
|
self.waterlevels = self._layer_create_from_features(reader.fields, reader.crs, features, "Pegelstände")
|
|
self._layer_add_to_instance(self.waterlevels, self._waterlevels_disconnect, self.ui.cb_waterlevels_visible)
|
|
|
|
if self.waterlevels is not None:
|
|
# Pegelstände sind (bereits) angelegt → setze Sichtbarkeit wie gefordert
|
|
self._layer_set_visible(self.waterlevels, visible)
|
|
if visible:
|
|
self._waterlevels_apply_style()
|
|
|
|
def _cb_waterlevels_name_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Pegelstand-Attributs 'Name' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_waterlevels_name.isChecked()
|
|
print("_cb_waterlevels_name_toggled: %s" % (checked,))
|
|
self._waterlevels_update_labels()
|
|
|
|
def _cb_waterlevels_number_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Pegelstand-Attributs 'Number' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_waterlevels_number.isChecked()
|
|
print("_cb_waterlevels_number_toggled: %s" % (checked,))
|
|
self._waterlevels_update_labels()
|
|
|
|
def _cb_waterlevels_agency_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Pegelstand-Attributs 'Agency' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_waterlevels_agency.isChecked()
|
|
print("_cb_waterlevels_agency_toggled: %s" % (checked,))
|
|
self._waterlevels_update_labels()
|
|
|
|
def _cb_waterlevels_timestamp_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Pegelstand-Attributs 'Timestamp' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_waterlevels_timestamp.isChecked()
|
|
print("_cb_waterlevels_timestamp_toggled: %s" % (checked,))
|
|
self._waterlevels_update_labels()
|
|
|
|
def _cb_waterlevels_value_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Pegelstand-Attributs 'Value' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_waterlevels_value.isChecked()
|
|
print("_cb_waterlevels_value_toggled: %s" % (checked,))
|
|
self._waterlevels_update_labels()
|
|
|
|
def _cb_waterlevels_mean_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Pegelstand-Attributs 'Mean' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_waterlevels_mean.isChecked()
|
|
print("_cb_waterlevels_mean_toggled: %s" % (checked,))
|
|
self._waterlevels_update_labels()
|
|
|
|
def _cb_waterlevels_absolute_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Pegelstand-Attributs 'Absolute' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_waterlevels_absolute.isChecked()
|
|
print("_cb_waterlevels_absolute_toggled: %s" % (checked,))
|
|
self._waterlevels_update_labels()
|
|
|
|
def _cb_waterlevels_water_toggled(self):
|
|
"""
|
|
Sichtbarkeit des Pegelstand-Attributs 'Water' umschalten und Labels updaten
|
|
"""
|
|
checked = self.ui.cb_waterlevels_water.isChecked()
|
|
print("_cb_waterlevels_water_toggled: %s" % (checked,))
|
|
self._waterlevels_update_labels()
|
|
|
|
def _waterlevels_disconnect(self):
|
|
"""
|
|
Löscht die Pegelstand-Layer-Referenz, nachdem der Layer aus QGIS gelöscht wurde und passt die Checkbox an
|
|
"""
|
|
print("waterlevels_disconnect")
|
|
self.waterlevels = None
|
|
self.ui.cb_waterlevels_visible.setChecked(False)
|
|
|
|
def _waterlevels_update_labels(self):
|
|
"""
|
|
Führt Änderungen an Pegelstand-Labels durch
|
|
"""
|
|
print("_waterlevels_update_labels")
|
|
if self.waterlevels is None:
|
|
return
|
|
|
|
fields = []
|
|
if self.ui.cb_waterlevels_number.isChecked():
|
|
# Attribut 'number' ausgewählt
|
|
fields.append('\'#\', "number"')
|
|
if self.ui.cb_waterlevels_name.isChecked():
|
|
# Attribut 'shortname' ausgewählt
|
|
fields.append('"shortname"')
|
|
if self.ui.cb_waterlevels_agency.isChecked():
|
|
# Attribut 'agency' ausgewählt
|
|
fields.append('"agency"')
|
|
if self.ui.cb_waterlevels_water.isChecked():
|
|
# Attribut 'water' ausgewählt
|
|
fields.append('"water"')
|
|
if self.ui.cb_waterlevels_timestamp.isChecked():
|
|
# Attribut 'timestamp' ausgewählt
|
|
fields.append('"timestamp"')
|
|
if self.ui.cb_waterlevels_value.isChecked():
|
|
# Attribut 'value' ausgewählt
|
|
fields.append('"value", \' \', "unit"') # 3 Teile anhängen: value, leerzeichen, unit
|
|
if self.ui.cb_waterlevels_mean.isChecked():
|
|
# Attribut 'mean' ausgewählt
|
|
fields.append('\'MnwMhw=\', "mean"')
|
|
if self.ui.cb_waterlevels_absolute.isChecked():
|
|
# Attribut 'absolute' ausgewählt
|
|
fields.append('\'NswHsw=\', "absolute"')
|
|
|
|
self._layer_update_labels(self.waterlevels, fields)
|
|
|
|
# layers ------------------------------------------------------------------
|
|
|
|
def _layer_create_from_features(self, fields, crs, features, title) -> None | QgsVectorLayer:
|
|
"""
|
|
Erzeugt einen neuen Layer aus QGIS-Features
|
|
:param fields: Anzulegende QGIS-Felder
|
|
:type fields: list[QgsField]
|
|
:param crs: Bezugs-Koordinaten-System
|
|
:type crs: QgsCoordinateReferenceSystem
|
|
:param features: Hinzuzufügende QGIS-Features
|
|
:type features: list[QgsFeature]
|
|
"""
|
|
print("_layer_create_from_features")
|
|
|
|
if features is None:
|
|
# Es wurden keine Features mitgegeben → Abbruch
|
|
print("_layer_create_from_features: Keine Features mitgegeben")
|
|
return None
|
|
|
|
layer_path = "Point?crs=%s" % (crs.authid(),)
|
|
print("_layer_create_from_features: layer_path: " + layer_path)
|
|
|
|
layer = QgsVectorLayer(layer_path, title, "memory")
|
|
|
|
if not layer.isValid():
|
|
# Fehler beim Erzeugen des Layers → Abbruch
|
|
print("_layer_create_from_features: QgsVectorLayer nicht gültig: title=%s" % (title,))
|
|
return None
|
|
|
|
# Felder setzen
|
|
provider = layer.dataProvider()
|
|
provider.addAttributes(fields)
|
|
layer.updateFields()
|
|
|
|
# Features zum Provider hinzufügen
|
|
provider.addFeatures(features)
|
|
for error in provider.errors():
|
|
# Liste Fehler auf
|
|
print("_layer_create_from_features: Fehler beim Hinzufügen von Features: " + error)
|
|
|
|
# Über Änderungen benachrichtigen
|
|
layer.updateExtents()
|
|
layer.commitChanges()
|
|
|
|
return layer
|
|
|
|
def _layer_set_visible(self, layer: QgsVectorLayer, visible):
|
|
"""
|
|
Setzt die Sichtbarkeit des gegebenen Layers
|
|
:param layer: zu behandelnder Layer
|
|
:type layer: QgsVectorLayer
|
|
:param visible: Sichtbarkeit
|
|
:type visible: bool
|
|
"""
|
|
print("_layer_set_visible: %s" % (visible,))
|
|
layer_tree = QgsProject.instance().layerTreeRoot().findLayer(layer.id())
|
|
layer_tree.setItemVisibilityChecked(visible)
|
|
|
|
def _layer_add_to_instance(self, layer: QgsVectorLayer, disconnect: Callable[[], None], checkbox: QCheckBox):
|
|
"""
|
|
Fügt einen Layer der QGIS-Instanz hinzu und verbindet dessen Signale
|
|
:param layer: zu behandelnder Layer
|
|
:type layer: QgsVectorLayer
|
|
:param disconnect: Methode die Aufgerufen wird, wenn der Layer gelöscht wird
|
|
:type disconnect: Callable[[], None]
|
|
:param checkbox: Sichtbarkeit-Checkbox um sie via Signal upzudaten
|
|
:type checkbox: QCheckBox
|
|
"""
|
|
print("_layer_add_to_instance")
|
|
if layer is None:
|
|
# Kein Layer mitgegeben → Abbruch
|
|
print("_layer_add_to_instance: Fehler: Übergebener Layer ist None")
|
|
return
|
|
|
|
# disconnect Signal verbinden
|
|
layer.willBeDeleted.connect(disconnect)
|
|
layer.selectionChanged.connect(self._layer_selection_changed)
|
|
|
|
# zur Instanz hinzufügen
|
|
QgsProject.instance().addMapLayer(layer, False)
|
|
|
|
# zum LayerTree hinzufügen
|
|
layer_tree = self.iface.layerTreeCanvasBridge().rootGroup()
|
|
layer_tree.insertChildNode(0, QgsLayerTreeLayer(layer)) # am oberen Ende anhängen → liegt somit über basemap
|
|
|
|
# Signal zur Erkennung von Sichtbarkeitsänderungen verbinden
|
|
self._connect_layer_list_visibility_signal(layer, checkbox)
|
|
|
|
def _connect_layer_list_visibility_signal(self, layer, checkbox):
|
|
"""
|
|
Verbinde das Layer-Sichtbarkeit-Signal der QGIS-Layer-Liste mit unserer Layer-Referenz und unserer Checkbox
|
|
:param layer: zu behandelnder Layer
|
|
:type layer: QgsVectorLayer
|
|
:param checkbox: Sichtbarkeit-Checkbox um sie via Signal upzudaten
|
|
:type checkbox: QCheckBox
|
|
"""
|
|
root = QgsProject.instance().layerTreeRoot()
|
|
node = root.findLayer(layer.id())
|
|
if node:
|
|
# Node existiert → Signal verbinden
|
|
node.visibilityChanged.connect(lambda: self._layer_set_visible_includingCheckbox(layer, node.isVisible(), checkbox))
|
|
print("_layer_add_to_instance: Layer-Sichtbarkeit-Signal verbunden!")
|
|
else:
|
|
# Node nicht gefunden → ignorieren (Programmierfehler)
|
|
print("_layer_add_to_instance: Fehler: Node im Tree nicht gefunden!")
|
|
|
|
def _layer_set_visible_includingCheckbox(self, layer, visible: bool, checkbox: QCheckBox):
|
|
"""
|
|
Setzt die Sichtbarkeit des gegebenen Layers (inklusive gegebener Checkbox)
|
|
:param layer: zu behandelnder Layer
|
|
:type layer: QgsVectorLayer
|
|
:param visible: Sichtbarkeit
|
|
:type visible: bool
|
|
:param checkbox: Sichtbarkeit-Checkbox um sie via Signal upzudaten
|
|
:type checkbox: QCheckBox
|
|
"""
|
|
checkbox.setChecked(visible)
|
|
self._layer_set_visible(layer, visible)
|
|
|
|
def _layer_update_labels(self, layer, fields):
|
|
"""
|
|
Aktualisiert die Labels eines Layers
|
|
:param layer: zu behandelnder Layer
|
|
:type layer: QgsVectorLayer
|
|
:param fields: Anzuzeigende Felder (QGIS-Label-Expressions)
|
|
:type fields: list[str]
|
|
"""
|
|
print("_layer_update_labels")
|
|
labeling = QgsVectorLayerSimpleLabeling(QgsPalLayerSettings())
|
|
|
|
# Feldnamen zu einem Slash-getrennten QGIS-Label-Expression-String zusammenbauen
|
|
expression = ", ' / ', ".join(fields)
|
|
|
|
settings = labeling.settings()
|
|
settings.fieldName = "concat(" + expression + ")"
|
|
settings.isExpression = True
|
|
|
|
layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
|
|
layer.setLabelsEnabled(True)
|
|
|
|
self._layer_refresh(layer)
|
|
|
|
def _layer_refresh(self, layer):
|
|
"""
|
|
Kümmert sich um das neuzeichnen eines Layers
|
|
:param layer: zu behandelnder Layer
|
|
:type layer: QgsVectorLayer
|
|
"""
|
|
print("_layerRefresh")
|
|
if self.iface.mapCanvas().isCachingEnabled():
|
|
layer.triggerRepaint()
|
|
else:
|
|
self.iface.mapCanvas().refresh()
|
|
|
|
# layer selection ---------------------------------------------------------
|
|
|
|
def _layer_selection_changed(self):
|
|
"""
|
|
Wird aufgerufen, wenn sich eine Feature-Markierung auf einem Layer geändert hat.
|
|
Öffnet das Graf-Widget mit gewünschtem Pegelverlauf,
|
|
falls nur ein Feature vom Pegelstand-Layer markiert ist.
|
|
"""
|
|
print("_layer_selection_changed")
|
|
|
|
if self.waterlevels is None or self.waterlevels != self.iface.activeLayer():
|
|
# Falscher Layer aktiv → Abbruch
|
|
print("_layer_selection_changed: Aktueller Layer ist nicht unser Pegelstands-Layer")
|
|
return
|
|
|
|
selected = self.waterlevels.selectedFeatures()
|
|
if len(selected) == 1:
|
|
# Juhu, nur ein Pegelstand ausgewählt → Graph laden
|
|
|
|
if self.ui.sl_graph_station.count() == 0:
|
|
# Wir haben aber noch keine Stationen geladen → machen wir zuerst
|
|
self._graph_load_stations()
|
|
|
|
selected_shortname = selected[0].attribute("shortname")
|
|
print("_layer_selection_changed: Lade Pegelstandsverlauf zur Auswahl: %s" % (selected_shortname,))
|
|
self._graphStation_set_by_shortname(selected_shortname)
|
|
else:
|
|
# Leider 0 oder mehrere Pegelstände ausgewählt → Abbruch
|
|
print("_layer_selection_changed: Anzahl ausgewählter Elemente ist NICHT 1, lade Pegelstandsverlauf NICHT!")
|
|
|
|
# layer styles ------------------------------------------------------------
|
|
|
|
def _layer_apply_style_per_category(self, layer, attribute_name, preset_fallback_file):
|
|
"""
|
|
Färbt die Features des gegebenen Layers kategorisch nach einem Attribut ein.
|
|
:param layer: zu behandelnder Layer
|
|
:type layer: QgsVectorLayer
|
|
:param attribute_name: Name des Attributes nach dem kategorisiert werden soll
|
|
:type attribute_name: str
|
|
:param preset_fallback_file: Preset- bzw Fallback-Style-Datei (.qml)
|
|
:type preset_fallback_file: str
|
|
"""
|
|
print("_layer_apply_style_per_category: Erzeuge kategorisierte Farben...")
|
|
|
|
ramp = QgsStyle().defaultStyle().colorRamp("Turbo")
|
|
if ramp is None:
|
|
# Standard-Ramp 'Turbo' nicht gefunden → Fallback auf Style-Datei
|
|
self._layer_apply_style_from_file(layer, preset_fallback_file)
|
|
print("_layer_apply_style_per_category: Farbrampe nicht gefunden.")
|
|
return
|
|
|
|
attribute_index = layer.fields().indexOf(attribute_name)
|
|
if attribute_index is None or attribute_index < 0:
|
|
# Gegebenes Attribut nicht gefunden → Fallback auf Style-Datei
|
|
self._layer_apply_style_from_file(layer, preset_fallback_file)
|
|
print("_layer_apply_style_per_category: Attribut nicht gefunden: %s" % (attribute_name,))
|
|
return
|
|
|
|
attribute_values = sorted(v for v in layer.uniqueValues(attribute_index) if v is not None)
|
|
|
|
categories = []
|
|
for value in attribute_values:
|
|
# Hole Farbe anhand linear umgerechnetem index von der 'Color-Ramp'
|
|
symbol = QgsSymbol.defaultSymbol(layer.geometryType())
|
|
color_index = attribute_values.index(value) / (len(attribute_values) - 1)
|
|
color = ramp.color(color_index)
|
|
symbol.setColor(color)
|
|
category = QgsRendererCategory(value, symbol, str(value))
|
|
categories.append(category)
|
|
|
|
renderer = QgsCategorizedSymbolRenderer(attribute_name, categories)
|
|
layer.setRenderer(renderer)
|
|
self._layer_refresh(layer)
|
|
|
|
def _layer_apply_style_from_file(self, layer, preset_fallback_file):
|
|
"""
|
|
Wendet die gegebene Style-Datei (.qml) auf den gegebenen Layer an
|
|
:param layer: zu behandelnder Layer
|
|
:type layer: QgsVectorLayer
|
|
:param preset_fallback_file: Style-Datei (.qml)
|
|
:type preset_fallback_file: str
|
|
"""
|
|
path = os.path.join(self.local_dir, preset_fallback_file)
|
|
print("_layer_apply_style_from_file: Lade Style Datei: %s" % (path,))
|
|
|
|
res = layer.loadNamedStyle(path)
|
|
print("_layer_apply_style_from_file: result: %s" % (res,))
|
|
|
|
self._layer_refresh(layer)
|
|
|
|
# graph signals ---------------------------------------------------------
|
|
|
|
def _graph_connect_signals(self):
|
|
"""
|
|
Verbindet alle GrafWidget-Signale mit der UI
|
|
"""
|
|
print("_graph_connect_signals")
|
|
self.ui.sl_graph_station.currentTextChanged.connect(self._sl_graph_station_changed)
|
|
self.ui.btn_graph_stations_refresh.clicked.connect(self._btn_graph_stations_refresh_clicked)
|
|
self.ui.num_graph_days.valueChanged.connect(self._num_graph_days_changed)
|
|
self.ui.btn_graph_load.clicked.connect(self._graph_load_graph)
|
|
|
|
def _sl_graph_station_changed(self):
|
|
"""
|
|
Behandelt die Stations-Änderung und lädt den Pegelstandsverlauf-Graf neu
|
|
"""
|
|
print("_sl_graph_station_changed: %s" % (self.ui.sl_graph_station.currentText(),))
|
|
self._graph_load_graph()
|
|
|
|
def _btn_graph_stations_refresh_clicked(self):
|
|
"""
|
|
Klick auf Graf-Stations-Liste-Refresh:
|
|
Lässt die Stations-Liste für den Graphen neu laden
|
|
"""
|
|
print("_btn_graph_stations_refresh_clicked")
|
|
self._graph_load_stations()
|
|
|
|
def _num_graph_days_changed(self):
|
|
"""
|
|
Loggt lediglich die Graf-Tages-Änderung
|
|
"""
|
|
print("_num_graph_days_changed: %s" % (self.ui.num_graph_days.value(),))
|
|
|
|
def _graph_load_graph(self):
|
|
"""
|
|
Versucht den aktuell gewünschten Pegelstandsverlauf herunterzuladen und im GrafWidget anzuzeigen.
|
|
"""
|
|
print("_graph_load_graph")
|
|
|
|
if not self.ui.sl_graph_station.isEnabled():
|
|
# während dem Aktualisieren der Stationsliste treten change-signale auf, die werden hier abgefangen
|
|
print("_graph_load_graph: Stationsliste ist aktuell gesperrt")
|
|
return
|
|
|
|
station = self.ui.sl_graph_station.currentText()
|
|
days = self.ui.num_graph_days.value()
|
|
|
|
self.graph.load(station, days)
|
|
|
|
def _graph_load_stations(self):
|
|
"""
|
|
Lädt die Stations-Liste für den Graphen neu.
|
|
Sperrt so lange relevante UI-Elemente.
|
|
Versucht die bisher ausgewählten Station in der neuen Liste wiederzufinden.
|
|
"""
|
|
print("_graph_load_stations")
|
|
self.ui.sl_graph_station.setEnabled(False)
|
|
self.ui.btn_graph_load.setEnabled(False)
|
|
|
|
# behalte die aktuelle Station, um sie (mit eventuell neuem Index) wiederherzustellen
|
|
current_station = self.ui.sl_graph_station.currentText()
|
|
print("_graph_load_stations: bisherige_station=%s" % (current_station,))
|
|
|
|
# leere die bisherige Auswahlliste
|
|
self.ui.sl_graph_station.clear()
|
|
|
|
# Lade aktuelle StationsListe herunter
|
|
stations = PoStationReader().get_stations()
|
|
if stations is None or len(stations) == 0:
|
|
# Keine Stationen erhalte → Abbruch
|
|
print("_graph_load_stations: Fehler: Keine Stationen erhalten")
|
|
return
|
|
|
|
for station in stations:
|
|
# Füge ein Item je Station hinzu
|
|
self.ui.sl_graph_station.addItem(station.shortname)
|
|
|
|
if self._graphStation_set_by_shortname(current_station):
|
|
# Bisherige Station wiedergefunden → Wieder ausgewählt
|
|
print("_graph_load_stations: Bisherige Station \"%s\" wiederhergestellt" % (current_station,))
|
|
else:
|
|
# Bisherige Station nicht wiedergefunden → Wähle die erste Station aus der Liste
|
|
self.ui.sl_graph_station.setCurrentIndex(0)
|
|
station = self.ui.sl_graph_station.currentText()
|
|
print("_graph_load_stations: Bisherige Station \"%s\" nicht wiedergefunden. Nehme erste Station: %s" % (current_station, station))
|
|
|
|
self.ui.sl_graph_station.setEnabled(True)
|
|
self.ui.btn_graph_load.setEnabled(True)
|
|
|
|
def _graphStation_set_by_shortname(self, shortname):
|
|
"""
|
|
Setzt eine Graf-Station anhand des gegebenen Kurznamens
|
|
:param shortname: Kurzname der Station die gesetzt werden soll
|
|
:type shortname: str
|
|
"""
|
|
index = self._graphStation_get_index_by_shortname(shortname)
|
|
if index is None:
|
|
# Station nicht gefunden → Abbruch
|
|
return False
|
|
|
|
self.ui.sl_graph_station.setCurrentIndex(index)
|
|
return True
|
|
|
|
def _graphStation_get_index_by_shortname(self, shortname):
|
|
"""
|
|
Sucht den Index einer Graf-Station anhand des gegebenen Kurznamens
|
|
:param shortname: Kurzname der Station die gesucht werden soll
|
|
:type shortname: str
|
|
"""
|
|
print("_graphStation_get_index_by_shortname: shortname=%s" % (shortname,))
|
|
for index in range(self.ui.sl_graph_station.count()):
|
|
text = self.ui.sl_graph_station.itemText(index)
|
|
if shortname == text:
|
|
# Station nicht gefunden → Abbruch
|
|
print("_graphStation_get_index_by_shortname: index=%d" % (index,))
|
|
return index
|
|
|
|
print("_graphStation_get_index_by_shortname: Nicht gefunden")
|
|
return None
|