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