#!/usr/bin/env python3 """ Trimble Geodesy Tool - Hauptprogramm mit GUI Geodätische Vermessungsarbeiten mit JXL-Dateien """ import sys import os from pathlib import Path # Module-Pfad hinzufügen sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QTabWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel, QPushButton, QLineEdit, QTextEdit, QFileDialog, QTableWidget, QTableWidgetItem, QMessageBox, QGroupBox, QComboBox, QSpinBox, QDoubleSpinBox, QCheckBox, QSplitter, QFrame, QScrollArea, QHeaderView, QListWidget, QListWidgetItem, QDialog, QDialogButtonBox, QFormLayout, QProgressBar, QStatusBar, QMenuBar, QMenu, QAction, QToolBar, QStyle ) from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtGui import QFont, QIcon, QPalette, QColor from modules.jxl_parser import JXLParser from modules.cor_generator import CORGenerator, CORPoint from modules.transformation import CoordinateTransformer, LocalSystemTransformer from modules.georeferencing import Georeferencer, ControlPoint from modules.network_adjustment import NetworkAdjustment from modules.reference_point_adjuster import ReferencePointAdjuster, TransformationResult class JXLAnalysisTab(QWidget): """Tab für JXL-Datei Analyse und Bearbeitung""" def __init__(self, parent=None): super().__init__(parent) self.main_window = parent self.setup_ui() def setup_ui(self): layout = QVBoxLayout(self) # Datei laden file_group = QGroupBox("JXL-Datei") file_layout = QHBoxLayout(file_group) self.file_path_edit = QLineEdit() self.file_path_edit.setPlaceholderText("JXL-Datei auswählen...") file_layout.addWidget(self.file_path_edit) browse_btn = QPushButton("Durchsuchen") browse_btn.clicked.connect(self.browse_file) file_layout.addWidget(browse_btn) load_btn = QPushButton("Laden") load_btn.clicked.connect(self.load_file) file_layout.addWidget(load_btn) layout.addWidget(file_group) # Splitter für Info und Tabellen splitter = QSplitter(Qt.Vertical) # Zusammenfassung summary_group = QGroupBox("Zusammenfassung") summary_layout = QVBoxLayout(summary_group) self.summary_text = QTextEdit() self.summary_text.setReadOnly(True) self.summary_text.setMaximumHeight(200) summary_layout.addWidget(self.summary_text) splitter.addWidget(summary_group) # Punkte-Tabelle points_group = QGroupBox("Punkte") points_layout = QVBoxLayout(points_group) self.points_table = QTableWidget() self.points_table.setColumnCount(7) self.points_table.setHorizontalHeaderLabels( ["Name", "Code", "East (X)", "North (Y)", "Elevation (Z)", "Methode", "Aktiv"]) self.points_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) points_layout.addWidget(self.points_table) # Punkt-Aktionen point_actions = QHBoxLayout() remove_point_btn = QPushButton("Ausgewählten Punkt entfernen") remove_point_btn.clicked.connect(self.remove_selected_point) point_actions.addWidget(remove_point_btn) point_actions.addStretch() points_layout.addLayout(point_actions) splitter.addWidget(points_group) # Prismenkonstanten prism_group = QGroupBox("Prismenkonstanten") prism_layout = QVBoxLayout(prism_group) self.prism_table = QTableWidget() self.prism_table.setColumnCount(4) self.prism_table.setHorizontalHeaderLabels( ["Target ID", "Prismentyp", "Konstante [mm]", "Neue Konstante [mm]"]) self.prism_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) prism_layout.addWidget(self.prism_table) prism_actions = QHBoxLayout() apply_prism_btn = QPushButton("Prismenkonstanten übernehmen") apply_prism_btn.clicked.connect(self.apply_prism_changes) prism_actions.addWidget(apply_prism_btn) prism_actions.addStretch() prism_layout.addLayout(prism_actions) splitter.addWidget(prism_group) layout.addWidget(splitter) def browse_file(self): file_path, _ = QFileDialog.getOpenFileName( self, "JXL-Datei öffnen", "", "JXL Files (*.jxl);;All Files (*)") if file_path: self.file_path_edit.setText(file_path) def load_file(self): file_path = self.file_path_edit.text() if not file_path or not os.path.exists(file_path): QMessageBox.warning(self, "Fehler", "Bitte eine gültige JXL-Datei auswählen!") return parser = JXLParser() if parser.parse(file_path): self.main_window.parser = parser self.update_display() self.main_window.statusBar().showMessage(f"JXL-Datei geladen: {file_path}") else: QMessageBox.critical(self, "Fehler", "Datei konnte nicht geladen werden!") def update_display(self): if not self.main_window.parser: return parser = self.main_window.parser # Zusammenfassung self.summary_text.setText(parser.get_summary()) # Punkte-Tabelle points = parser.get_active_points() self.points_table.setRowCount(len(points)) for row, (name, point) in enumerate(sorted(points.items())): self.points_table.setItem(row, 0, QTableWidgetItem(name)) self.points_table.setItem(row, 1, QTableWidgetItem(point.code or "")) self.points_table.setItem(row, 2, QTableWidgetItem(f"{point.east:.4f}" if point.east else "")) self.points_table.setItem(row, 3, QTableWidgetItem(f"{point.north:.4f}" if point.north else "")) self.points_table.setItem(row, 4, QTableWidgetItem(f"{point.elevation:.4f}" if point.elevation else "")) self.points_table.setItem(row, 5, QTableWidgetItem(point.method)) self.points_table.setItem(row, 6, QTableWidgetItem("Ja" if not point.deleted else "Nein")) # Prismen-Tabelle targets = parser.targets self.prism_table.setRowCount(len(targets)) for row, (tid, target) in enumerate(targets.items()): self.prism_table.setItem(row, 0, QTableWidgetItem(tid)) self.prism_table.setItem(row, 1, QTableWidgetItem(target.prism_type)) self.prism_table.setItem(row, 2, QTableWidgetItem(f"{target.prism_constant * 1000:.1f}")) spin = QDoubleSpinBox() spin.setRange(-100, 100) spin.setDecimals(1) spin.setValue(target.prism_constant * 1000) self.prism_table.setCellWidget(row, 3, spin) def remove_selected_point(self): row = self.points_table.currentRow() if row >= 0: name = self.points_table.item(row, 0).text() reply = QMessageBox.question( self, "Bestätigung", f"Punkt '{name}' wirklich entfernen?", QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: self.main_window.parser.remove_point(name) self.update_display() def apply_prism_changes(self): parser = self.main_window.parser if not parser: return for row in range(self.prism_table.rowCount()): tid = self.prism_table.item(row, 0).text() spin = self.prism_table.cellWidget(row, 3) new_const = spin.value() / 1000.0 # mm to m parser.modify_prism_constant(tid, new_const) QMessageBox.information(self, "Info", "Prismenkonstanten wurden aktualisiert!") class CORGeneratorTab(QWidget): """Tab für COR-Datei Generierung""" def __init__(self, parent=None): super().__init__(parent) self.main_window = parent self.cor_generator = None self.setup_ui() def setup_ui(self): layout = QVBoxLayout(self) # Optionen options_group = QGroupBox("Generierungsoptionen") options_layout = QGridLayout(options_group) options_layout.addWidget(QLabel("Methode:"), 0, 0) self.method_combo = QComboBox() self.method_combo.addItems([ "Aus berechneten Koordinaten (ComputedGrid)", "Aus Rohbeobachtungen berechnen" ]) options_layout.addWidget(self.method_combo, 0, 1) self.include_header_check = QCheckBox("Stations-Header einfügen") self.include_header_check.setChecked(True) options_layout.addWidget(self.include_header_check, 1, 0, 1, 2) layout.addWidget(options_group) # Generieren Button generate_btn = QPushButton("COR-Datei generieren") generate_btn.clicked.connect(self.generate_cor) layout.addWidget(generate_btn) # Vorschau preview_group = QGroupBox("Vorschau") preview_layout = QVBoxLayout(preview_group) self.preview_table = QTableWidget() self.preview_table.setColumnCount(4) self.preview_table.setHorizontalHeaderLabels(["Punkt", "X (East)", "Y (North)", "Z (Elev)"]) self.preview_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) preview_layout.addWidget(self.preview_table) # Statistiken self.stats_text = QTextEdit() self.stats_text.setReadOnly(True) self.stats_text.setMaximumHeight(100) preview_layout.addWidget(self.stats_text) layout.addWidget(preview_group) # Export export_group = QGroupBox("Export") export_layout = QHBoxLayout(export_group) export_cor_btn = QPushButton("Als COR speichern") export_cor_btn.clicked.connect(lambda: self.export_file('cor')) export_layout.addWidget(export_cor_btn) export_csv_btn = QPushButton("Als CSV speichern") export_csv_btn.clicked.connect(lambda: self.export_file('csv')) export_layout.addWidget(export_csv_btn) export_txt_btn = QPushButton("Als TXT speichern") export_txt_btn.clicked.connect(lambda: self.export_file('txt')) export_layout.addWidget(export_txt_btn) export_dxf_btn = QPushButton("Als DXF speichern") export_dxf_btn.clicked.connect(lambda: self.export_file('dxf')) export_layout.addWidget(export_dxf_btn) layout.addWidget(export_group) def generate_cor(self): if not self.main_window.parser: QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei laden!") return self.cor_generator = CORGenerator(self.main_window.parser) if self.method_combo.currentIndex() == 0: points = self.cor_generator.generate_from_computed_grid() else: points = self.cor_generator.compute_from_observations() # Tabelle aktualisieren self.preview_table.setRowCount(len(points)) for row, p in enumerate(points): self.preview_table.setItem(row, 0, QTableWidgetItem(p.name)) self.preview_table.setItem(row, 1, QTableWidgetItem(f"{p.x:.4f}")) self.preview_table.setItem(row, 2, QTableWidgetItem(f"{p.y:.4f}")) self.preview_table.setItem(row, 3, QTableWidgetItem(f"{p.z:.4f}")) # Statistiken self.stats_text.setText(self.cor_generator.get_statistics()) self.main_window.statusBar().showMessage(f"{len(points)} Punkte generiert") def export_file(self, format_type): if not self.cor_generator or not self.cor_generator.cor_points: QMessageBox.warning(self, "Fehler", "Bitte zuerst Punkte generieren!") return filters = { 'cor': "COR Files (*.cor)", 'csv': "CSV Files (*.csv)", 'txt': "Text Files (*.txt)", 'dxf': "DXF Files (*.dxf)" } file_path, _ = QFileDialog.getSaveFileName( self, "Speichern unter", "", filters.get(format_type, "All Files (*)")) if file_path: if format_type == 'cor': self.cor_generator.write_cor_file(file_path, self.include_header_check.isChecked()) elif format_type == 'csv': self.cor_generator.export_csv(file_path) elif format_type == 'txt': self.cor_generator.export_txt(file_path) elif format_type == 'dxf': self.cor_generator.export_dxf(file_path) QMessageBox.information(self, "Erfolg", f"Datei gespeichert: {file_path}") class TransformationTab(QWidget): """Tab für Koordinatentransformation""" def __init__(self, parent=None): super().__init__(parent) self.main_window = parent self.transformer = CoordinateTransformer() self.setup_ui() def setup_ui(self): layout = QVBoxLayout(self) # Methode auswählen method_group = QGroupBox("Transformationsmethode") method_layout = QVBoxLayout(method_group) self.manual_radio = QCheckBox("Manuelle Parameter") self.manual_radio.setChecked(True) self.manual_radio.stateChanged.connect(self.toggle_method) method_layout.addWidget(self.manual_radio) self.twopoint_radio = QCheckBox("Über 2 Punkte definieren") self.twopoint_radio.stateChanged.connect(self.toggle_method) method_layout.addWidget(self.twopoint_radio) layout.addWidget(method_group) # Manuelle Parameter self.manual_group = QGroupBox("Manuelle Parameter") manual_layout = QGridLayout(self.manual_group) manual_layout.addWidget(QLabel("Verschiebung X (East):"), 0, 0) self.dx_spin = QDoubleSpinBox() self.dx_spin.setRange(-1000000, 1000000) self.dx_spin.setDecimals(4) manual_layout.addWidget(self.dx_spin, 0, 1) manual_layout.addWidget(QLabel("m"), 0, 2) manual_layout.addWidget(QLabel("Verschiebung Y (North):"), 1, 0) self.dy_spin = QDoubleSpinBox() self.dy_spin.setRange(-1000000, 1000000) self.dy_spin.setDecimals(4) manual_layout.addWidget(self.dy_spin, 1, 1) manual_layout.addWidget(QLabel("m"), 1, 2) manual_layout.addWidget(QLabel("Verschiebung Z (Höhe):"), 2, 0) self.dz_spin = QDoubleSpinBox() self.dz_spin.setRange(-1000000, 1000000) self.dz_spin.setDecimals(4) manual_layout.addWidget(self.dz_spin, 2, 1) manual_layout.addWidget(QLabel("m"), 2, 2) manual_layout.addWidget(QLabel("Rotation:"), 3, 0) self.rotation_spin = QDoubleSpinBox() self.rotation_spin.setRange(-400, 400) self.rotation_spin.setDecimals(6) manual_layout.addWidget(self.rotation_spin, 3, 1) manual_layout.addWidget(QLabel("gon"), 3, 2) manual_layout.addWidget(QLabel("Drehpunkt X:"), 4, 0) self.pivot_x_spin = QDoubleSpinBox() self.pivot_x_spin.setRange(-1000000, 1000000) self.pivot_x_spin.setDecimals(4) manual_layout.addWidget(self.pivot_x_spin, 4, 1) manual_layout.addWidget(QLabel("Drehpunkt Y:"), 5, 0) self.pivot_y_spin = QDoubleSpinBox() self.pivot_y_spin.setRange(-1000000, 1000000) self.pivot_y_spin.setDecimals(4) manual_layout.addWidget(self.pivot_y_spin, 5, 1) layout.addWidget(self.manual_group) # 2-Punkte-Definition self.twopoint_group = QGroupBox("2-Punkte-Definition") twopoint_layout = QGridLayout(self.twopoint_group) twopoint_layout.addWidget(QLabel("Ursprung (0,0):"), 0, 0) self.origin_combo = QComboBox() twopoint_layout.addWidget(self.origin_combo, 0, 1) twopoint_layout.addWidget(QLabel("Y-Richtung:"), 1, 0) self.direction_combo = QComboBox() twopoint_layout.addWidget(self.direction_combo, 1, 1) twopoint_layout.addWidget(QLabel("Z-Referenz (0):"), 2, 0) self.zref_combo = QComboBox() twopoint_layout.addWidget(self.zref_combo, 2, 1) refresh_btn = QPushButton("Punktliste aktualisieren") refresh_btn.clicked.connect(self.refresh_point_lists) twopoint_layout.addWidget(refresh_btn, 3, 0, 1, 2) self.twopoint_group.setVisible(False) layout.addWidget(self.twopoint_group) # Transformation durchführen transform_btn = QPushButton("Transformation durchführen") transform_btn.clicked.connect(self.execute_transformation) layout.addWidget(transform_btn) # Ergebnisse results_group = QGroupBox("Ergebnisse") results_layout = QVBoxLayout(results_group) self.results_text = QTextEdit() self.results_text.setReadOnly(True) results_layout.addWidget(self.results_text) # Export export_layout = QHBoxLayout() export_report_btn = QPushButton("Bericht exportieren") export_report_btn.clicked.connect(self.export_report) export_layout.addWidget(export_report_btn) export_points_btn = QPushButton("Punkte exportieren") export_points_btn.clicked.connect(self.export_points) export_layout.addWidget(export_points_btn) results_layout.addLayout(export_layout) layout.addWidget(results_group) def toggle_method(self): self.manual_group.setVisible(self.manual_radio.isChecked()) self.twopoint_group.setVisible(self.twopoint_radio.isChecked()) if self.twopoint_radio.isChecked(): self.refresh_point_lists() def refresh_point_lists(self): if not self.main_window.parser: return points = list(self.main_window.parser.get_active_points().keys()) self.origin_combo.clear() self.direction_combo.clear() self.zref_combo.clear() self.zref_combo.addItem("(wie Ursprung)") for name in sorted(points): self.origin_combo.addItem(name) self.direction_combo.addItem(name) self.zref_combo.addItem(name) def execute_transformation(self): if not self.main_window.parser: QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei laden!") return # Punkte aus Parser holen points = [] for name, p in self.main_window.parser.get_active_points().items(): if p.east is not None and p.north is not None: points.append(CORPoint( name=name, x=p.east, y=p.north, z=p.elevation or 0.0 )) self.transformer.set_points(points) if self.manual_radio.isChecked(): self.transformer.set_manual_parameters( dx=self.dx_spin.value(), dy=self.dy_spin.value(), dz=self.dz_spin.value(), rotation_gon=self.rotation_spin.value(), pivot_x=self.pivot_x_spin.value(), pivot_y=self.pivot_y_spin.value() ) elif self.twopoint_radio.isChecked(): origin = self.origin_combo.currentText() direction = self.direction_combo.currentText() zref = self.zref_combo.currentText() if zref == "(wie Ursprung)": zref = None if not self.transformer.compute_from_two_points(origin, direction, zref): QMessageBox.warning(self, "Fehler", "Punkte nicht gefunden!") return self.transformer.transform() # Ergebnisse anzeigen report = self.transformer.get_parameters_report() report += "\n\n" report += self.transformer.get_comparison_table() self.results_text.setText(report) self.main_window.statusBar().showMessage("Transformation durchgeführt") def export_report(self): file_path, _ = QFileDialog.getSaveFileName( self, "Bericht speichern", "", "Text Files (*.txt)") if file_path: with open(file_path, 'w', encoding='utf-8') as f: f.write(self.results_text.toPlainText()) QMessageBox.information(self, "Erfolg", f"Bericht gespeichert: {file_path}") def export_points(self): if not self.transformer.transformed_points: QMessageBox.warning(self, "Fehler", "Keine transformierten Punkte vorhanden!") return file_path, _ = QFileDialog.getSaveFileName( self, "Punkte speichern", "", "CSV Files (*.csv)") if file_path: lines = ["Punkt;X;Y;Z"] for p in self.transformer.transformed_points: lines.append(f"{p.name};{p.x:.4f};{p.y:.4f};{p.z:.4f}") with open(file_path, 'w', encoding='utf-8') as f: f.write("\n".join(lines)) QMessageBox.information(self, "Erfolg", f"Punkte gespeichert: {file_path}") class GeoreferencingTab(QWidget): """Tab für Georeferenzierung""" def __init__(self, parent=None): super().__init__(parent) self.main_window = parent self.georeferencer = Georeferencer() self.setup_ui() def setup_ui(self): layout = QVBoxLayout(self) # Passpunkte cp_group = QGroupBox("Passpunkte (mind. 3)") cp_layout = QVBoxLayout(cp_group) self.cp_table = QTableWidget() self.cp_table.setColumnCount(7) self.cp_table.setHorizontalHeaderLabels([ "Punkt", "X_lokal", "Y_lokal", "Z_lokal", "X_Ziel", "Y_Ziel", "Z_Ziel"]) self.cp_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) cp_layout.addWidget(self.cp_table) # Passpunkt-Buttons cp_buttons = QHBoxLayout() add_cp_btn = QPushButton("Passpunkt hinzufügen") add_cp_btn.clicked.connect(self.add_control_point) cp_buttons.addWidget(add_cp_btn) remove_cp_btn = QPushButton("Entfernen") remove_cp_btn.clicked.connect(self.remove_control_point) cp_buttons.addWidget(remove_cp_btn) load_local_btn = QPushButton("Lokale Koordinaten aus JXL") load_local_btn.clicked.connect(self.load_local_from_jxl) cp_buttons.addWidget(load_local_btn) load_target_btn = QPushButton("Zielkoordinaten importieren") load_target_btn.clicked.connect(self.load_target_coords) cp_buttons.addWidget(load_target_btn) cp_buttons.addStretch() cp_layout.addLayout(cp_buttons) layout.addWidget(cp_group) # Transformation berechnen calc_btn = QPushButton("Transformation berechnen") calc_btn.clicked.connect(self.calculate_transformation) layout.addWidget(calc_btn) # Ergebnisse results_group = QGroupBox("Ergebnisse") results_layout = QVBoxLayout(results_group) self.results_text = QTextEdit() self.results_text.setReadOnly(True) self.results_text.setFont(QFont("Courier")) results_layout.addWidget(self.results_text) # Export export_layout = QHBoxLayout() export_report_btn = QPushButton("Bericht exportieren") export_report_btn.clicked.connect(self.export_report) export_layout.addWidget(export_report_btn) transform_all_btn = QPushButton("Alle Punkte transformieren") transform_all_btn.clicked.connect(self.transform_all_points) export_layout.addWidget(transform_all_btn) results_layout.addLayout(export_layout) layout.addWidget(results_group) def add_control_point(self): row = self.cp_table.rowCount() self.cp_table.insertRow(row) # Punkt-Auswahl combo = QComboBox() if self.main_window.parser: for name in sorted(self.main_window.parser.get_active_points().keys()): combo.addItem(name) self.cp_table.setCellWidget(row, 0, combo) # Editierbare Felder für Koordinaten for col in range(1, 7): spin = QDoubleSpinBox() spin.setRange(-10000000, 10000000) spin.setDecimals(4) self.cp_table.setCellWidget(row, col, spin) def remove_control_point(self): row = self.cp_table.currentRow() if row >= 0: self.cp_table.removeRow(row) def load_local_from_jxl(self): if not self.main_window.parser: QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei laden!") return for row in range(self.cp_table.rowCount()): combo = self.cp_table.cellWidget(row, 0) if combo: name = combo.currentText() if name in self.main_window.parser.points: p = self.main_window.parser.points[name] self.cp_table.cellWidget(row, 1).setValue(p.east or 0) self.cp_table.cellWidget(row, 2).setValue(p.north or 0) self.cp_table.cellWidget(row, 3).setValue(p.elevation or 0) def load_target_coords(self): file_path, _ = QFileDialog.getOpenFileName( self, "Zielkoordinaten laden", "", "CSV Files (*.csv);;Text Files (*.txt);;All Files (*)") if not file_path: return try: with open(file_path, 'r', encoding='utf-8') as f: lines = f.readlines() # Format: Punkt;X;Y;Z oder Punkt,X,Y,Z coord_dict = {} for line in lines: line = line.strip() if not line or line.startswith('#'): continue parts = line.replace(',', ';').split(';') if len(parts) >= 4: name = parts[0].strip() x = float(parts[1]) y = float(parts[2]) z = float(parts[3]) coord_dict[name] = (x, y, z) # In Tabelle eintragen for row in range(self.cp_table.rowCount()): combo = self.cp_table.cellWidget(row, 0) if combo: name = combo.currentText() if name in coord_dict: x, y, z = coord_dict[name] self.cp_table.cellWidget(row, 4).setValue(x) self.cp_table.cellWidget(row, 5).setValue(y) self.cp_table.cellWidget(row, 6).setValue(z) QMessageBox.information(self, "Erfolg", f"{len(coord_dict)} Koordinaten geladen!") except Exception as e: QMessageBox.critical(self, "Fehler", f"Fehler beim Laden: {e}") def calculate_transformation(self): self.georeferencer.clear_control_points() for row in range(self.cp_table.rowCount()): combo = self.cp_table.cellWidget(row, 0) if not combo: continue name = combo.currentText() local_x = self.cp_table.cellWidget(row, 1).value() local_y = self.cp_table.cellWidget(row, 2).value() local_z = self.cp_table.cellWidget(row, 3).value() target_x = self.cp_table.cellWidget(row, 4).value() target_y = self.cp_table.cellWidget(row, 5).value() target_z = self.cp_table.cellWidget(row, 6).value() self.georeferencer.add_control_point( name, local_x, local_y, local_z, target_x, target_y, target_z) if len(self.georeferencer.control_points) < 3: QMessageBox.warning(self, "Fehler", "Mindestens 3 Passpunkte erforderlich!") return try: self.georeferencer.compute_transformation() report = self.georeferencer.get_transformation_report() self.results_text.setText(report) self.main_window.statusBar().showMessage("Georeferenzierung berechnet") except Exception as e: QMessageBox.critical(self, "Fehler", f"Berechnung fehlgeschlagen: {e}") def export_report(self): file_path, _ = QFileDialog.getSaveFileName( self, "Bericht speichern", "", "Text Files (*.txt)") if file_path: with open(file_path, 'w', encoding='utf-8') as f: f.write(self.results_text.toPlainText()) QMessageBox.information(self, "Erfolg", f"Bericht gespeichert: {file_path}") def transform_all_points(self): if self.georeferencer.result is None: QMessageBox.warning(self, "Fehler", "Bitte zuerst Transformation berechnen!") return if not self.main_window.parser: return # Punkte transformieren points = [] for name, p in self.main_window.parser.get_active_points().items(): if p.east is not None and p.north is not None: points.append(CORPoint( name=name, x=p.east, y=p.north, z=p.elevation or 0)) self.georeferencer.set_points_to_transform(points) transformed = self.georeferencer.transform_points() # Export-Dialog file_path, _ = QFileDialog.getSaveFileName( self, "Transformierte Punkte speichern", "", "CSV Files (*.csv)") if file_path: lines = ["Punkt;X;Y;Z"] for p in transformed: lines.append(f"{p.name};{p.x:.4f};{p.y:.4f};{p.z:.4f}") with open(file_path, 'w', encoding='utf-8') as f: f.write("\n".join(lines)) QMessageBox.information(self, "Erfolg", f"{len(transformed)} Punkte transformiert und gespeichert!") class NetworkAdjustmentTab(QWidget): """Tab für Netzausgleichung""" def __init__(self, parent=None): super().__init__(parent) self.main_window = parent self.adjustment = None self.setup_ui() def setup_ui(self): layout = QVBoxLayout(self) # Konfiguration config_group = QGroupBox("Konfiguration") config_layout = QGridLayout(config_group) config_layout.addWidget(QLabel("Max. Iterationen:"), 0, 0) self.max_iter_spin = QSpinBox() self.max_iter_spin.setRange(1, 100) self.max_iter_spin.setValue(20) config_layout.addWidget(self.max_iter_spin, 0, 1) config_layout.addWidget(QLabel("Konvergenzlimit [mm]:"), 1, 0) self.convergence_spin = QDoubleSpinBox() self.convergence_spin.setRange(0.001, 10) self.convergence_spin.setDecimals(3) self.convergence_spin.setValue(0.01) config_layout.addWidget(self.convergence_spin, 1, 1) config_layout.addWidget(QLabel("Sigma-0 a-priori:"), 2, 0) self.sigma0_spin = QDoubleSpinBox() self.sigma0_spin.setRange(0.1, 10) self.sigma0_spin.setDecimals(2) self.sigma0_spin.setValue(1.0) config_layout.addWidget(self.sigma0_spin, 2, 1) layout.addWidget(config_group) # Festpunkte fixed_group = QGroupBox("Festpunkte") fixed_layout = QVBoxLayout(fixed_group) self.fixed_list = QListWidget() self.fixed_list.setSelectionMode(QListWidget.MultiSelection) fixed_layout.addWidget(self.fixed_list) fixed_buttons = QHBoxLayout() refresh_btn = QPushButton("Liste aktualisieren") refresh_btn.clicked.connect(self.refresh_point_list) fixed_buttons.addWidget(refresh_btn) auto_btn = QPushButton("Auto (Referenzpunkte)") auto_btn.clicked.connect(self.auto_select_fixed) fixed_buttons.addWidget(auto_btn) fixed_layout.addLayout(fixed_buttons) layout.addWidget(fixed_group) # Ausgleichung durchführen adjust_btn = QPushButton("Netzausgleichung durchführen") adjust_btn.clicked.connect(self.run_adjustment) layout.addWidget(adjust_btn) # Ergebnisse results_group = QGroupBox("Ergebnisse") results_layout = QVBoxLayout(results_group) self.results_text = QTextEdit() self.results_text.setReadOnly(True) self.results_text.setFont(QFont("Courier", 9)) results_layout.addWidget(self.results_text) # Export export_layout = QHBoxLayout() export_report_btn = QPushButton("Bericht exportieren") export_report_btn.clicked.connect(self.export_report) export_layout.addWidget(export_report_btn) export_points_btn = QPushButton("Koordinaten exportieren") export_points_btn.clicked.connect(self.export_points) export_layout.addWidget(export_points_btn) results_layout.addLayout(export_layout) layout.addWidget(results_group) def refresh_point_list(self): self.fixed_list.clear() if not self.main_window.parser: return for name in sorted(self.main_window.parser.get_active_points().keys()): item = QListWidgetItem(name) self.fixed_list.addItem(item) def auto_select_fixed(self): if not self.main_window.parser: return self.fixed_list.clearSelection() ref_line = self.main_window.parser.get_reference_line() if ref_line: for i in range(self.fixed_list.count()): item = self.fixed_list.item(i) if item.text() in [ref_line.start_point, ref_line.end_point]: item.setSelected(True) def run_adjustment(self): if not self.main_window.parser: QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei laden!") return # Adjustment erstellen self.adjustment = NetworkAdjustment(self.main_window.parser) self.adjustment.max_iterations = self.max_iter_spin.value() self.adjustment.convergence_limit = self.convergence_spin.value() / 1000.0 self.adjustment.sigma_0_priori = self.sigma0_spin.value() # Beobachtungen extrahieren self.adjustment.extract_observations() self.adjustment.initialize_points() # Festpunkte setzen for item in self.fixed_list.selectedItems(): self.adjustment.set_fixed_point(item.text()) if not self.adjustment.fixed_points: self.adjustment.set_fixed_points_auto() try: result = self.adjustment.adjust() report = self.adjustment.get_adjustment_report() self.results_text.setText(report) status = "konvergiert" if result.converged else "nicht konvergiert" self.main_window.statusBar().showMessage( f"Ausgleichung abgeschlossen ({status}, {result.iterations} Iterationen)") except Exception as e: QMessageBox.critical(self, "Fehler", f"Ausgleichung fehlgeschlagen: {e}") import traceback traceback.print_exc() def export_report(self): if not self.adjustment or not self.adjustment.result: QMessageBox.warning(self, "Fehler", "Keine Ergebnisse vorhanden!") return file_path, _ = QFileDialog.getSaveFileName( self, "Bericht speichern", "", "Text Files (*.txt)") if file_path: self.adjustment.export_report(file_path) QMessageBox.information(self, "Erfolg", f"Bericht gespeichert: {file_path}") def export_points(self): if not self.adjustment or not self.adjustment.result: QMessageBox.warning(self, "Fehler", "Keine Ergebnisse vorhanden!") return file_path, _ = QFileDialog.getSaveFileName( self, "Koordinaten speichern", "", "CSV Files (*.csv)") if file_path: self.adjustment.export_adjusted_points(file_path, 'csv') QMessageBox.information(self, "Erfolg", f"Koordinaten gespeichert: {file_path}") class ReferencePointAdjusterTab(QWidget): """Tab für Referenzpunkt-Anpassung""" def __init__(self, parent=None): super().__init__(parent) self.main_window = parent self.adjuster = ReferencePointAdjuster() self.setup_ui() def setup_ui(self): layout = QVBoxLayout(self) # Info-Gruppe info_group = QGroupBox("Aktueller Referenzpunkt") info_layout = QFormLayout(info_group) self.ref_point_name = QLabel("Nicht geladen") self.ref_point_name.setStyleSheet("font-weight: bold; font-size: 14px;") info_layout.addRow("Punktname:", self.ref_point_name) self.current_east = QLabel("0.000") self.current_north = QLabel("0.000") self.current_elev = QLabel("0.000") info_layout.addRow("East (X):", self.current_east) info_layout.addRow("North (Y):", self.current_north) info_layout.addRow("Elevation (Z):", self.current_elev) refresh_btn = QPushButton("Referenzpunkt aktualisieren") refresh_btn.clicked.connect(self.load_reference_point) info_layout.addRow("", refresh_btn) layout.addWidget(info_group) # Neue Koordinaten Gruppe new_coords_group = QGroupBox("Neue Koordinaten für Referenzpunkt") new_coords_layout = QGridLayout(new_coords_group) new_coords_layout.addWidget(QLabel("East (X):"), 0, 0) self.new_east_spin = QDoubleSpinBox() self.new_east_spin.setRange(-10000000, 10000000) self.new_east_spin.setDecimals(4) self.new_east_spin.setSuffix(" m") new_coords_layout.addWidget(self.new_east_spin, 0, 1) new_coords_layout.addWidget(QLabel("North (Y):"), 1, 0) self.new_north_spin = QDoubleSpinBox() self.new_north_spin.setRange(-10000000, 10000000) self.new_north_spin.setDecimals(4) self.new_north_spin.setSuffix(" m") new_coords_layout.addWidget(self.new_north_spin, 1, 1) new_coords_layout.addWidget(QLabel("Elevation (Z):"), 2, 0) self.new_elev_spin = QDoubleSpinBox() self.new_elev_spin.setRange(-10000, 10000) self.new_elev_spin.setDecimals(4) self.new_elev_spin.setSuffix(" m") new_coords_layout.addWidget(self.new_elev_spin, 2, 1) # Translation anzeigen new_coords_layout.addWidget(QLabel(""), 3, 0) self.delta_label = QLabel("ΔX: 0.000 m | ΔY: 0.000 m | ΔZ: 0.000 m") self.delta_label.setStyleSheet("color: blue;") new_coords_layout.addWidget(self.delta_label, 4, 0, 1, 2) # Koordinaten-Änderungen live berechnen self.new_east_spin.valueChanged.connect(self.update_delta) self.new_north_spin.valueChanged.connect(self.update_delta) self.new_elev_spin.valueChanged.connect(self.update_delta) layout.addWidget(new_coords_group) # Aktionen actions_group = QGroupBox("Aktionen") actions_layout = QHBoxLayout(actions_group) preview_btn = QPushButton("Vorschau berechnen") preview_btn.clicked.connect(self.preview_transformation) preview_btn.setStyleSheet("background-color: #4CAF50; color: white;") actions_layout.addWidget(preview_btn) apply_btn = QPushButton("Transformation anwenden") apply_btn.clicked.connect(self.apply_transformation) apply_btn.setStyleSheet("background-color: #2196F3; color: white;") actions_layout.addWidget(apply_btn) export_btn = QPushButton("Neue JXL exportieren") export_btn.clicked.connect(self.export_jxl) export_btn.setStyleSheet("background-color: #FF9800; color: white;") actions_layout.addWidget(export_btn) layout.addWidget(actions_group) # Vorschau-Tabelle preview_group = QGroupBox("Vorschau der betroffenen Punkte") preview_layout = QVBoxLayout(preview_group) self.preview_table = QTableWidget() self.preview_table.setColumnCount(7) self.preview_table.setHorizontalHeaderLabels( ["Punkt", "Alt X", "Alt Y", "Alt Z", "Neu X", "Neu Y", "Neu Z"]) self.preview_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) preview_layout.addWidget(self.preview_table) layout.addWidget(preview_group) # Bericht report_group = QGroupBox("Transformationsbericht") report_layout = QVBoxLayout(report_group) self.report_text = QTextEdit() self.report_text.setReadOnly(True) self.report_text.setFont(QFont("Courier", 9)) report_layout.addWidget(self.report_text) layout.addWidget(report_group) def load_reference_point(self): """Lädt die Informationen zum Referenzpunkt""" if not self.main_window.parser: QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei im 'JXL-Analyse' Tab laden!") return self.adjuster.set_parser(self.main_window.parser) info = self.adjuster.get_reference_point_info() if not info["found"]: QMessageBox.warning(self, "Fehler", f"Referenzpunkt nicht gefunden: {info['message']}") return self.ref_point_name.setText(info["name"]) self.current_east.setText(f"{info['east']:.4f} m") self.current_north.setText(f"{info['north']:.4f} m") self.current_elev.setText(f"{info['elevation']:.4f} m") # Setze aktuelle Werte als Standard für neue Koordinaten self.new_east_spin.setValue(info['east']) self.new_north_spin.setValue(info['north']) self.new_elev_spin.setValue(info['elevation']) self.main_window.statusBar().showMessage( f"Referenzpunkt '{info['name']}' geladen") def update_delta(self): """Aktualisiert die Anzeige der Verschiebung""" if not self.adjuster.parser: return dx = self.new_east_spin.value() - self.adjuster.original_coords[0] dy = self.new_north_spin.value() - self.adjuster.original_coords[1] dz = self.new_elev_spin.value() - self.adjuster.original_coords[2] self.delta_label.setText( f"ΔX: {dx:+.4f} m | ΔY: {dy:+.4f} m | ΔZ: {dz:+.4f} m") def preview_transformation(self): """Zeigt eine Vorschau der Transformation""" if not self.main_window.parser: QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei laden!") return # Validierung valid, message = self.adjuster.validate_input( self.new_east_spin.value(), self.new_north_spin.value(), self.new_elev_spin.value() ) if not valid: reply = QMessageBox.warning(self, "Warnung", f"Mögliche Probleme erkannt:\n\n{message}\n\nTrotzdem fortfahren?", QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.No: return self.adjuster.set_new_coordinates( self.new_east_spin.value(), self.new_north_spin.value(), self.new_elev_spin.value() ) results = self.adjuster.preview_transformation() # Tabelle aktualisieren self.preview_table.setRowCount(len(results)) for i, result in enumerate(results): self.preview_table.setItem(i, 0, QTableWidgetItem(result.original_point)) self.preview_table.setItem(i, 1, QTableWidgetItem(f"{result.original_coords[0]:.4f}")) self.preview_table.setItem(i, 2, QTableWidgetItem(f"{result.original_coords[1]:.4f}")) self.preview_table.setItem(i, 3, QTableWidgetItem(f"{result.original_coords[2]:.4f}")) self.preview_table.setItem(i, 4, QTableWidgetItem(f"{result.new_coords[0]:.4f}")) self.preview_table.setItem(i, 5, QTableWidgetItem(f"{result.new_coords[1]:.4f}")) self.preview_table.setItem(i, 6, QTableWidgetItem(f"{result.new_coords[2]:.4f}")) # Bericht aktualisieren self.report_text.setText(self.adjuster.get_summary_report()) self.main_window.statusBar().showMessage( f"Vorschau berechnet: {len(results)} Punkte betroffen") def apply_transformation(self): """Wendet die Transformation auf die JXL-Datei an""" if not self.main_window.parser: QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei laden!") return if not self.adjuster.affected_points: QMessageBox.warning(self, "Fehler", "Bitte zuerst eine Vorschau berechnen!") return reply = QMessageBox.question(self, "Bestätigung", f"Soll die Transformation auf {len(self.adjuster.affected_points)} Punkte angewendet werden?\n\n" "Die Änderungen werden in der geladenen JXL-Datei gespeichert.", QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.No: return if self.adjuster.apply_transformation(): # Bericht aktualisieren self.report_text.setText(self.adjuster.get_summary_report()) QMessageBox.information(self, "Erfolg", "Transformation erfolgreich angewendet!\n\n" "Die Koordinaten wurden aktualisiert.\n" "Verwenden Sie 'Neue JXL exportieren' zum Speichern.") self.main_window.statusBar().showMessage("Transformation angewendet") else: QMessageBox.critical(self, "Fehler", "Transformation konnte nicht angewendet werden!") def export_jxl(self): """Exportiert die modifizierte JXL-Datei""" if not self.main_window.parser: QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei laden!") return if not self.adjuster.transformation_applied: reply = QMessageBox.warning(self, "Warnung", "Die Transformation wurde noch nicht angewendet.\n" "Möchten Sie die Originaldatei exportieren?", QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.No: return # Standard-Dateiname vorschlagen original_path = self.main_window.parser.file_path if original_path: import os base, ext = os.path.splitext(original_path) default_name = f"{base}_transformed{ext}" else: default_name = "transformed.jxl" file_path, _ = QFileDialog.getSaveFileName( self, "JXL-Datei exportieren", default_name, "JXL Files (*.jxl)") if file_path: if self.adjuster.export_jxl(file_path): QMessageBox.information(self, "Erfolg", f"JXL-Datei exportiert:\n{file_path}") self.main_window.statusBar().showMessage(f"Exportiert: {file_path}") else: QMessageBox.critical(self, "Fehler", "Fehler beim Exportieren der JXL-Datei!") class MainWindow(QMainWindow): """Hauptfenster der Anwendung""" def __init__(self): super().__init__() self.parser: JXLParser = None self.setup_ui() def setup_ui(self): self.setWindowTitle("Trimble Geodesy Tool - Geodätische Vermessungsarbeiten") self.setMinimumSize(1200, 800) # Menüleiste self.setup_menu() # Statusleiste self.statusBar().showMessage("Bereit") # Zentrale Widget mit Tabs central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # Tab-Widget self.tabs = QTabWidget() self.jxl_tab = JXLAnalysisTab(self) self.tabs.addTab(self.jxl_tab, "📁 JXL-Analyse") self.cor_tab = CORGeneratorTab(self) self.tabs.addTab(self.cor_tab, "📄 COR-Generator") self.transform_tab = TransformationTab(self) self.tabs.addTab(self.transform_tab, "🔄 Transformation") self.georef_tab = GeoreferencingTab(self) self.tabs.addTab(self.georef_tab, "🌍 Georeferenzierung") self.adjust_tab = NetworkAdjustmentTab(self) self.tabs.addTab(self.adjust_tab, "📐 Netzausgleichung") self.refpoint_tab = ReferencePointAdjusterTab(self) self.tabs.addTab(self.refpoint_tab, "📍 Referenzpunkt anpassen") layout.addWidget(self.tabs) def setup_menu(self): menubar = self.menuBar() # Datei-Menü file_menu = menubar.addMenu("&Datei") open_action = QAction("&Öffnen...", self) open_action.setShortcut("Ctrl+O") open_action.triggered.connect(self.open_file) file_menu.addAction(open_action) file_menu.addSeparator() exit_action = QAction("&Beenden", self) exit_action.setShortcut("Ctrl+Q") exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) # Hilfe-Menü help_menu = menubar.addMenu("&Hilfe") about_action = QAction("&Über", self) about_action.triggered.connect(self.show_about) help_menu.addAction(about_action) def open_file(self): file_path, _ = QFileDialog.getOpenFileName( self, "JXL-Datei öffnen", "", "JXL Files (*.jxl);;All Files (*)") if file_path: self.jxl_tab.file_path_edit.setText(file_path) self.jxl_tab.load_file() def show_about(self): QMessageBox.about(self, "Über Trimble Geodesy Tool", "

Trimble Geodesy Tool

" "

Geodätische Vermessungsarbeiten mit JXL-Dateien

" "

Funktionen:

" "" "

Version 1.0

") def main(): app = QApplication(sys.argv) # Stil setzen app.setStyle('Fusion') # Fenster erstellen und anzeigen window = MainWindow() window.show() sys.exit(app.exec_()) if __name__ == "__main__": main()