import csv from datetime import datetime from Koordinatentransformationen import Transformationen import numpy as np import os import pandas as pd import sympy as sp import webbrowser class Export: """Hilfsfunktionen zum Exportieren von Ergebnissen und Protokollen. Die Klasse stellt Methoden zur Verfügung für: - Export von Matrizen in CSV-Dateien, - Export von Ausgleichungsergebnissen (Skalare und Matrizen) in CSV-Dateien, - Erzeugung und Speicherung eines HTML-Protokolls inklusive automatischem Öffnen im Browser. """ @staticmethod def matrix_to_csv(dateiname: str, liste_spaltenbeschriftung: list, liste_zeilenbeschriftung: list, Matrix: np.matrix | sp.Matrix, beschriftung_kopfzeile: object = "") -> None: """Schreibt eine Matrix mit Zeilen- und Spaltenbeschriftungen in eine CSV-Datei. Die Ausgabe erfolgt mit Semikolon als Trennzeichen. Die Kopfzeile enthält optional eine zusätzliche Beschriftung sowie Spaltenbeschriftungen. Zudem werden Zeilenbeschriftungen durchgeführt. :param dateiname: Pfad der zu erstellenden CSV-Datei. :type dateiname: str :param liste_spaltenbeschriftung: Liste der Spaltenbeschriftungen. :type liste_spaltenbeschriftung: list :param liste_zeilenbeschriftung: Liste der Zeilenbeschriftungen. :type liste_zeilenbeschriftung: list :param Matrix: Zu exportierende Matrix. :type Matrix: np.matrix | sp.Matrix :param beschriftung_kopfzeile: Optionaler Eintrag in der linken oberen Zelle der Kopfzeile. :type beschriftung_kopfzeile: object :return: None :rtype: None """ with open(dateiname, "w", newline="", encoding="utf-8") as csvfile: writer = csv.writer(csvfile, delimiter=";") kopfzeile = [beschriftung_kopfzeile] for spaltenbeschriftung in liste_spaltenbeschriftung: kopfzeile.append(str(spaltenbeschriftung)) writer.writerow(kopfzeile) for zeilenbeschriftung, zeile in zip(liste_zeilenbeschriftung, Matrix.tolist()): zeile_als_text = [zeilenbeschriftung] for eintrag in zeile: try: # Dezimaltrenner von Punkt in Komma ändern, weil Python und Excel andere Konventionen vertreten. eintrag_text = str(eintrag).replace(".", ",") try: eintrag_text = float(eintrag_text) except: eintrag_text = eintrag_text except Exception: eintrag_text = str(eintrag) zeile_als_text.append(eintrag_text) writer.writerow(zeile_als_text) @staticmethod def erzeuge_ergebnis_datenframes(pfad_datenbank: str, pfad_tif_quasigeoidundulation: str, dict_koordinaten_ausgleichungsergebnis: dict) -> tuple[pd.DataFrame, pd.DataFrame]: """ Erzeugt Ergebnis-DataFrames für ausgeglichene Koordinaten in ECEF (XYZ) und UTM. Die Funktion nimmt ein Dictionary mit ausgeglichenen geozentrisch-kartesischen Koordinaten entgegen, transformiert diese über die Transformationen-Klasse in UTM mit Normalhöhen unter Nutzung des GCG2016 - df_x_final: Geozentrische Koordinaten X/Y/Z [m], - df_utm_final: UTM-Koordinaten (Rechtswert/Hochwert/Höhe) [m]. :param pfad_datenbank: Pfad zur SQLite-Datenbank (für Koordinaten-/Transformationszugriffe). :type pfad_datenbank: str :param pfad_tif_quasigeoidundulation: Pfad zum GCG 2016 als GeoTIFF. :type pfad_tif_quasigeoidundulution: str :param dict_koordinaten_ausgleichungsergebnis: Dictionary der ausgeglichenen ECEF-Koordinaten je Punkt. :type dict_koordinaten_ausgleichungsergebnis: dict :return: Tuple aus DataFrame der ECEF-Koordinaten (XYZ) und DataFrame der UTM-Koordinaten. :rtype: tuple[pandas.DataFrame, pandas.DataFrame] """ # Transformation initialisieren trafos = Transformationen(pfad_datenbank) dict_koordinaten_utm = trafos.ecef_to_utm( dict_koordinaten_ausgleichungsergebnis, pfad_tif_quasigeoidundulation ) namen = list(dict_koordinaten_ausgleichungsergebnis.keys()) # SymPy-Matrizen zu Liste koordinaten_liste = [] for k in namen: matrix_werte = dict_koordinaten_ausgleichungsergebnis[k] koordinaten_liste.append([ float(matrix_werte[0]), float(matrix_werte[1]), float(matrix_werte[2]) ]) # DataFrame geozentrisch df_x_final = pd.DataFrame( koordinaten_liste, columns=['X [m]', 'Y [m]', 'Z [m]'], index=namen ) df_x_final.index.name = 'Punktnummer' # DataFrame UTM utm_flach = { k: np.array(v).flatten().tolist() for k, v in dict_koordinaten_utm.items() } df_utm_final = pd.DataFrame.from_dict( utm_flach, orient='index', columns=['Rechtswert [m]', 'Hochwert [m]', 'Höhe [m]'] ) df_utm_final.index.name = 'Punktnummer' # Zahlenformatierung df_x_final = df_x_final.map(lambda val: f"{val:.4f}") df_utm_final = df_utm_final.map(lambda val: f"{val:.4f}") return df_x_final, df_utm_final def speichere_html_protokoll(metadaten: dict, ergebnisse: dict) -> None: """Erzeugt ein HTML-Protokoll der Ausgleichungsergebnisse und speichert es als Datei. Es wird der Unterordner Protokolle angelegt, falls dieser noch nicht existiert. Das Protokoll wird als HTML-Datei gespeichert und anschließend im Browser geöffnet. Erwartete Eingaben in metadaten sind u. a. "projekt", "bearbeiter" und "datum". Erwartete Eingaben in ergebnisse sind u. a. "df_globaltest", "df_redundanz", "df_ellipsen", "df_konfidenzellipsen", "df_koordinaten_geozentrisch_kartesisch" und "df_koordinaten_utm". Die zugehörigen Werte müssen die Methode to_html bereitstellen. :param metadaten: Dictionary mit Metadaten zum Protokoll. :type metadaten: dict :param ergebnisse: Dictionary mit Ergebnisobjekten (z. B. DataFrames) zur HTML-Ausgabe. :type ergebnisse: dict :return: None :rtype: None """ for key in ergebnisse: wert = ergebnisse[key] if isinstance(wert, tuple): if len(wert) > 0 and isinstance(wert[0], pd.DataFrame): ergebnisse[key] = wert[0] else: ergebnisse[key] = pd.DataFrame(list(wert)) elif isinstance(wert, dict): ergebnisse[key] = pd.DataFrame([wert]) # Pfad für den Ordner ordner = "Protokolle" if not os.path.exists(ordner): os.makedirs(ordner) dateiname = f"{ordner}/Protokoll_{metadaten['projekt']}.html" abs_path = os.path.abspath(dateiname) # HTML Inhalt html_content = f""" Netzprotokoll - {metadaten['projekt']}

Netzprotokoll: {metadaten['projekt']}

Bearbeiter: {metadaten['bearbeiter']}

Datum: {metadaten['datum']}

Ausgleichungsmodus: Methode der kleinsten Quadrate (L2-Norm)

Koordinatenreferenzsystem: {metadaten['koordinatenreferenzsystem']}

Datumsfestlegung: {metadaten['datumsfestlegung']}

Dimension: 3D

Inhaltsverzeichnis

1. Globaltest

{ergebnisse['df_globaltest'].to_html(index=False)}

2. Redundanzanteile

{ergebnisse['df_redundanz'].to_html(index=False)}

3. Lokaltest

{ergebnisse['df_lokaltest'].to_html(index=False)}

4. Varianzkomponentenschätzung

{ergebnisse['df_vks'].to_html(index=False)}

5. Äussere Zuverlässigkeit

{ergebnisse['df_aeussere_zuver'].to_html(index=False)}

6. Standardabweichungen und Helmert'scher Punktfehler

{ergebnisse['df_punktfehler'].to_html(index=False)}

7. Standardellipsoid

{ergebnisse['df_standardellipsoid'].to_html(index=False)}

8. Konfidenzellipoid

{ergebnisse['df_konfidenzellipsoid'].to_html(index=False)}

9. Konfidenzellipsen im ENU-System

{ergebnisse['df_konfidenzellipsen_enu'].to_html(index=False)}

10. Plots: Konfidenzellipsen im ENU-System (Gesamtes Netz)

Gesamtnetz

Gesamtes Netz

Netzplot im ENU-System mit Konfidenzellipsen - Gesamtes Netz

11. Plots: Konfidenzellipsen im ENU-System (Auschnitt)

Detailansicht (Zoom)

2Netzplot im ENU-System mit Konfidenzellipsen - Gesamtes Netz

Netzplot im ENU-System mit Konfidenzellipsen - Ausschnitt

12. Koordinaten Geozentrisch Kartesisch

{ergebnisse['df_koordinaten_geozentrisch_kartesisch'].to_html(index=True)}

13. Koordinaten UTM

{ergebnisse['df_koordinaten_utm'].to_html(index=True)} """ # Datei schreiben with open(dateiname, "w", encoding="utf-8") as f: f.write(html_content) print(f"✅ Protokoll wurde gespeichert unter: {abs_path}") # Datei automatisch im Standard-Browser öffnen webbrowser.open(f"file://{abs_path}")