Source code for districtheatingsim.gui.NetSimulationTab.network_data_tab

"""Network Data Tab Module
=======================

Tab for network data file selection and preview visualization.

:author: Dipl.-Ing. (FH) Jonas Pfeiffer
"""

import os

import geopandas as gpd
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
from PyQt6.QtWidgets import (
    QComboBox,
    QFileDialog,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMessageBox,
    QPushButton,
    QVBoxLayout,
    QWidget,
)
from shapely import Point


[docs] class NetworkDataTab(QWidget): """ Widget for selecting network data files and previewing GeoJSON data. """
[docs] def __init__(self, base_path, dialog_config, parent=None): """ Initialize network data tab. :param base_path: Base path for file operations. :type base_path: str :param dialog_config: Configuration data. :type dialog_config: dict :param parent: Parent widget. :type parent: QWidget """ super().__init__(parent) self.base_path = base_path self.dialog_config = dialog_config self.parent = parent self.initUI()
[docs] def initUI(self): """ Initialize user interface components. """ layout = QVBoxLayout(self) importGroup = QGroupBox("Import Netzdaten und Wärmebedarfsrechnung") importGroup.setStyleSheet("QGroupBox { font-size: 11pt; font-weight: bold; }") importLayout = QVBoxLayout() importLayout.addWidget(QLabel("Importtyp Netz:")) self.importTypeComboBox = QComboBox() self.importTypeComboBox.addItems(["GeoJSON"]) importLayout.addWidget(self.importTypeComboBox) self.importTypeComboBox.currentIndexChanged.connect(self.updateInputFieldsVisibility) self.geojsonInputs = self.createGeojsonInputs() for input_layout in self.geojsonInputs: importLayout.addLayout(input_layout) jsonImportLayout = QHBoxLayout() jsonLabel = QLabel("JSON mit Daten:") jsonImportLayout.addWidget(jsonLabel) self.jsonLineEdit = QLineEdit( os.path.join( self.base_path, self.parent.parent.config_manager.get_relative_path("building_load_profile_path") ) ) jsonImportLayout.addWidget(self.jsonLineEdit) jsonBrowseButton = QPushButton("Datei auswählen") jsonBrowseButton.clicked.connect(self.browseJsonFile) jsonImportLayout.addWidget(jsonBrowseButton) importLayout.addLayout(jsonImportLayout) importGroup.setLayout(importLayout) layout.addWidget(importGroup) DiagramsGroup = QGroupBox("Vorschau Netzdaten") DiagramsGroup.setStyleSheet("QGroupBox { font-size: 11pt; font-weight: bold; }") DiagramsLayout = QVBoxLayout() self.figure1 = Figure() self.canvas1 = FigureCanvas(self.figure1) self.canvas1.setMinimumSize(350, 350) self.toolbar1 = NavigationToolbar(self.canvas1, self) DiagramsLayout.addWidget(self.canvas1) DiagramsLayout.addWidget(self.toolbar1) DiagramsGroup.setLayout(DiagramsLayout) layout.addWidget(DiagramsGroup) # Update der Sichtbarkeit self.updateInputFieldsVisibility() self.update_plot()
[docs] def createGeojsonInputs(self): """ Create GeoJSON file input layouts. :return: List of input layouts. :rtype: list """ # Default path for unified network GeoJSON default_network_path = os.path.join( self.base_path, self.parent.parent.config_manager.get_relative_path("dimensioned_net_path") ) file_inputs_layout = self.createFileInputsGeoJSON(default_network_path) inputs = [file_inputs_layout] return inputs
[docs] def createFileInputsGeoJSON(self, default_network_path): """ Create file input widget for unified network GeoJSON file. :param default_network_path: Default path to Wärmenetz.geojson. :type default_network_path: str :return: Layout containing file input. :rtype: QVBoxLayout """ layout = QVBoxLayout() self.networkInput = self.createFileInput("Wärmenetz GeoJSON:", default_network_path) layout.addLayout(self.networkInput) return layout
[docs] def createFileInput(self, label_text, default_text): """ Create file input row with label, text field, and browse button. :param label_text: Label text. :type label_text: str :param default_text: Default file path. :type default_text: str :return: Layout containing file input components. :rtype: QHBoxLayout """ layout = QHBoxLayout() label = QLabel(label_text) line_edit = QLineEdit(default_text) button = QPushButton("Datei auswählen") button.clicked.connect(lambda: self.selectFilename(line_edit)) layout.addWidget(label) layout.addWidget(line_edit) layout.addWidget(button) return layout
[docs] def browseJsonFile(self): """ Open file dialog for JSON file selection. """ fname, _ = QFileDialog.getOpenFileName( self, "Select JSON File", os.path.join( self.base_path, self.parent.parent.config_manager.get_relative_path("building_load_profile_path") ), "JSON Files (*.json);;All Files (*)", ) if fname: self.jsonLineEdit.setText(fname)
[docs] def selectFilename(self, line_edit): """ Open file dialog and update line edit with selected file. :param line_edit: Line edit widget to update. :type line_edit: QLineEdit """ fname, _ = QFileDialog.getOpenFileName( self, "Datei auswählen", "", "All Files (*);;CSV Files (*.csv);;GeoJSON Files (*.geojson)" ) if fname: line_edit.setText(fname) self.update_plot()
[docs] def update_plot(self): """ Update plot visualization based on selected unified GeoJSON file. """ try: # Import schema for feature type identification from districtheatingsim.net_generation.network_geojson_schema import NetworkGeoJSONSchema # Pfad auslesen network_path = self.networkInput.itemAt(1).widget().text() # Datei prüfen, ob sie existiert if not os.path.exists(network_path): raise FileNotFoundError("Die Wärmenetz GeoJSON-Datei wurde nicht gefunden.") # Datei einlesen network_gdf = gpd.read_file(network_path) # Separate features by type vorlauf = network_gdf[network_gdf["feature_type"] == NetworkGeoJSONSchema.FEATURE_TYPE_FLOW] ruecklauf = network_gdf[network_gdf["feature_type"] == NetworkGeoJSONSchema.FEATURE_TYPE_RETURN] hast = network_gdf[network_gdf["feature_type"] == NetworkGeoJSONSchema.FEATURE_TYPE_BUILDING] erzeugeranlagen = network_gdf[network_gdf["feature_type"] == NetworkGeoJSONSchema.FEATURE_TYPE_GENERATOR] # Plot vorbereiten self.figure1.clear() ax = self.figure1.add_subplot(111) # GeoJSON-Daten plotten vorlauf.plot(ax=ax, color="red") ruecklauf.plot(ax=ax, color="blue") hast.plot(ax=ax, color="green") erzeugeranlagen.plot(ax=ax, color="black") # Annotations vorbereiten annotations = [] for _idx, row in hast.iterrows(): point = row["geometry"].representative_point() # Access building data from nested structure building_data = row.get("building_data", {}) if isinstance(building_data, dict): adresse = building_data.get("Adresse", "N/A") waermebedarf = building_data.get("Wärmebedarf", "N/A") gebaeudetyp = building_data.get("Gebäudetyp", "N/A") vlt_max = building_data.get("VLT_max", "N/A") rlt_max = building_data.get("RLT_max", "N/A") else: # Fallback if data structure is different adresse = "N/A" waermebedarf = "N/A" gebaeudetyp = "N/A" vlt_max = "N/A" rlt_max = "N/A" label = ( f"{adresse}\nWärmebedarf: {waermebedarf}\n" f"Gebäudetyp: {gebaeudetyp}\nVLT_max: {vlt_max}\nRLT_max: {rlt_max}" ) annotation = ax.annotate( label, xy=(point.x, point.y), xytext=(10, 10), textcoords="offset points", bbox=dict(boxstyle="round", fc="w"), ) annotation.set_visible(False) annotations.append((point, annotation)) # Event-Handler für Mausbewegung def on_move(event): if event.xdata is None or event.ydata is None: return visibility_changed = False for point, annotation in annotations: should_be_visible = point.distance(Point(event.xdata, event.ydata)) < 5 if should_be_visible != annotation.get_visible(): visibility_changed = True annotation.set_visible(should_be_visible) if visibility_changed: self.canvas1.draw() # Maus-Bewegung-Event verbinden self.figure1.canvas.mpl_connect("motion_notify_event", on_move) ax.set_title("Visualisierung der GeoJSON-Netz-Daten") ax.set_xlabel("Longitude") ax.set_ylabel("Latitude") except FileNotFoundError as e: # Fehlermeldung anzeigen, wenn Dateien fehlen self.figure1.clear() ax = self.figure1.add_subplot(111) ax.text(0.5, 0.5, "No data available", fontsize=20, ha="center") self.canvas1.draw() msg = QMessageBox() msg.setIcon(QMessageBox.Icon.Warning) msg.setText("Dateien nicht gefunden") msg.setInformativeText(str(e)) msg.setWindowTitle("Fehler") msg.exec() except Exception as e: # Allgemeine Fehlermeldung bei anderen Fehlern msg = QMessageBox() msg.setIcon(QMessageBox.Icon.Critical) msg.setText("Ein Fehler ist aufgetreten") msg.setInformativeText(str(e)) msg.setWindowTitle("Fehler") msg.exec()
[docs] def set_layout_visibility(self, layout, visible): """ Set visibility of all widgets in a layout. :param layout: Layout to update. :type layout: QLayout :param visible: Visibility state. :type visible: bool """ for i in range(layout.count()): item = layout.itemAt(i) widget = item.widget() if widget: widget.setVisible(visible) elif item.layout(): self.set_layout_visibility(item.layout(), visible)
[docs] def set_default_value(self, parameter_row, value): """ Set default value for a parameter row. :param parameter_row: Parameter row layout. :type parameter_row: QHBoxLayout :param value: Default value to set. :type value: str """ # Zugriff auf das QLineEdit Widget in der Parameterzeile und Aktualisieren des Textes for i in range(parameter_row.count()): widget = parameter_row.itemAt(i).widget() if isinstance(widget, QLineEdit): widget.setText(value) break # Beendet die Schleife, sobald das QLineEdit gefunden und aktualisiert wurde
[docs] def updateInputFieldsVisibility(self): """ Update visibility of input fields based on selected options. """ is_geojson = self.importTypeComboBox.currentText() == "GeoJSON" # GeoJSON-spezifische Eingabefelder for input_layout in self.geojsonInputs: self.set_layout_visibility(input_layout, is_geojson)