1679 lines
68 KiB
Python
1679 lines
68 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Trimble Geodesy Tool - Hauptprogramm mit GUI
|
||
Geodätische Vermessungsarbeiten mit JXL-Dateien
|
||
Überarbeitet basierend auf Benutzer-Feedback
|
||
"""
|
||
|
||
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, QTreeWidget, QTreeWidgetItem, QAbstractItemView
|
||
)
|
||
from PyQt5.QtCore import Qt, QThread, pyqtSignal
|
||
from PyQt5.QtGui import QFont, QIcon, QPalette, QColor, QBrush
|
||
|
||
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 - Mit TreeView für Stationierungen"""
|
||
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent)
|
||
self.main_window = parent
|
||
self.prism_spin_widgets = {} # {measurement_record_id: QDoubleSpinBox}
|
||
self.control_point_checkboxes = {} # {(station_id, point_name): QCheckBox}
|
||
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(150)
|
||
summary_layout.addWidget(self.summary_text)
|
||
splitter.addWidget(summary_group)
|
||
|
||
# Stationierungen TreeView
|
||
stations_group = QGroupBox("Stationierungen und Messungen")
|
||
stations_layout = QVBoxLayout(stations_group)
|
||
|
||
self.stations_tree = QTreeWidget()
|
||
self.stations_tree.setHeaderLabels([
|
||
"Station/Messung", "Typ", "Prismenkonstante [mm]", "Qualität", "Aktiv"
|
||
])
|
||
self.stations_tree.setColumnCount(5)
|
||
self.stations_tree.setSelectionMode(QAbstractItemView.SingleSelection)
|
||
self.stations_tree.header().setSectionResizeMode(0, QHeaderView.Stretch)
|
||
self.stations_tree.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
|
||
self.stations_tree.header().setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
||
self.stations_tree.header().setSectionResizeMode(3, QHeaderView.ResizeToContents)
|
||
self.stations_tree.header().setSectionResizeMode(4, QHeaderView.ResizeToContents)
|
||
stations_layout.addWidget(self.stations_tree)
|
||
|
||
# Info-Label
|
||
info_label = QLabel("💡 Tipp: Prismenkonstanten können direkt in der Spalte 'Prismenkonstante' geändert werden.\n"
|
||
"Bei freien Stationierungen: Passpunkte mit Checkbox aktivieren/deaktivieren.")
|
||
info_label.setStyleSheet("color: #666; font-style: italic;")
|
||
stations_layout.addWidget(info_label)
|
||
|
||
splitter.addWidget(stations_group)
|
||
|
||
# Punkte-Tabelle (kompaktere Ansicht)
|
||
points_group = QGroupBox("Alle Punkte")
|
||
points_layout = QVBoxLayout(points_group)
|
||
|
||
self.points_table = QTableWidget()
|
||
self.points_table.setColumnCount(6)
|
||
self.points_table.setHorizontalHeaderLabels(
|
||
["Name", "Code", "East (X)", "North (Y)", "Elevation (Z)", "Methode"])
|
||
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)
|
||
|
||
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())
|
||
|
||
# Stationierungen TreeView
|
||
self.update_stations_tree()
|
||
|
||
# Punkte-Tabelle aktualisieren
|
||
self.update_points_table()
|
||
|
||
def update_stations_tree(self):
|
||
"""Aktualisiert den TreeView mit Stationierungen und Messungen"""
|
||
self.stations_tree.clear()
|
||
self.prism_spin_widgets.clear()
|
||
self.control_point_checkboxes.clear()
|
||
|
||
if not self.main_window.parser:
|
||
return
|
||
|
||
parser = self.main_window.parser
|
||
|
||
# Stationen durchgehen
|
||
for station_id, station in parser.stations.items():
|
||
# Station als Hauptknoten
|
||
station_item = QTreeWidgetItem()
|
||
station_item.setText(0, f"📍 {station.name}")
|
||
station_item.setText(1, station.station_type)
|
||
station_item.setData(0, Qt.UserRole, station_id)
|
||
|
||
# Hintergrundfarbe für verschiedene Stationstypen
|
||
if station.station_type == "ReflineStationSetup":
|
||
station_item.setBackground(0, QBrush(QColor(200, 230, 200))) # Hellgrün
|
||
elif station.station_type == "StandardResection":
|
||
station_item.setBackground(0, QBrush(QColor(200, 200, 230))) # Hellblau
|
||
|
||
self.stations_tree.addTopLevelItem(station_item)
|
||
|
||
# Messungen von dieser Station
|
||
measurements = parser.get_measurements_from_station(station_id)
|
||
|
||
# Bei freier Stationierung: Qualität der Passpunkte berechnen
|
||
control_point_residuals = {}
|
||
if station.station_type == "StandardResection":
|
||
control_point_residuals = self.calculate_control_point_quality(
|
||
station_id, station, measurements)
|
||
|
||
for meas in measurements:
|
||
meas_item = QTreeWidgetItem()
|
||
meas_item.setText(0, f" ↳ {meas.name}")
|
||
|
||
# Typ ermitteln
|
||
if meas.classification == "BackSight":
|
||
meas_type = "Passpunkt"
|
||
else:
|
||
meas_type = "Messung"
|
||
meas_item.setText(1, meas_type)
|
||
|
||
# Prismenkonstante als editierbares SpinBox
|
||
if meas.target_id and meas.target_id in parser.targets:
|
||
target = parser.targets[meas.target_id]
|
||
prism_spin = QDoubleSpinBox()
|
||
prism_spin.setRange(-100, 100)
|
||
prism_spin.setDecimals(1)
|
||
prism_spin.setValue(target.prism_constant * 1000) # m -> mm
|
||
prism_spin.setSuffix(" mm")
|
||
prism_spin.setProperty("target_id", meas.target_id)
|
||
prism_spin.valueChanged.connect(
|
||
lambda val, tid=meas.target_id: self.on_prism_changed(tid, val))
|
||
|
||
self.prism_spin_widgets[meas.record_id] = prism_spin
|
||
self.stations_tree.setItemWidget(meas_item, 2, prism_spin)
|
||
|
||
# Bei freier Stationierung: Passpunkt-Qualität und Checkbox
|
||
if station.station_type == "StandardResection" and meas.classification == "BackSight":
|
||
# Qualitätswert anzeigen
|
||
if meas.name in control_point_residuals:
|
||
quality_info = control_point_residuals[meas.name]
|
||
quality_value = quality_info['residual']
|
||
rank = quality_info['rank']
|
||
total = quality_info['total']
|
||
|
||
# Farbcodierung basierend auf Rang
|
||
quality_label = QLabel(f"{quality_value:.1f} mm")
|
||
if rank == 1: # Bester
|
||
quality_label.setStyleSheet("background-color: #90EE90; padding: 2px;") # Grün
|
||
elif rank == total: # Schlechtester
|
||
quality_label.setStyleSheet("background-color: #FFB6C1; padding: 2px;") # Rot
|
||
else: # Mittlere
|
||
quality_label.setStyleSheet("background-color: #FFFF99; padding: 2px;") # Gelb
|
||
|
||
self.stations_tree.setItemWidget(meas_item, 3, quality_label)
|
||
|
||
# Checkbox für Aktivierung/Deaktivierung
|
||
checkbox = QCheckBox()
|
||
checkbox.setChecked(not meas.deleted)
|
||
checkbox.setProperty("station_id", station_id)
|
||
checkbox.setProperty("point_name", meas.name)
|
||
checkbox.stateChanged.connect(
|
||
lambda state, sid=station_id, pn=meas.name:
|
||
self.on_control_point_toggled(sid, pn, state))
|
||
|
||
self.control_point_checkboxes[(station_id, meas.name)] = checkbox
|
||
self.stations_tree.setItemWidget(meas_item, 4, checkbox)
|
||
|
||
station_item.addChild(meas_item)
|
||
|
||
station_item.setExpanded(True)
|
||
|
||
def calculate_control_point_quality(self, station_id, station, measurements):
|
||
"""
|
||
Berechnet die Qualität der Passpunkte einer freien Stationierung.
|
||
Nutzt die Residuen aus der Stationierungsberechnung.
|
||
"""
|
||
parser = self.main_window.parser
|
||
control_points = []
|
||
|
||
for meas in measurements:
|
||
if meas.classification == "BackSight" and not meas.deleted:
|
||
# Residuum berechnen: Differenz zwischen gemessenen und berechneten Koordinaten
|
||
# Vereinfachte Berechnung basierend auf Streckendifferenz
|
||
if meas.edm_distance is not None and meas.name in parser.points:
|
||
target_point = parser.points[meas.name]
|
||
if target_point.east is not None and target_point.north is not None:
|
||
if station.east is not None and station.north is not None:
|
||
# Berechnete Strecke
|
||
dx = target_point.east - station.east
|
||
dy = target_point.north - station.north
|
||
calc_dist = (dx**2 + dy**2)**0.5
|
||
|
||
# Residuum (Differenz zur gemessenen Strecke)
|
||
residual = abs(meas.edm_distance - calc_dist) * 1000 # in mm
|
||
control_points.append((meas.name, residual))
|
||
|
||
# Sortieren und Ränge vergeben
|
||
if not control_points:
|
||
return {}
|
||
|
||
control_points.sort(key=lambda x: x[1])
|
||
result = {}
|
||
for rank, (name, residual) in enumerate(control_points, 1):
|
||
result[name] = {
|
||
'residual': residual,
|
||
'rank': rank,
|
||
'total': len(control_points)
|
||
}
|
||
|
||
return result
|
||
|
||
def on_prism_changed(self, target_id, new_value_mm):
|
||
"""Wird aufgerufen, wenn eine Prismenkonstante geändert wird"""
|
||
if self.main_window.parser and target_id in self.main_window.parser.targets:
|
||
new_value_m = new_value_mm / 1000.0
|
||
self.main_window.parser.modify_prism_constant(target_id, new_value_m)
|
||
self.main_window.statusBar().showMessage(
|
||
f"Prismenkonstante für {target_id} auf {new_value_mm:.1f} mm gesetzt")
|
||
|
||
def on_control_point_toggled(self, station_id, point_name, state):
|
||
"""Wird aufgerufen, wenn ein Passpunkt aktiviert/deaktiviert wird"""
|
||
is_active = state == Qt.Checked
|
||
# Hier könnte man die Stationierung neu berechnen
|
||
self.main_window.statusBar().showMessage(
|
||
f"Passpunkt {point_name} {'aktiviert' if is_active else 'deaktiviert'}")
|
||
|
||
def update_points_table(self):
|
||
"""Aktualisiert die Punkte-Tabelle"""
|
||
if not self.main_window.parser:
|
||
return
|
||
|
||
points = self.main_window.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))
|
||
|
||
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()
|
||
|
||
|
||
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 - Überarbeitet"""
|
||
|
||
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 (überarbeitet)
|
||
self.twopoint_group = QGroupBox("2-Punkte-Definition")
|
||
twopoint_layout = QGridLayout(self.twopoint_group)
|
||
|
||
twopoint_layout.addWidget(QLabel("XY-Nullpunkt (0,0):"), 0, 0)
|
||
self.xy_origin_combo = QComboBox()
|
||
twopoint_layout.addWidget(self.xy_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-Nullpunkt (0):"), 2, 0)
|
||
self.z_origin_combo = QComboBox()
|
||
twopoint_layout.addWidget(self.z_origin_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 berechnen")
|
||
transform_btn.clicked.connect(self.execute_transformation)
|
||
transform_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
|
||
layout.addWidget(transform_btn)
|
||
|
||
# Anwenden Button (NEU - Bug Fix)
|
||
apply_btn = QPushButton("Transformation anwenden (Punktliste aktualisieren)")
|
||
apply_btn.clicked.connect(self.apply_transformation)
|
||
apply_btn.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold;")
|
||
layout.addWidget(apply_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.xy_origin_combo.clear()
|
||
self.direction_combo.clear()
|
||
self.z_origin_combo.clear()
|
||
|
||
# Standard-Vorschläge: 7001 für XY, 7002 für Z
|
||
default_xy = None
|
||
default_direction = None
|
||
default_z = None
|
||
|
||
for name in sorted(points):
|
||
self.xy_origin_combo.addItem(name)
|
||
self.direction_combo.addItem(name)
|
||
self.z_origin_combo.addItem(name)
|
||
|
||
if name == "7001":
|
||
default_xy = name
|
||
if name == "7002":
|
||
default_z = name
|
||
|
||
# Standard-Werte setzen falls vorhanden
|
||
if default_xy:
|
||
idx = self.xy_origin_combo.findText(default_xy)
|
||
if idx >= 0:
|
||
self.xy_origin_combo.setCurrentIndex(idx)
|
||
|
||
if default_z:
|
||
idx = self.z_origin_combo.findText(default_z)
|
||
if idx >= 0:
|
||
self.z_origin_combo.setCurrentIndex(idx)
|
||
|
||
# Info anzeigen
|
||
if default_xy or default_z:
|
||
self.main_window.statusBar().showMessage(
|
||
f"Standard-Vorschlag: XY={default_xy or 'nicht gefunden'}, Z={default_z or 'nicht gefunden'}")
|
||
|
||
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.xy_origin_combo.currentText()
|
||
direction = self.direction_combo.currentText()
|
||
zref = self.z_origin_combo.currentText()
|
||
|
||
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 berechnet (noch nicht angewendet)")
|
||
|
||
def apply_transformation(self):
|
||
"""Wendet die Transformation auf die Punktliste an (Bug Fix)"""
|
||
if not self.transformer.transformed_points:
|
||
QMessageBox.warning(self, "Fehler",
|
||
"Bitte zuerst 'Transformation berechnen' ausführen!")
|
||
return
|
||
|
||
if not self.main_window.parser:
|
||
return
|
||
|
||
reply = QMessageBox.question(
|
||
self, "Bestätigung",
|
||
"Sollen die transformierten Koordinaten auf alle Punkte angewendet werden?\n\n"
|
||
"Dies ändert die Koordinaten im Speicher.",
|
||
QMessageBox.Yes | QMessageBox.No)
|
||
|
||
if reply == QMessageBox.No:
|
||
return
|
||
|
||
# Transformierte Koordinaten in Parser übernehmen
|
||
for trans_point in self.transformer.transformed_points:
|
||
if trans_point.name in self.main_window.parser.points:
|
||
p = self.main_window.parser.points[trans_point.name]
|
||
p.east = trans_point.x
|
||
p.north = trans_point.y
|
||
p.elevation = trans_point.z
|
||
|
||
# GUI aktualisieren
|
||
# JXL-Tab aktualisieren
|
||
jxl_tab = self.main_window.tabs.widget(0)
|
||
if hasattr(jxl_tab, 'update_display'):
|
||
jxl_tab.update_display()
|
||
|
||
# Erfolgsmeldung
|
||
QMessageBox.information(self, "Erfolg",
|
||
f"{len(self.transformer.transformed_points)} Punkte wurden transformiert!\n\n"
|
||
"Die Punktliste wurde aktualisiert.")
|
||
|
||
self.main_window.statusBar().showMessage("Transformation angewendet - Punktliste aktualisiert")
|
||
|
||
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)
|
||
calc_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
|
||
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 - Überarbeitet mit automatischer Punkterkennung"""
|
||
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent)
|
||
self.main_window = parent
|
||
self.adjustment = None
|
||
self.fixed_points = set()
|
||
self.measurement_points = set()
|
||
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)
|
||
|
||
# Automatisch erkannte Punkte
|
||
points_group = QGroupBox("Automatisch erkannte Punkttypen")
|
||
points_layout = QVBoxLayout(points_group)
|
||
|
||
# Info-Label
|
||
info_label = QLabel(
|
||
"💡 Das Programm erkennt automatisch:\n"
|
||
" • Festpunkte: Alle Punkte, die in Stationierungen verwendet werden (1000er, 2000er Serie)\n"
|
||
" • Messpunkte: 3000er Punkte (Detailmessungen)")
|
||
info_label.setStyleSheet("color: #666; background-color: #f0f0f0; padding: 10px;")
|
||
points_layout.addWidget(info_label)
|
||
|
||
# Tabelle für erkannte Punkte
|
||
self.points_table = QTableWidget()
|
||
self.points_table.setColumnCount(4)
|
||
self.points_table.setHorizontalHeaderLabels(["Punkt", "Typ", "X", "Y"])
|
||
self.points_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||
self.points_table.setMaximumHeight(200)
|
||
points_layout.addWidget(self.points_table)
|
||
|
||
# Button zum Aktualisieren
|
||
refresh_btn = QPushButton("Punkte automatisch erkennen")
|
||
refresh_btn.clicked.connect(self.auto_detect_points)
|
||
refresh_btn.setStyleSheet("background-color: #FF9800; color: white;")
|
||
points_layout.addWidget(refresh_btn)
|
||
|
||
layout.addWidget(points_group)
|
||
|
||
# Ausgleichung durchführen
|
||
adjust_btn = QPushButton("Netzausgleichung durchführen")
|
||
adjust_btn.clicked.connect(self.run_adjustment)
|
||
adjust_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
|
||
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 auto_detect_points(self):
|
||
"""Erkennt automatisch Festpunkte und Messpunkte"""
|
||
if not self.main_window.parser:
|
||
QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei laden!")
|
||
return
|
||
|
||
parser = self.main_window.parser
|
||
self.fixed_points.clear()
|
||
self.measurement_points.clear()
|
||
|
||
# Festpunkte: Punkte die in Stationierungen verwendet werden
|
||
# Das sind typischerweise 1000er (Stationen) und 2000er (Anschlusspunkte)
|
||
for station_id, station in parser.stations.items():
|
||
# Station selbst ist Festpunkt
|
||
if station.name:
|
||
self.fixed_points.add(station.name)
|
||
|
||
# Anschlusspunkte aus Backbearings
|
||
for bb_id, bb in parser.backbearings.items():
|
||
if bb.station_record_id == station_id and bb.backsight:
|
||
self.fixed_points.add(bb.backsight)
|
||
|
||
# Anschlusspunkte aus Messungen (BackSight Classification)
|
||
measurements = parser.get_measurements_from_station(station_id)
|
||
for meas in measurements:
|
||
if meas.classification == "BackSight" and meas.name:
|
||
self.fixed_points.add(meas.name)
|
||
|
||
# Messpunkte: 3000er Serie
|
||
for name in parser.get_active_points().keys():
|
||
if name.startswith("3"):
|
||
self.measurement_points.add(name)
|
||
|
||
# Tabelle aktualisieren
|
||
self.update_points_table()
|
||
|
||
self.main_window.statusBar().showMessage(
|
||
f"Erkannt: {len(self.fixed_points)} Festpunkte, {len(self.measurement_points)} Messpunkte")
|
||
|
||
def update_points_table(self):
|
||
"""Aktualisiert die Tabelle mit erkannten Punkten"""
|
||
parser = self.main_window.parser
|
||
if not parser:
|
||
return
|
||
|
||
all_points = list(self.fixed_points) + list(self.measurement_points)
|
||
self.points_table.setRowCount(len(all_points))
|
||
|
||
for row, name in enumerate(sorted(all_points)):
|
||
# Punkt-Name
|
||
self.points_table.setItem(row, 0, QTableWidgetItem(name))
|
||
|
||
# Typ
|
||
if name in self.fixed_points:
|
||
type_item = QTableWidgetItem("Festpunkt")
|
||
type_item.setBackground(QBrush(QColor(200, 230, 200))) # Hellgrün
|
||
else:
|
||
type_item = QTableWidgetItem("Messpunkt")
|
||
type_item.setBackground(QBrush(QColor(200, 200, 230))) # Hellblau
|
||
self.points_table.setItem(row, 1, type_item)
|
||
|
||
# Koordinaten
|
||
if name in parser.points:
|
||
p = parser.points[name]
|
||
self.points_table.setItem(row, 2, QTableWidgetItem(f"{p.east:.4f}" if p.east else ""))
|
||
self.points_table.setItem(row, 3, QTableWidgetItem(f"{p.north:.4f}" if p.north else ""))
|
||
|
||
def run_adjustment(self):
|
||
if not self.main_window.parser:
|
||
QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei laden!")
|
||
return
|
||
|
||
# Automatische Erkennung falls noch nicht geschehen
|
||
if not self.fixed_points and not self.measurement_points:
|
||
self.auto_detect_points()
|
||
|
||
if not self.fixed_points:
|
||
QMessageBox.warning(self, "Fehler", "Keine Festpunkte erkannt!")
|
||
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 (automatisch erkannte)
|
||
for point_name in self.fixed_points:
|
||
self.adjustment.set_fixed_point(point_name)
|
||
|
||
try:
|
||
result = self.adjustment.adjust()
|
||
|
||
# Bericht erstellen mit Festpunkt/Messpunkt-Unterscheidung
|
||
report = self.create_detailed_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 create_detailed_report(self):
|
||
"""Erstellt einen detaillierten Bericht mit Festpunkt/Messpunkt-Unterscheidung"""
|
||
if not self.adjustment or not self.adjustment.result:
|
||
return "Keine Ergebnisse vorhanden."
|
||
|
||
lines = []
|
||
lines.append("=" * 80)
|
||
lines.append("NETZAUSGLEICHUNG - ERGEBNISBERICHT")
|
||
lines.append("=" * 80)
|
||
lines.append("")
|
||
|
||
# Allgemeine Informationen
|
||
lines.append("ALLGEMEINE INFORMATIONEN")
|
||
lines.append("-" * 80)
|
||
lines.append(f"Job: {self.main_window.parser.job_name}")
|
||
lines.append(f"Anzahl Festpunkte: {len(self.fixed_points)}")
|
||
lines.append(f"Anzahl Messpunkte: {len(self.measurement_points)}")
|
||
lines.append(f"Anzahl Beobachtungen: {self.adjustment.result.num_observations}")
|
||
lines.append(f"Iterationen: {self.adjustment.result.iterations}")
|
||
lines.append(f"Konvergiert: {'Ja' if self.adjustment.result.converged else 'Nein'}")
|
||
lines.append("")
|
||
|
||
# Qualitätsparameter
|
||
lines.append("GLOBALE QUALITÄTSPARAMETER")
|
||
lines.append("-" * 80)
|
||
lines.append(f"Sigma-0 a-posteriori: {self.adjustment.result.sigma_0_posteriori:.4f}")
|
||
lines.append(f"RMSE Richtungen: {self.adjustment.result.rmse_directions:.2f} mgon")
|
||
lines.append(f"RMSE Strecken: {self.adjustment.result.rmse_distances:.2f} mm")
|
||
lines.append("")
|
||
|
||
# Festpunkte
|
||
lines.append("FESTPUNKTE (Stationspunkte und Anschlusspunkte)")
|
||
lines.append("-" * 80)
|
||
lines.append(f"{'Punkt':<12} {'X [m]':>14} {'Y [m]':>14} {'Z [m]':>12} {'σX [mm]':>10} {'σY [mm]':>10}")
|
||
lines.append("-" * 80)
|
||
|
||
for name in sorted(self.fixed_points):
|
||
if name in self.adjustment.points:
|
||
p = self.adjustment.points[name]
|
||
std_x = p.std_x * 1000 if p.std_x else 0
|
||
std_y = p.std_y * 1000 if p.std_y else 0
|
||
lines.append(f"{name:<12} {p.x:>14.4f} {p.y:>14.4f} {p.z:>12.4f} {std_x:>10.2f} {std_y:>10.2f}")
|
||
lines.append("")
|
||
|
||
# Messpunkte
|
||
lines.append("MESSPUNKTE (3000er Serie)")
|
||
lines.append("-" * 80)
|
||
lines.append(f"{'Punkt':<12} {'X [m]':>14} {'Y [m]':>14} {'Z [m]':>12} {'σX [mm]':>10} {'σY [mm]':>10}")
|
||
lines.append("-" * 80)
|
||
|
||
for name in sorted(self.measurement_points):
|
||
if name in self.adjustment.points:
|
||
p = self.adjustment.points[name]
|
||
std_x = p.std_x * 1000 if p.std_x else 0
|
||
std_y = p.std_y * 1000 if p.std_y else 0
|
||
lines.append(f"{name:<12} {p.x:>14.4f} {p.y:>14.4f} {p.z:>12.4f} {std_x:>10.2f} {std_y:>10.2f}")
|
||
|
||
lines.append("")
|
||
lines.append("=" * 80)
|
||
|
||
return "\n".join(lines)
|
||
|
||
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:
|
||
with open(file_path, 'w', encoding='utf-8') as f:
|
||
f.write(self.create_detailed_report())
|
||
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:
|
||
lines = ["Punkt;Typ;X;Y;Z;Sigma_X;Sigma_Y"]
|
||
|
||
for name, p in sorted(self.adjustment.points.items()):
|
||
if name in self.fixed_points:
|
||
typ = "Festpunkt"
|
||
elif name in self.measurement_points:
|
||
typ = "Messpunkt"
|
||
else:
|
||
typ = "Sonstig"
|
||
|
||
std_x = p.std_x * 1000 if p.std_x else 0
|
||
std_y = p.std_y * 1000 if p.std_y else 0
|
||
lines.append(f"{name};{typ};{p.x:.4f};{p.y:.4f};{p.z:.4f};{std_x:.2f};{std_y:.2f}")
|
||
|
||
with open(file_path, 'w', encoding='utf-8') as f:
|
||
f.write("\n".join(lines))
|
||
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())
|
||
|
||
# JXL-Tab aktualisieren
|
||
jxl_tab = self.main_window.tabs.widget(0)
|
||
if hasattr(jxl_tab, 'update_display'):
|
||
jxl_tab.update_display()
|
||
|
||
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 = None
|
||
self.setWindowTitle("Trimble Geodesy Tool v2.0")
|
||
self.setMinimumSize(1000, 700)
|
||
self.setup_ui()
|
||
self.setup_menu()
|
||
|
||
def setup_ui(self):
|
||
# Zentrales Widget
|
||
central_widget = QWidget()
|
||
self.setCentralWidget(central_widget)
|
||
main_layout = QVBoxLayout(central_widget)
|
||
|
||
# Tab-Widget
|
||
self.tabs = QTabWidget()
|
||
|
||
self.tabs.addTab(JXLAnalysisTab(self), "📁 JXL-Analyse")
|
||
self.tabs.addTab(CORGeneratorTab(self), "📄 COR-Generator")
|
||
self.tabs.addTab(TransformationTab(self), "🔄 Transformation")
|
||
self.tabs.addTab(GeoreferencingTab(self), "🌍 Georeferenzierung")
|
||
self.tabs.addTab(NetworkAdjustmentTab(self), "📐 Netzausgleichung")
|
||
self.tabs.addTab(ReferencePointAdjusterTab(self), "📍 Referenzpunkt-Anpassung")
|
||
|
||
main_layout.addWidget(self.tabs)
|
||
|
||
# Statusleiste
|
||
self.statusBar().showMessage("Bereit - Bitte JXL-Datei laden")
|
||
|
||
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:
|
||
jxl_tab = self.tabs.widget(0)
|
||
jxl_tab.file_path_edit.setText(file_path)
|
||
jxl_tab.load_file()
|
||
|
||
def show_about(self):
|
||
QMessageBox.about(self, "Über Trimble Geodesy Tool",
|
||
"<h2>Trimble Geodesy Tool v2.0</h2>"
|
||
"<p>Geodätische Vermessungsarbeiten mit JXL-Dateien</p>"
|
||
"<p><b>Funktionen:</b></p>"
|
||
"<ul>"
|
||
"<li>JXL-Datei Analyse mit TreeView für Stationierungen</li>"
|
||
"<li>Prismenkonstanten-Verwaltung bei einzelnen Messungen</li>"
|
||
"<li>Passpunkt-Qualitätsbewertung bei freien Stationierungen</li>"
|
||
"<li>COR-Datei Generierung und Export</li>"
|
||
"<li>Koordinatentransformation mit 2-Punkte-Definition</li>"
|
||
"<li>Georeferenzierung mit Passpunkten</li>"
|
||
"<li>Netzausgleichung mit automatischer Punkterkennung</li>"
|
||
"<li>Referenzpunkt-Anpassung</li>"
|
||
"</ul>"
|
||
"<p>Version 2.0 - Überarbeitet Januar 2026</p>")
|
||
|
||
|
||
def main():
|
||
app = QApplication(sys.argv)
|
||
|
||
# Modernes Styling
|
||
app.setStyle('Fusion')
|
||
|
||
window = MainWindow()
|
||
window.show()
|
||
|
||
sys.exit(app.exec_())
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|