1335 lines
51 KiB
Python
1335 lines
51 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
|
|
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",
|
|
"<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()
|