History + code clean

This commit is contained in:
Patrick Haßel 2025-09-27 15:12:41 +02:00
parent d72fbe169d
commit 4691e5b79f
9 changed files with 333 additions and 242 deletions

View File

@ -48,7 +48,7 @@ plugin_path:/home/patrick/.local/share/QGIS/QGIS3/profiles/default/python/plugin
[files]
# Python files that should be deployed with the plugin
python_files: __init__.py pegelonline.py pegelonline_dockwidget.py po_runner.py poGraph2.py
python_files: __init__.py pegelonline.py pegelonline_dockwidget.py po_runner.py
# The main dialog file that is loaded (not compiled)
main_dialog: pegelonline_dockwidget_base.ui

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>290</width>
<height>676</height>
<width>288</width>
<height>660</height>
</rect>
</property>
<property name="maximumSize">
@ -39,8 +39,6 @@
<property name="title">
<string>Optionen:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_7">
@ -61,7 +59,7 @@
<item>
<widget class="QCheckBox" name="cbWaterlevelsAgency">
<property name="text">
<string>Betreiber</string>
<string>Organisation</string>
</property>
</widget>
</item>
@ -100,8 +98,6 @@
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
@ -168,7 +164,7 @@
<item>
<widget class="QCheckBox" name="cbStationsAgency">
<property name="text">
<string>Betreiber</string>
<string>Organisation</string>
</property>
</widget>
</item>
@ -199,35 +195,137 @@
</widget>
</item>
<item row="5" column="0">
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="gbHistory">
<property name="title">
<string>Pegelverlauf:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Station:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox"/>
<widget class="QComboBox" name="slHistoryStation">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnHistoryStationsRefresh">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Neu Laden</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Tage</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox"/>
<widget class="QSpinBox" name="inHistoryDays">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>30</number>
</property>
<property name="value">
<number>14</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnHistoryGo">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Anzeigen</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="lbHistory">
<property name="text">
<string>Bitte wählen...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -1,117 +0,0 @@
import ctypes
from urllib.parse import quote
from PyQt5 import QtCore, QtGui
from PyQt5 import QtWidgets
from .pomodules import poBaseURL
from .pomodules.urlreader import UrlReader
class PoGraphDisplay(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.setupUi(self)
# Layer
self.layer = None
def setupUi(self, poGraphDisplay):
"""Definition der Benutzeroberflaeche des Wasserstanddiagramms mit den dazugehoerigen Funktionen."""
# uebergeordnetes Layout
self.verticalLayout = QtWidgets.QVBoxLayout(poGraphDisplay)
# Erste Zeile ---
self.horizontalLayout = QtWidgets.QHBoxLayout()
# Label Station
self.lbStation = QtWidgets.QLabel()
self.lbStation.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter)
self.horizontalLayout.addWidget(self.lbStation)
self.lbStation.setText("Geben Sie hier eine Station ein:")
# ComboBox Stations
self.comboBox = QtWidgets.QComboBox()
self.comboBox.setEditable(True)
self.horizontalLayout.addWidget(self.comboBox)
# Label Tage
self.lbTage = QtWidgets.QLabel()
self.lbTage.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter)
self.lbTage.setText("Tage")
self.horizontalLayout.addWidget(self.lbTage)
# SpinBox Tage
self.sbTage = QtWidgets.QSpinBox()
self.sbTage.setMinimum(5)
self.sbTage.setMaximum(30)
self.sbTage.setProperty("value", 14)
self.horizontalLayout.addWidget(self.sbTage)
# PushButton Laden
self.pbLaden = QtWidgets.QPushButton()
self.pbLaden.setObjectName("pbLaden")
self.pbLaden.setText("Laden")
self.horizontalLayout.addWidget(self.pbLaden)
# Signal Slots
self.pbLaden.clicked.connect(self.doPbLaden)
self.verticalLayout.addLayout(self.horizontalLayout)
# Zweite Zeile ---
# Label fuer den Graphen
self.lbGraph = QtWidgets.QLabel()
self.lbGraph.setText("")
self.lbGraph.setObjectName("lbGraph")
self.verticalLayout.addWidget(self.lbGraph)
# Quellenhinweis
self.lbDatenquelle = QtWidgets.QLabel()
self.lbDatenquelle.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignLeading | QtCore.Qt.AlignVCenter)
self.lbDatenquelle.setText("Die Daten werden von 'pegelonline.wsv.de' zur Verfügung gestellt.")
self.verticalLayout.addWidget(self.lbDatenquelle)
# Dritte Zeile ---
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
def doLoadGraph(self):
"""Laedt die Stationsdaten anhand der vom Nutzer eingegebenen Daten."""
# Anzahl der Tage in der SpinBox
days = self.getCurrentDays()
# Stationsname aus der ComboBox
station = quote(self.getCurrentStation())
url = poBaseURL + "/stations/%s/W/measurements.png?start=P%dD" % (station, days)
ur = UrlReader(url)
self.img_data = ur.getDataResponse()
if ur.code > 299:
AlertBox = ctypes.windll.user32.MessageBoxW
AlertBox(None, "Beim Laden der Daten ist ein Fehler aufgetreten.", "Fehler", 0)
else:
# Grafik einsetzen
pixmap = QtGui.QPixmap()
pixmap.loadFromData(self.img_data)
self.lbGraph.clear()
self.lbGraph.setPixmap(pixmap)
self.lbGraph.resize(pixmap.width(), pixmap.height())
def doPbLaden(self):
"""Laedt den Graphen der ausgewaehlten Station."""
if self.layer is None:
self.doLoadGraph()
def getCurrentDays(self):
return self.sbTage.value()
def getCurrentStation(self):
return self.comboBox.currentText()

View File

@ -1,11 +1,14 @@
import os.path
from typing import Callable
from PyQt5 import QtGui
from qgis._core import QgsVectorLayer, QgsProject, QgsLayerTreeLayer, QgsPalLayerSettings, QgsVectorLayerSimpleLabeling
from .pegelonline_dockwidget import PegelonlineDockWidget
from .pomodules.poqgscurrentw import PoQgsCurrentW
from .pomodules.poqgsstations import PoQgsStations
from .pomodules.po_history import PoHistory
from .pomodules.po_stations import PoStations
from .pomodules.po_stations_qgs import PoQgsStations
from .pomodules.po_waterlevels_qgs import PoQgsCurrentW
INFIX = ", ' - ', "
@ -27,16 +30,12 @@ class PoRunner(object):
self.connect_basemap_signals()
self.connect_stations_signals()
self.connect_waterlevels_signals()
self.connect_history_signals()
self._historyLoadStations()
# basemap -----------------------------------------------------------------
def disconnectStations(self):
print("disconnectStations")
self.stations = None
self.ui.cbStationsVisible.setChecked(False)
# basemap signals ---------------------------------------------------------
def _basemapCreate(self, path, name, disconnect: Callable[[], None]) -> None | QgsVectorLayer:
print("_basemapCreate: %s" % (name,))
path = os.path.join(self.local_dir, "basemap", path)
@ -59,6 +58,8 @@ class PoRunner(object):
return basemap
# basemap signals ---------------------------------------------------------
def connect_basemap_signals(self):
print("connect_basemap_signals")
self.ui.cbBasemapLines.toggled.connect(self.cbBasemapLinesToggled)
@ -98,28 +99,6 @@ class PoRunner(object):
# stations ----------------------------------------------------------------
# noinspection DuplicatedCode
def _stationsUpdateLabeling(self):
print("_stationsUpdateLabeling")
if self.stations is None:
return
fields = []
if self.ui.cbStationsName.isChecked():
fields.append("shortname")
if self.ui.cbStationsNumber.isChecked():
fields.append("number")
if self.ui.cbStationsAgency.isChecked():
fields.append("agency")
if self.ui.cbStationsKm.isChecked():
fields.append("km")
if self.ui.cbStationsWater.isChecked():
fields.append("water")
self._layerUpdateLabeling(self.stations, fields)
# stations signals --------------------------------------------------------
def connect_stations_signals(self):
print("connect_stations_signals")
# noinspection DuplicatedCode
@ -169,31 +148,32 @@ class PoRunner(object):
print("cbStationsWaterToggled: %s" % (checked,))
self._stationsUpdateLabeling()
# waterlevels -------------------------------------------------------------
def disconnectStations(self):
print("disconnectStations")
self.stations = None
self.ui.cbStationsVisible.setChecked(False)
# noinspection DuplicatedCode
def _waterlevelsUpdateLabeling(self):
if self.waterlevels is None:
def _stationsUpdateLabeling(self):
print("_stationsUpdateLabeling")
if self.stations is None:
return
fields = []
if self.ui.cbWaterlevelsName.isChecked():
if self.ui.cbStationsName.isChecked():
fields.append("shortname")
if self.ui.cbWaterlevelsNumber.isChecked():
if self.ui.cbStationsNumber.isChecked():
fields.append("number")
if self.ui.cbWaterlevelsAgency.isChecked():
if self.ui.cbStationsAgency.isChecked():
fields.append("agency")
if self.ui.cbWaterlevelsTimestamp.isChecked():
fields.append("timestamp")
if self.ui.cbWaterlevelsValue.isChecked():
fields.append("value")
if self.ui.cbWaterlevelsMean.isChecked():
fields.append("stateMnwMhw")
if self.ui.cbWaterlevelsAbsolute.isChecked():
fields.append("stateNswHsw")
self._layerUpdateLabeling(self.waterlevels, fields)
if self.ui.cbStationsKm.isChecked():
fields.append("km")
if self.ui.cbStationsWater.isChecked():
fields.append("water")
# waterlevels signals -----------------------------------------------------
self._layerUpdateLabeling(self.stations, fields)
# waterlevels -------------------------------------------------------------
def connect_waterlevels_signals(self):
print("connect_waterlevels_signals")
@ -261,6 +241,28 @@ class PoRunner(object):
self.waterlevels = None
self.ui.cbWaterlevelsVisible.setChecked(False)
# noinspection DuplicatedCode
def _waterlevelsUpdateLabeling(self):
if self.waterlevels is None:
return
fields = []
if self.ui.cbWaterlevelsName.isChecked():
fields.append("shortname")
if self.ui.cbWaterlevelsNumber.isChecked():
fields.append("number")
if self.ui.cbWaterlevelsAgency.isChecked():
fields.append("agency")
if self.ui.cbWaterlevelsTimestamp.isChecked():
fields.append("timestamp")
if self.ui.cbWaterlevelsValue.isChecked():
fields.append("value")
if self.ui.cbWaterlevelsMean.isChecked():
fields.append("stateMnwMhw")
if self.ui.cbWaterlevelsAbsolute.isChecked():
fields.append("stateNswHsw")
self._layerUpdateLabeling(self.waterlevels, fields)
# layers ------------------------------------------------------------------
def _layerSetVisible(self, basemap: QgsVectorLayer, visible):
@ -338,3 +340,81 @@ class PoRunner(object):
layer.triggerRepaint()
else:
self.iface.mapCanvas().refresh()
# history signals ---------------------------------------------------------
def connect_history_signals(self):
self.ui.slHistoryStation.currentTextChanged.connect(self.slHistoryStationChanged)
self.ui.btnHistoryStationsRefresh.clicked.connect(self.btnHistoryStationsRefreshClicked)
self.ui.inHistoryDays.valueChanged.connect(self.inHistoryDaysChanged)
self.ui.btnHistoryGo.clicked.connect(self._historyLoadGraph)
def btnHistoryStationsRefreshClicked(self):
print("btnHistoryStationsRefreshClicked")
self._historyLoadStations()
def slHistoryStationChanged(self):
print("slHistoryStationChanged: %s" % (self.ui.slHistoryStation.currentText(),))
self._historyLoadGraph()
def inHistoryDaysChanged(self):
print("inHistoryDays: %s" % (self.ui.inHistoryDays.value(),))
def _historyLoadGraph(self):
station = self.ui.slHistoryStation.currentText()
days = self.ui.inHistoryDays.value()
print("_historyLoad: station=%s days=%s" % (station, days))
self.ui.lbHistory.clear()
self.ui.lbHistory.setText("Laden...")
if station == '' or station is None:
print("_historyLoad: Fehler: Ungültige Station: %s" % (station,))
self.ui.lbHistory.setText("Bitte Station wählen...")
return
if days is None or days < 1 or days > 30:
print("_historyLoad: Fehler: Ungültige Anzahl von Tagen: %s" % (days,))
self.ui.lbHistory.setText("Bitte Tage [1, 30] wählen...")
return
history = PoHistory(station, days)
image_data = history.download()
if image_data is None or len(image_data) == 0:
print("_historyLoad: Fehler: Keine Daten erhalten")
self.ui.lbHistory.setText("Fehler beim Download!")
return
pixmap = QtGui.QPixmap()
pixmap.loadFromData(image_data)
self.ui.lbHistory.setPixmap(pixmap)
self.ui.lbHistory.resize(pixmap.width(), pixmap.height())
def _historyLoadStations(self):
print("_init_history_signals")
# merke die aktuelle Station, um sie wiederherzustellen
bisherige_station = self.ui.slHistoryStation.currentText()
self.ui.slHistoryStation.clear()
stations = PoStations().getStations()
if stations is None or len(stations) == 0:
print("_init_history_signals: Fehler: Keine Stationen erhalten")
return
index = 0
neuer_index = None
for station in stations:
shortname = station['attributes']['shortname']
if shortname == bisherige_station:
neuer_index = index
self.ui.slHistoryStation.addItem(shortname)
index += 1
if neuer_index is not None:
self.ui.slHistoryStation.setCurrentIndex(neuer_index)
else:
self.ui.slHistoryStation.setCurrentIndex(0)

21
pomodules/po_history.py Normal file
View File

@ -0,0 +1,21 @@
from urllib.parse import quote
from . import poBaseURL
from .urlreader import UrlReader
class PoHistory(UrlReader):
def __init__(self, station: str, days: int):
super().__init__(poBaseURL + 'stations/%s/W/measurements.png?start=P%dD' % (quote(station), days))
def download(self):
print("download: Getting data...")
image_data = self.getDataResponse()
if image_data is None or len(image_data) == 0:
print("download: Fehler: Keine Daten erhalten")
return None
print("download: Complete")
return image_data

View File

@ -2,17 +2,16 @@ from . import poBaseURL
from .urlreader import UrlReader
class PoStations(object):
class PoStations(UrlReader):
def __init__(self):
self.url = poBaseURL + 'stations.json'
super().__init__(poBaseURL + 'stations.json')
def getStations(self):
print("getStations: Getting data...")
reader = UrlReader(self.url)
stations_json = reader.getJsonResponse()
if stations_json is None:
stations_json = self.getJsonResponse()
if stations_json is None or len(stations_json) == 0:
print("getStations: Keine Stationen erhalten")
return None

View File

@ -2,13 +2,13 @@ from PyQt5.QtCore import QVariant
from qgis._core import QgsCoordinateReferenceSystem, QgsGeometry, QgsPointXY
from qgis.core import QgsFields, QgsFeature, QgsField
from .postations import PoStations
from .po_stations import PoStations
class PoQgsStations(PoStations):
def __init__(self):
PoStations.__init__(self)
super().__init__()
self.fields = None
self.crs = QgsCoordinateReferenceSystem(4326, QgsCoordinateReferenceSystem.EpsgCrsId)
@ -25,7 +25,13 @@ class PoQgsStations(PoStations):
self.fields.append(QgsField('water', QVariant.String))
features = []
for station in self.getStations():
stations = self.getStations()
if stations is None or len(stations) == 0:
print("getStations: Fehler: Keine Stationen erhalten")
return None
for station in stations:
feature = self._getFeatureForStation(station)
features.append(feature)

View File

@ -2,17 +2,16 @@ from . import poBaseURL
from .urlreader import UrlReader
class PoCurrentW(object):
class PoCurrentW(UrlReader):
def __init__(self):
self.url = poBaseURL + 'stations.json?timeseries=W&includeTimeseries=true&includeCurrentMeasurement=true'
super().__init__(poBaseURL + 'stations.json?timeseries=W&includeTimeseries=true&includeCurrentMeasurement=true')
def getCurrentW(self):
print("getCurrentW: Getting data...")
reader = UrlReader(self.url)
stations_json = reader.getJsonResponse()
if stations_json is None:
stations_json = self.getJsonResponse()
if stations_json is None or len(stations_json) == 0:
print("getCurrentW: FEHLER: Keine Stationen erhalten")
return None

View File

@ -2,7 +2,7 @@ from PyQt5.QtCore import QVariant
from qgis._core import QgsCoordinateReferenceSystem, QgsGeometry, QgsPointXY
from qgis.core import QgsFields, QgsFeature, QgsField
from .pocurrentw import PoCurrentW
from .po_waterlevels import PoCurrentW
class PoQgsCurrentW(PoCurrentW):
@ -25,7 +25,12 @@ class PoQgsCurrentW(PoCurrentW):
self.fields.append(QgsField('stateNswHsw', QVariant.String))
features = []
for station in self.getCurrentW():
waterlevels = self.getCurrentW()
if waterlevels is None or len(waterlevels) > 0:
print("getCurrentW: Fehler: Keine Pegelstände erhalten")
return None
for station in waterlevels:
feature = self._getFeatureForStation(station)
features.append(feature)