305 lines
14 KiB
Python
305 lines
14 KiB
Python
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 zu schreibenden 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,
|
|
pfad_tif_quasigeoidundulation,
|
|
dict_koordinaten_ausgleichungsergebnis
|
|
):
|
|
"""
|
|
Erzeugt Ergebnis-DataFrames für ausgeglichene Koordinaten in ECEF (XYZ) und UTM.
|
|
|
|
Die Funktion nimmt ein Dictionary mit ausgeglichenen geozentrisch-kartesischen Koordinaten
|
|
(ECEF/XYZ) entgegen, transformiert diese über die Transformationen-Klasse nach ETRS89/UTM
|
|
(inkl. Höhenangabe; optional unter Nutzung einer Quasigeoidundulations-Datei) und erstellt
|
|
zwei tabellarische Ergebnisdarstellungen:
|
|
|
|
- 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 zu Quasigeoidundulationsdaten als GeoTIFF (optional, für Höhen/PROJ).
|
|
:type pfad_tif_quasigeoidundulution: str | None
|
|
: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 Schlüssel in metadaten sind u. a. "projekt", "bearbeiter" und "datum".
|
|
Erwartete Schlüssel 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"""
|
|
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 40px; line-height: 1.6; color: #333; }}
|
|
h1 {{ color: #2c3e50; border-bottom: 2px solid #2c3e50; }}
|
|
h3 {{ color: #2980b9; margin-top: 30px; }}
|
|
.metadata {{ background: #f9f9f9; padding: 15px; border-radius: 5px; border-left: 5px solid #2980b9; }}
|
|
|
|
/* Styling für das Inhaltsverzeichnis */
|
|
.toc {{ background: #fff; border: 1px solid #ddd; padding: 15px; border-radius: 5px; display: inline-block; min-width: 300px; }}
|
|
.toc ul {{ list-style: none; padding-left: 0; }}
|
|
.toc li {{ margin: 5px 0; }}
|
|
.toc a {{ text-decoration: none; color: #2980b9; font-weight: bold; }}
|
|
.toc a:hover {{ text-decoration: underline; }}
|
|
|
|
/* Styling für Bilder */
|
|
.plot-container {{ margin-top: 20px; text-align: center; background: #fff; padding: 10px; border: 1px solid #ddd; }}
|
|
.plot-img {{ width: 100%; max-width: 1000px; height: auto; border: 1px solid #eee; }}
|
|
.plot-caption {{ font-style: italic; color: #666; margin-top: 5px; }}
|
|
|
|
table {{ border-collapse: collapse; width: 100%; margin-top: 10px; }}
|
|
th, td {{ text-align: left; padding: 8px; border-bottom: 1px solid #ddd; }}
|
|
tr:nth-child(even) {{ background-color: #f2f2f2; }}
|
|
th {{ background-color: #2980b9; color: white; }}
|
|
.footer {{ margin-top: 50px; font-size: 0.8em; color: #7f8c8d; text-align: center; }}
|
|
</style>
|
|
<title>Netzprotokoll - {metadaten['projekt']}</title>
|
|
</head>
|
|
<body>
|
|
<h1>Netzprotokoll: {metadaten['projekt']}</h1>
|
|
|
|
<div class="metadata">
|
|
<p><strong>Bearbeiter:</strong> {metadaten['bearbeiter']}</p>
|
|
<p><strong>Datum:</strong> {metadaten['datum']}</p>
|
|
<p><strong>Ausgleichungsmodus:</strong> Methode der kleinsten Quadrate (L2-Norm)</p>
|
|
<p><strong>Koordinatenreferenzsystem:</strong> {metadaten['koordinatenreferenzsystem']}</p>
|
|
<p><strong>Datumsfestlegung:</strong> {metadaten['datumsfestlegung']}</p>
|
|
<p><strong>Dimension:</strong> 3D</p>
|
|
|
|
</div>
|
|
|
|
<div class="toc">
|
|
<strong>Inhaltsverzeichnis</strong>
|
|
<ul>
|
|
<li><a href="#globaltest">1. Globaltest</a></li>
|
|
<li><a href="#redundanz">2. Redundanzanteile</a></li>
|
|
<li><a href="#lokaltest">3. Lokaltest</a></li>
|
|
<li><a href="#vks">4. Varianzkomponentenschätzung</a></li>
|
|
<li><a href="#aeussere_zuver">5. Äussere Zuverlässigkeit</a></li>
|
|
<li><a href="#Helmertscher_Punktfehler">6. Standardabweichungen und Helmert'scher Punktfehler</a></li>
|
|
<li><a href="#standardellipsen">7. Standardellipsen</a></li>
|
|
<li><a href="#konfidenzellipsen">8. Konfidenzellipsen</a></li>
|
|
<li><a href="#Konfidenzellipse_ENU">9. Konfidenzellipsen im ENU-System</a></li>
|
|
<li><a href="#netzplot_gesamt">10. Netzplot im ENU-System mit Konfidenzellipsen - Gesamtes Netz</a></li>
|
|
<li><a href="#netzplot_zoom">11. Netzplot im ENU-System mit Konfidenzellipsen - Ausschnitt</a></li>
|
|
<li><a href="#koordinaten_xyz">12. Koordinaten Geozentrisch</a></li>
|
|
<li><a href="#koordinaten_utm">13. Koordinaten UTM</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<h3 id="globaltest">1. Globaltest</h3>
|
|
{ergebnisse['df_globaltest'].to_html(index=False)}
|
|
|
|
<h3 id="redundanz">2. Redundanzanteile</h3>
|
|
{ergebnisse['df_redundanz'].to_html(index=False)}
|
|
|
|
<h3 id="lokaltest">3. Lokaltest</h3>
|
|
{ergebnisse['df_lokaltest'].to_html(index=False)}
|
|
|
|
<h3 id="vks">4. Varianzkomponentenschätzung</h3>
|
|
{ergebnisse['df_vks'].to_html(index=False)}
|
|
|
|
<h3 id="aeussere_zuver">5. Äussere Zuverlässigkeit</h3>
|
|
{ergebnisse['df_aeussere_zuver'].to_html(index=False)}
|
|
|
|
<h3 id="Helmertscher_Punktfehler">6. Standardabweichungen und Helmert'scher Punktfehler</h3>
|
|
{ergebnisse['df_punktfehler'].to_html(index=False)}
|
|
|
|
<h3 id="standardellipsen">7. Standardellipsen</h3>
|
|
{ergebnisse['df_ellipsen'].to_html(index=False)}
|
|
|
|
<h3 id="konfidenzellipsen">8. Konfidenzellipsen</h3>
|
|
{ergebnisse['df_konfidenzellipsen'].to_html(index=False)}
|
|
|
|
<h3 id="Konfidenzellipse_ENU">9. Konfidenzellipsen im ENU-System</h3>
|
|
{ergebnisse['df_konfidenzellipsen_enu'].to_html(index=False)}
|
|
|
|
<h3 id="netzplot_gesamt">10. Plots: Konfidenzellipsen im ENU-System (Gesamtes Netz)</h3>
|
|
<div class="plot-container">
|
|
<h4>Gesamtnetz</h4>
|
|
<img src="../Netzqualitaet/netzplot_ellipsen_volle_ausdehnung.png" class="plot-img" alt="Gesamtes Netz">
|
|
<p class="plot-caption">Netzplot im ENU-System mit Konfidenzellipsen - Gesamtes Netz</p>
|
|
</div>
|
|
|
|
<h3 id="netzplot_zoom">11. Plots: Konfidenzellipsen im ENU-System (Auschnitt) </h3>
|
|
<div class="plot-container">
|
|
<h4>Detailansicht (Zoom)</h4>
|
|
<img src="../Netzqualitaet/netzplot_ellipsen_zoom_ansicht.png" class="plot-img" alt="2Netzplot im ENU-System mit Konfidenzellipsen - Gesamtes Netz">
|
|
<p class="plot-caption">Netzplot im ENU-System mit Konfidenzellipsen - Ausschnitt</p>
|
|
</div>
|
|
|
|
<h3 id="koordinaten_xyz">12. Koordinaten Geozentrisch Kartesisch</h3>
|
|
{ergebnisse['df_koordinaten_geozentrisch_kartesisch'].to_html(index=True)}
|
|
|
|
<h3 id="koordinaten_utm">13. Koordinaten UTM</h3>
|
|
{ergebnisse['df_koordinaten_utm'].to_html(index=True)}
|
|
|
|
<div class="footer">
|
|
Erstellt automatisch am {datetime.now().strftime('%d.%m.%Y um %H:%M:%S')}
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# 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}") |