diff --git a/pegelonline_dockwidget_base.ui b/pegelonline_dockwidget_base.ui index 6e7b083..7442223 100644 --- a/pegelonline_dockwidget_base.ui +++ b/pegelonline_dockwidget_base.ui @@ -193,7 +193,7 @@ - + Vorgabe @@ -673,7 +673,7 @@ 0 - + Vorgabe diff --git a/po_runner.py b/po_runner.py index a6a7719..e467fe5 100644 --- a/po_runner.py +++ b/po_runner.py @@ -19,12 +19,27 @@ WATERLEVELS_QML = "styles/waterlevels.qml" class PoRunner(object): + """ + 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 + """ def __init__(self, ui: PegelonlineDockWidget, graph: PegelonlineDockWidgetGraph, iface): + # Widget-Referenzen self.ui = ui self.graph = graph + # QGIS-Referenz self.iface = iface + self.local_dir = os.path.dirname(os.path.realpath(__file__)) # während dem Aktualisieren der Stationsliste treten change-signale auf, die werden so abgefangen @@ -53,12 +68,24 @@ class PoRunner(object): # basemap ----------------------------------------------------------------- + """ + 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 dem 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-Tips + """ + def _basemap_create(self, path, name, disconnect: Callable[[], None], map_tips) -> None | QgsVectorLayer: 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 @@ -77,40 +104,62 @@ class PoRunner(object): return basemap - # basemap signals --------------------------------------------------------- + """ + Verbindet alle Basemap-Signale mit der UI + """ def _basemap_connect_signals(self): print("_connect_basemap_signals") self.ui.cbBasemapLines.toggled.connect(self._cbBasemapLines_toggled) self.ui.cbBasemapAreas.toggled.connect(self._cbBasemapAreas_toggled) + """ + Behandelt die Sichbarkeitsänderung der Flüsse durch die UI-Checkbox + """ + def _cbBasemapLines_toggled(self): checked = self.ui.cbBasemapLines.isChecked() print("_cbBasemapLines_toggled: %s" % (checked,)) if self.lines is None and checked: + # Flüsse sind noch nicht als Layer angelegt → Anlegen self.lines = self._basemap_create("waters.gpkg|layername=water_l", "Flüsse", self._basemap_disconnect_lines, BASEMAP_MAP_TIPS) 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) + """ + Behandelt die Sichtbarkeitsänderung der Flächen durch die UI-Checkbox + """ + def _cbBasemapAreas_toggled(self): checked = self.ui.cbBasemapAreas.isChecked() print("_cbBasemapAreas_toggled: %s" % (checked,)) if self.areas is None and checked: + # Flächen sind noch nicht als Layer angelegt → Anlegen self.areas = self._basemap_create("waters.gpkg|layername=water_f", "Flächen", self._basemap_disconnect_areas, BASEMAP_MAP_TIPS) 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) + """ + Löscht die Flüsse-Layer-Referenz nachdem der Layer aus QGIS gelöscht wurde und passt die Checkbox an + """ + def _basemap_disconnect_lines(self): print("_basemap_disconnect_lines") self.lines = None self.ui.cbBasemapLines.setChecked(False) + """ + Löscht die Flächen-Layer-Referenz nachdem der Layer aus QGIS gelöscht wurde und passt die Checkbox an + """ + def _basemap_disconnect_areas(self): print("_basemap_disconnect_areas") self.areas = None @@ -118,6 +167,10 @@ class PoRunner(object): # stations ---------------------------------------------------------------- + """ + Verbindet alle Stations-Signale mit der UI + """ + def _stations_connect_signals(self): print("_connect_stations_signals") self.ui.cbStationsVisible.toggled.connect(self._cbStationsVisible_toggled) @@ -128,24 +181,41 @@ class PoRunner(object): self.ui.cbStationsWater.toggled.connect(self._cbStationsWater_toggled) self.ui.bgStationsStyle.buttonClicked.connect(self._bgStationsStyle_clicked) + """ + Wendet eingestellte Styles auf den Stations-Layer an + """ + def _stations_apply_style(self): button = self.ui.bgStationsStyle.checkedButton() self._bgStationsStyle_clicked(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 + """ + def _bgStationsStyle_clicked(self, button): print("_bgStationsStyle_clicked: %s" % (button.objectName(),)) if self.stations is None: + # Stations-Layer nicht geladen → Keine Änderung return - if self.ui.rbStationsStyleSimple == button: + if self.ui.rbStationsStylePreset == button: + # Vorgabe ausgewählt → lade aus Datei self._layer_apply_style_from_file(self.stations, STATIONS_QML) return - elif self.ui.rbStationsStyleAgency == button: + + if self.ui.rbStationsStyleAgency == button: + # Behörde ausgewählt field = "agency" elif self.ui.rbStationsStyleWater == button: + # Gewässer ausgewählt field = "water" else: + # Programmierfehler: Unbekannter Button print("_bgStationsStyle_clicked: Style nicht implementiert: %s" % (button.objectName(),)) self._layer_apply_style_from_file(self.stations, STATIONS_QML) return @@ -153,51 +223,85 @@ class PoRunner(object): self._stations_update_labels() self._layer_apply_style_per_category(self.stations, field, STATIONS_QML) + """ + Schaltet Sichtbarkeit des Stations-Layers um (und erstellt ihn, falls nötig) + """ + def _cbStationsVisible_toggled(self): visible = self.ui.cbStationsVisible.isChecked() print("_cbStationsVisible_toggled: %s" % (visible,)) if self.stations is None and visible: + # Stationen sind noch nicht als Layer angelegt → Anlegen reader = PoStationReaderQgs() features = reader.get_features() - self.stations = self._layer_create_from_reader(reader.fields, reader.crs, features, "Stationen", STATIONS_MAP_TIPS) + self.stations = self._layer_create_from_features(reader.fields, reader.crs, features, "Stationen", STATIONS_MAP_TIPS) self._layer_add_to_instance(self.stations, self._stations_disconnect) 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() + """ + Sichtbarkeit des Stations-Attributs 'Name' umschalten und Labels updaten + """ + def _cbStationsName_toggled(self): checked = self.ui.cbStationsName.isChecked() print("_cbStationsName_toggled: %s" % (checked,)) self._stations_update_labels() + """ + Sichtbarkeit des Stations-Attributs 'Number' umschalten und Labels updaten + """ + def _cbStationsNumber_toggled(self): checked = self.ui.cbStationsNumber.isChecked() print("_cbStationsNumber_toggled: %s" % (checked,)) self._stations_update_labels() + """ + Sichtbarkeit des Stations-Attributs 'Agency' umschalten und Labels updaten + """ + def _cbStationsAgency_toggled(self): checked = self.ui.cbStationsAgency.isChecked() print("_cbStationsAgency_toggled: %s" % (checked,)) self._stations_update_labels() + """ + Sichtbarkeit des Stations-Attributs 'Km' umschalten und Labels updaten + """ + def _cbStationsKm_toggled(self): checked = self.ui.cbStationsKm.isChecked() print("_cbStationsKm_toggled: %s" % (checked,)) self._stations_update_labels() + """ + Sichtbarkeit des Stations-Attributs 'Water' umschalten und Labels updaten + """ + def _cbStationsWater_toggled(self): checked = self.ui.cbStationsWater.isChecked() print("_cbStationsWater_toggled: %s" % (checked,)) self._stations_update_labels() + """ + Löscht die Stations-Layer-Referenz nachdem der Layer aus QGIS gelöscht wurde und passt die Checkbox an + """ + def _stations_disconnect(self): print("_stations_disconnect") self.stations = None self.ui.cbStationsVisible.setChecked(False) + """ + Führt Änderungen an Stations-Labels durch + """ + def _stations_update_labels(self): print("_stations_update_labels") if self.stations is None: @@ -205,20 +309,29 @@ class PoRunner(object): fields = [] if self.ui.cbStationsNumber.isChecked(): + # Attribut 'number' ausgewählt fields.append('\'#\', "number"') if self.ui.cbStationsName.isChecked(): + # Attribut 'shortname' ausgewählt fields.append('"shortname"') if self.ui.cbStationsAgency.isChecked(): + # Attribut 'agency' ausgewählt fields.append('"agency"') if self.ui.cbStationsWater.isChecked(): + # Attribut 'water' ausgewählt fields.append('"water"') if self.ui.cbStationsKm.isChecked(): + # Attribut 'km' ausgewählt fields.append('"km", \' km\'') # 2 Teile anhängen: km, " km" self._layer_update_labels(self.stations, fields) # waterlevels ------------------------------------------------------------- + """ + Verbindet alle Pegelstand-Signale mit der UI + """ + def _waterlevels_connect_signals(self): print("_waterlevels_connect_signals") self.ui.cbWaterlevelsVisible.toggled.connect(self._cbWaterlevelsVisible_toggled) @@ -232,28 +345,47 @@ class PoRunner(object): self.ui.cbWaterlevelsWater.toggled.connect(self._cbWaterlevelsWater_toggled) self.ui.bgWaterlevelsStyle.buttonClicked.connect(self._bgWaterlevelsStyle_clicked) + """ + Wendet eingestellte Styles auf den Pegelstand-Layer an + """ + def _waterlevels_apply_style(self): button = self.ui.bgWaterlevelsStyle.checkedButton() self._bgWaterlevelsStyle_clicked(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 + """ + def _bgWaterlevelsStyle_clicked(self, button): print("_bgWaterlevelsStyle_clicked: %s" % (button.objectName(),)) if self.waterlevels is None: + # Pegelstand-Layer nicht geladen → Keine Änderung return - if self.ui.rbWaterlevelsStyleSimple == button: + if self.ui.rbWaterlevelsStylePreset == button: + # Vorgabe ausgewählt → lade aus Datei self._layer_apply_style_from_file(self.waterlevels, WATERLEVELS_QML) return - elif self.ui.rbWaterlevelsStyleAgency == button: + + if self.ui.rbWaterlevelsStyleAgency == button: + # Behörde ausgewählt field = "agency" elif self.ui.rbWaterlevelsStyleWater == button: + # Gewässer ausgewählt field = "water" elif self.ui.rbWaterlevelsStyleMean == button: + # MNW, MHW ausgewählt field = "mean" elif self.ui.rbWaterlevelsStyleAbsolute == button: + # NSW, HSW ausgewählt field = "absolute" else: + # Programmierfehler: Unbekannter Button print("_bgWaterlevelsStyle_clicked: Style nicht implementiert: %s" % (button.objectName(),)) self._layer_apply_style_from_file(self.waterlevels, WATERLEVELS_QML) return @@ -261,66 +393,112 @@ class PoRunner(object): self._waterlevels_update_labels() self._layer_apply_style_per_category(self.waterlevels, field, WATERLEVELS_QML) + """ + Schaltet Sichtbarkeit des Pegelstand-Layers um (und erstellt ihn, falls nötig) + """ + def _cbWaterlevelsVisible_toggled(self): visible = self.ui.cbWaterlevelsVisible.isChecked() print("_cbWaterlevelsVisible_toggled: %s" % (visible,)) if self.waterlevels is None and visible: + # Pegelstände sind noch nicht als Layer angelegt → Anlegen reader = PoWaterlevelReaderQgs() features = reader.get_features() - self.waterlevels = self._layer_create_from_reader(reader.fields, reader.crs, features, "Pegelstände", WATERLEVELS_MAP_TIPS) + self.waterlevels = self._layer_create_from_features(reader.fields, reader.crs, features, "Pegelstände", WATERLEVELS_MAP_TIPS) self._layer_add_to_instance(self.waterlevels, self.waterlevels_disconnect) 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() + """ + Sichtbarkeit des Pegelstand-Attributs 'Name' umschalten und Labels updaten + """ + def _cbWaterlevelsName_toggled(self): checked = self.ui.cbWaterlevelsName.isChecked() print("_cbWaterlevelsName_toggled: %s" % (checked,)) self._waterlevels_update_labels() + """ + Sichtbarkeit des Pegelstand-Attributs 'Number' umschalten und Labels updaten + """ + def _cbWaterlevelsNumber_toggled(self): checked = self.ui.cbWaterlevelsNumber.isChecked() print("_cbWaterlevelsNumber_toggled: %s" % (checked,)) self._waterlevels_update_labels() + """ + Sichtbarkeit des Pegelstand-Attributs 'Agency' umschalten und Labels updaten + """ + def _cbWaterlevelsAgency_toggled(self): checked = self.ui.cbWaterlevelsAgency.isChecked() print("_cbWaterlevelsAgency_toggled: %s" % (checked,)) self._waterlevels_update_labels() + """ + Sichtbarkeit des Pegelstand-Attributs 'Timestamp' umschalten und Labels updaten + """ + def _cbWaterlevelsTimestamp_toggled(self): checked = self.ui.cbWaterlevelsTimestamp.isChecked() print("_cbWaterlevelsTimestamp_toggled: %s" % (checked,)) self._waterlevels_update_labels() + """ + Sichtbarkeit des Pegelstand-Attributs 'Value' umschalten und Labels updaten + """ + def _cbWaterlevelsValue_toggled(self): checked = self.ui.cbWaterlevelsValue.isChecked() print("_cbWaterlevelsValue_toggled: %s" % (checked,)) self._waterlevels_update_labels() + """ + Sichtbarkeit des Pegelstand-Attributs 'Mean' umschalten und Labels updaten + """ + def _cbWaterlevelsMean_toggled(self): checked = self.ui.cbWaterlevelsMean.isChecked() print("_cbWaterlevelsMean_toggled: %s" % (checked,)) self._waterlevels_update_labels() + """ + Sichtbarkeit des Pegelstand-Attributs 'Absolute' umschalten und Labels updaten + """ + def _cbWaterlevelsAbsolute_toggled(self): checked = self.ui.cbWaterlevelsAbsolute.isChecked() print("_cbWaterlevelsAbsolute_toggled: %s" % (checked,)) self._waterlevels_update_labels() + """ + Sichtbarkeit des Pegelstand-Attributs 'Water' umschalten und Labels updaten + """ + def _cbWaterlevelsWater_toggled(self): checked = self.ui.cbWaterlevelsWater.isChecked() print("_cbWaterlevelsWater_toggled: %s" % (checked,)) self._waterlevels_update_labels() + """ + Löscht die Pegelstand-Layer-Referenz nachdem der Layer aus QGIS gelöscht wurde und passt die Checkbox an + """ + def waterlevels_disconnect(self): print("waterlevels_disconnect") self.waterlevels = None self.ui.cbWaterlevelsVisible.setChecked(False) + """ + Führt Änderungen an Pegelstand-Labels durch + """ + def _waterlevels_update_labels(self): print("_waterlevels_update_labels") if self.waterlevels is None: @@ -328,63 +506,109 @@ class PoRunner(object): fields = [] if self.ui.cbWaterlevelsNumber.isChecked(): + # Attribut 'number' ausgewählt fields.append('\'#\', "number"') if self.ui.cbWaterlevelsName.isChecked(): + # Attribut 'shortname' ausgewählt fields.append('"shortname"') if self.ui.cbWaterlevelsAgency.isChecked(): + # Attribut 'agency' ausgewählt fields.append('"agency"') if self.ui.cbWaterlevelsWater.isChecked(): + # Attribut 'water' ausgewählt fields.append('"water"') if self.ui.cbWaterlevelsTimestamp.isChecked(): + # Attribut 'timestamp' ausgewählt fields.append('"timestamp"') if self.ui.cbWaterlevelsValue.isChecked(): + # Attribut 'value' ausgewählt fields.append('"value", \' \', "unit"') # 3 Teile anhängen: value, leerzeichen, unit if self.ui.cbWaterlevelsMean.isChecked(): + # Attribut 'mean' ausgewählt fields.append('\'MnwMhw=\', "mean"') if self.ui.cbWaterlevelsAbsolute.isChecked(): + # Attribut 'absolute' ausgewählt fields.append('\'NswHsw=\', "absolute"') + self._layer_update_labels(self.waterlevels, fields) # layers ------------------------------------------------------------------ - def _layer_set_visible(self, basemap: QgsVectorLayer, visible): - print("_layer_set_visible: %s => %s" % (basemap.name, visible)) - layer_tree = QgsProject.instance().layerTreeRoot().findLayer(basemap.id()) - layer_tree.setItemVisibilityChecked(visible) + """ + 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] + :param map_tips: HTML für QGIS-Map-Tips + :type map_tips: str + """ + + def _layer_create_from_features(self, fields, crs, features, title, map_tips) -> None | QgsVectorLayer: + print("_layer_create_from_features") - def _layer_create_from_reader(self, fields, crs, features, title, map_tips) -> None | QgsVectorLayer: - print("_layer_create_from_reader") 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_reader: layer_path: " + layer_path) + print("_layer_create_from_features: layer_path: " + layer_path) layer = QgsVectorLayer(layer_path, title, "memory") - provider = layer.dataProvider() + + if not layer.isValid(): + # Fehler beim Erzeugen des Layers → Abbruch + print("_layer_create_from_features: QgsVectorLayer nicht gültig: title=%s" % (title,)) + return None # map-tips setzen layer.setMapTipTemplate(map_tips) + # Felder setzen + provider = layer.dataProvider() provider.addAttributes(fields) layer.updateFields() + # Features zum Provider hinzufügen provider.addFeatures(features) for error in provider.errors(): - print("_layer_create_from_reader: Fehler beim Hinzufügen von Features: " + error) + # Liste Fehler auf + print("_layer_create_from_features: Fehler beim Hinzufügen von Features: " + error) + # Über Änderungen benachrichtigen layer.updateExtents() - layer.commitChanges() - if layer.isValid(): - return layer + return layer - return None + """ + Setzt die Sichtbarkeit des gegebenen Layers + :param layer: Zu behandelnder Layer + :type layer: QgsVectorLayer + :param visible: Sichtbarkeit + :type visible: bool + """ + + def _layer_set_visible(self, layer: QgsVectorLayer, visible): + print("_layer_set_visible: %s => %s" % (layer.name, visible)) + layer_tree = QgsProject.instance().layerTreeRoot().findLayer(layer.id()) + layer_tree.setItemVisibilityChecked(visible) + + """ + 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] + """ def _layer_add_to_instance(self, layer: QgsVectorLayer, disconnect: Callable[[], None]): print("_layer_add_to_instance") if layer is None: + # Kein Layer mitgegeben → Abbruch print("_layer_add_to_instance: Fehler: Übergebener Layer ist None") return @@ -399,28 +623,19 @@ class PoRunner(object): layer_tree = self.iface.layerTreeCanvasBridge().rootGroup() layer_tree.insertChildNode(0, QgsLayerTreeLayer(layer)) # am oberen Ende anhängen → liegt somit über basemap - def _layer_selection_changed(self): - print("_layer_selection_changed") - if self.waterlevels is None or self.waterlevels != self.iface.activeLayer(): - print("_layer_selection_changed: Aktueller Layer ist nicht unser Pegelstands-Layer") - return - - selected = self.waterlevels.selectedFeatures() - if len(selected) == 1: - if self.ui.slHistoryStation.count() == 0: - self._history_load_stations() - - selected_shortname = selected[0].attribute("shortname") - print("_layer_selection_changed: Lade Pegelstandsverlauf zur Auswahl: %s" % (selected_shortname,)) - self._historyStation_set_by_shortname(selected_shortname) - else: - print("_layer_selection_changed: Anzahl ausgewählter Elemente ist NICHT 1, lade Pegelstandsverlauf NICHT!") + """ + Aktualisiert die Labels eines Layers + :param layer: Zu behandelnder Layer + :type layer: QgsVectorLayer + :param fields: Anzuzeigende Felder (QGIS-Label-Expressions) + :type fields: list[str] + """ def _layer_update_labels(self, layer, fields): print("_layer_update_labels") labeling = QgsVectorLayerSimpleLabeling(QgsPalLayerSettings()) - # Feldnamen zu einem Minus-getrennten String zusammenbauen + # Feldnamen zu einem Slash-getrennten QGIS-Label-Expression-String zusammenbauen expression = ", ' / ', ".join(fields) settings = labeling.settings() @@ -432,6 +647,12 @@ class PoRunner(object): self._layer_refresh(layer) + """ + Kümmert sich um das neuzeichnen eines Layers + :param layer: Zu behandelnder Layer + :type layer: QgsVectorLayer + """ + def _layer_refresh(self, layer): print("_layerRefresh") if self.iface.mapCanvas().isCachingEnabled(): @@ -439,35 +660,92 @@ class PoRunner(object): else: self.iface.mapCanvas().refresh() - # layers styles ----------------------------------------------------------- + # layer selection --------------------------------------------------------- - def _layer_apply_style_per_category(self, layer, attribute_name, file): + """ + Wird aufgerufen wenn sich eine Feature-Markierung auf einem Layer geändert hat. + Öffnet das Graph-Widget mit gewünschtem Pegelverlauf, + falls nur ein Feature vom Pegelstand-Layer markiert ist. + """ + + def _layer_selection_changed(self): + 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.slHistoryStation.count() == 0: + # Wir haben aber noch keine Stationen geladen → machen wir zuerst + self._history_load_stations() + + selected_shortname = selected[0].attribute("shortname") + print("_layer_selection_changed: Lade Pegelstandsverlauf zur Auswahl: %s" % (selected_shortname,)) + self._historyStation_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 ------------------------------------------------------------ + + """ + 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 + """ + + def _layer_apply_style_per_category(self, layer, attribute_name, preset_fallback_file): print("_layer_apply_style_per_category: Erzeuge kategorisierte Farben...") + ramp = QgsStyle().defaultStyle().colorRamp("Turbo") if ramp is None: - self._layer_apply_style_from_file(layer, file) + # 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 - idx = layer.fields().indexOf(attribute_name) - values = sorted(v for v in layer.uniqueValues(idx) if v is not None) + 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 - cats = [] - for v in values: - sym = QgsSymbol.defaultSymbol(layer.geometryType()) - if len(values) > 1: - t = values.index(v) / (len(values) - 1) - else: - t = 0.0 - sym.setColor(ramp.color(t)) - cats.append(QgsRendererCategory(v, sym, str(v))) + attribute_values = sorted(v for v in layer.uniqueValues(attribute_index) if v is not None) - renderer = QgsCategorizedSymbolRenderer(attribute_name, cats) + 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, file): - path = os.path.join(self.local_dir, 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 + """ + + def _layer_apply_style_from_file(self, layer, preset_fallback_file): + 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) @@ -477,6 +755,10 @@ class PoRunner(object): # history signals --------------------------------------------------------- + """ + Verbindet alle GraphWidget-Signale mit der UI + """ + def _history_connect_signals(self): print("_history_connect_signals") self.ui.slHistoryStation.currentTextChanged.connect(self._slHistoryStation_changed) @@ -484,17 +766,34 @@ class PoRunner(object): self.ui.numHistoryDays.valueChanged.connect(self._numHistoryDays_changed) self.ui.btnHistoryGo.clicked.connect(self._history_load_graph) + """ + Behandelt die Stations-Änderung und Lädt den Pegelstandsverlauf-Graph neu + """ + def _slHistoryStation_changed(self): print("_slHistoryStation_changed: %s" % (self.ui.slHistoryStation.currentText(),)) self._history_load_graph() + """ + Klick auf Graph-Stations-Liste-Refresh: + Lässt die Stations-Liste für den Graphen neu laden + """ + def _btnHistoryStationsRefresh_clicked(self): print("_btnHistoryStationsRefresh_clicked") self._history_load_stations() + """ + Loggt lediglich die Graph-Tages-Änderung + """ + def _numHistoryDays_changed(self): print("_numHistoryDays_changed: %s" % (self.ui.numHistoryDays.value(),)) + """ + Versucht den aktuell gewünschten Pegelstandsverlauf herunterzuladen und im GraphWidget anzuzeigen. + """ + def _history_load_graph(self): print("_history_load_graph") @@ -513,11 +812,13 @@ class PoRunner(object): self.graph.show() if station == '' or station is None: + # Keine Station ausgewählt → Abbruch print("_history_load_graph: Fehler: Ungültige Station: %s" % (station,)) self.graph.lbHistory.setText("Bitte Station wählen...") return if days is None or days < 1 or days > 30: + # Ungültige Anzahl an Tagen ausgewählt → Abbruch print("_history_load_graph: Fehler: Ungültige Anzahl von Tagen: %s" % (days,)) self.graph.lbHistory.setText("Bitte Tage [1, 30] wählen...") return @@ -526,6 +827,7 @@ class PoRunner(object): image_data = history.download() if image_data is None or len(image_data) == 0: + # Keine Bild-Daten beim Herunterladen erhalten → Abbruch print("_history_load_graph: Fehler: Keine Daten erhalten") self.graph.lbHistory.setText("Fehler beim Download!") return @@ -537,6 +839,12 @@ class PoRunner(object): print("_history_load_graph: Bild erfolgreich geladen") + """ + Lädt die Stations-Liste für den Graphen neu. + Sperrt solange relevante UI-Elemente. + Versucht die bisher ausgewählten Station in der neuen Liste wiederzufinden. + """ + def _history_load_stations(self): print("_history_load_stations") self.ui.slHistoryStation.setEnabled(False) @@ -546,19 +854,25 @@ class PoRunner(object): current_station = self.ui.slHistoryStation.currentText() print("_history_load_stations: bisherige_station=%s" % (current_station,)) + # leere die bisherige Auswahlliste self.ui.slHistoryStation.clear() + # Lade aktuelle StationsListe herunter stations = PoStationReader().get_stations() if stations is None or len(stations) == 0: + # Keine Stationen erhalte → Abbruch print("_history_load_stations: Fehler: Keine Stationen erhalten") return for station in stations: + # Füge ein Item je Station hinzu self.ui.slHistoryStation.addItem(station.shortname) if self._historyStation_set_by_shortname(current_station): + # Bisherige Station wiedergefunden → Wieder ausgewählt print("_history_load_stations: Bisherige Station \"%s\" wiederhergestellt" % (current_station,)) else: + # Bisherige Station nicht wiedergefunden → Wähle die erste Station aus der Liste self.ui.slHistoryStation.setCurrentIndex(0) station = self.ui.slHistoryStation.currentText() print("_history_load_stations: Bisherige Station \"%s\" nicht wiedergefunden. Nehme erste Station: %s" % (current_station, station)) @@ -566,6 +880,12 @@ class PoRunner(object): self.ui.slHistoryStation.setEnabled(True) self.ui.btnHistoryGo.setEnabled(True) + """ + Setzt eine Graph-Station anhand des gegebenen Kurznamens + :param shortname: Kurzname der Station die gesetzt werden soll + :type shortname: str + """ + def _historyStation_set_by_shortname(self, shortname): index = self._historyStation_get_index_by_shortname(shortname) if index is None: @@ -573,6 +893,12 @@ class PoRunner(object): self.ui.slHistoryStation.setCurrentIndex(index) return True + """ + Sucht den Index einer Graph-Station anhand des gegebenen Kurznamens + :param shortname: Kurzname der Station die gesucht werden soll + :type shortname: str + """ + def _historyStation_get_index_by_shortname(self, shortname): print("_historyStation_get_index_by_shortname: shortname=%s" % (shortname,)) for index in range(self.ui.slHistoryStation.count()):