trimble_geodesy/main.py

1057 lines
40 KiB
Python

#!/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
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 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")
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",
"<h3>Trimble Geodesy Tool</h3>"
"<p>Geodätische Vermessungsarbeiten mit JXL-Dateien</p>"
"<p><b>Funktionen:</b></p>"
"<ul>"
"<li>JXL-Datei Analyse und Bearbeitung</li>"
"<li>COR-Datei Generierung</li>"
"<li>Koordinatentransformation (Rotation/Translation)</li>"
"<li>Georeferenzierung mit Passpunkten</li>"
"<li>Netzausgleichung nach kleinsten Quadraten</li>"
"</ul>"
"<p>Version 1.0</p>")
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()