"""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 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