1768 lines
70 KiB
Python
1768 lines
70 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Trimble Geodesy Tool - Hauptprogramm mit GUI
|
|
Geodätische Vermessungsarbeiten mit JXL-Dateien
|
|
Überarbeitet: Export-Dialog, Georeferenzierung, TreeView
|
|
"""
|
|
|
|
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,
|
|
QRadioButton, QButtonGroup
|
|
)
|
|
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
|
|
|
|
|
|
# =============================================================================
|
|
# Export-Dialog (wiederverwendbar für alle Module)
|
|
# =============================================================================
|
|
class ExportFormatDialog(QDialog):
|
|
"""Dialog zur Auswahl des Export-Formats (COR, CSV, TXT, DXF)"""
|
|
|
|
def __init__(self, parent=None, title="Export-Format wählen"):
|
|
super().__init__(parent)
|
|
self.setWindowTitle(title)
|
|
self.setMinimumWidth(300)
|
|
self.selected_format = "cor" # Standard
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Format-Auswahl
|
|
format_group = QGroupBox("Export-Format auswählen")
|
|
format_layout = QVBoxLayout(format_group)
|
|
|
|
self.format_group = QButtonGroup(self)
|
|
|
|
self.cor_radio = QRadioButton("COR - Koordinatendatei (Standard)")
|
|
self.cor_radio.setChecked(True)
|
|
self.format_group.addButton(self.cor_radio, 0)
|
|
format_layout.addWidget(self.cor_radio)
|
|
|
|
self.csv_radio = QRadioButton("CSV - Komma-getrennte Werte")
|
|
self.format_group.addButton(self.csv_radio, 1)
|
|
format_layout.addWidget(self.csv_radio)
|
|
|
|
self.txt_radio = QRadioButton("TXT - Tabulatorgetrennt")
|
|
self.format_group.addButton(self.txt_radio, 2)
|
|
format_layout.addWidget(self.txt_radio)
|
|
|
|
self.dxf_radio = QRadioButton("DXF - AutoCAD Format")
|
|
self.format_group.addButton(self.dxf_radio, 3)
|
|
format_layout.addWidget(self.dxf_radio)
|
|
|
|
layout.addWidget(format_group)
|
|
|
|
# Info-Text
|
|
info_label = QLabel(
|
|
"💡 COR und CSV: PunktID,X,Y,Z (keine Header-Zeile)\n"
|
|
" TXT: Tabulatorgetrennt\n"
|
|
" DXF: AutoCAD-kompatibel"
|
|
)
|
|
info_label.setStyleSheet("color: #666; font-style: italic; padding: 5px;")
|
|
layout.addWidget(info_label)
|
|
|
|
# Buttons
|
|
button_box = QDialogButtonBox(
|
|
QDialogButtonBox.Ok | QDialogButtonBox.Cancel
|
|
)
|
|
button_box.accepted.connect(self.accept)
|
|
button_box.rejected.connect(self.reject)
|
|
layout.addWidget(button_box)
|
|
|
|
def get_selected_format(self):
|
|
"""Gibt das ausgewählte Format zurück"""
|
|
if self.cor_radio.isChecked():
|
|
return "cor"
|
|
elif self.csv_radio.isChecked():
|
|
return "csv"
|
|
elif self.txt_radio.isChecked():
|
|
return "txt"
|
|
elif self.dxf_radio.isChecked():
|
|
return "dxf"
|
|
return "cor"
|
|
|
|
def get_file_filter(self):
|
|
"""Gibt den Dateifilter für den Speicherdialog zurück"""
|
|
format_type = self.get_selected_format()
|
|
filters = {
|
|
'cor': "COR Files (*.cor)",
|
|
'csv': "CSV Files (*.csv)",
|
|
'txt': "Text Files (*.txt)",
|
|
'dxf': "DXF Files (*.dxf)"
|
|
}
|
|
return filters.get(format_type, "All Files (*)")
|
|
|
|
|
|
def show_export_dialog_and_save(parent, cor_generator, default_name="export"):
|
|
"""
|
|
Zeigt den Export-Dialog und speichert die Datei.
|
|
Wiederverwendbare Funktion für alle Module.
|
|
"""
|
|
# Format-Dialog anzeigen
|
|
dialog = ExportFormatDialog(parent)
|
|
if dialog.exec_() != QDialog.Accepted:
|
|
return None
|
|
|
|
format_type = dialog.get_selected_format()
|
|
file_filter = dialog.get_file_filter()
|
|
|
|
# Dateiendung hinzufügen
|
|
extensions = {'cor': '.cor', 'csv': '.csv', 'txt': '.txt', 'dxf': '.dxf'}
|
|
suggested_name = f"{default_name}{extensions.get(format_type, '.cor')}"
|
|
|
|
# Datei-Dialog
|
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
parent, "Speichern unter", suggested_name, file_filter
|
|
)
|
|
|
|
if not file_path:
|
|
return None
|
|
|
|
# Exportieren
|
|
try:
|
|
if format_type == 'cor':
|
|
cor_generator.write_cor_file(file_path)
|
|
elif format_type == 'csv':
|
|
cor_generator.export_csv(file_path)
|
|
elif format_type == 'txt':
|
|
cor_generator.export_txt(file_path)
|
|
elif format_type == 'dxf':
|
|
cor_generator.export_dxf(file_path)
|
|
|
|
QMessageBox.information(parent, "Erfolg", f"Datei gespeichert: {file_path}")
|
|
return file_path
|
|
except Exception as e:
|
|
QMessageBox.critical(parent, "Fehler", f"Fehler beim Speichern: {e}")
|
|
return None
|
|
|
|
|
|
def export_points_with_dialog(parent, points, default_name="punkte"):
|
|
"""
|
|
Exportiert eine Liste von CORPoint-Objekten mit Export-Dialog.
|
|
Universelle Funktion für alle Module.
|
|
"""
|
|
if not points:
|
|
QMessageBox.warning(parent, "Fehler", "Keine Punkte zum Exportieren!")
|
|
return None
|
|
|
|
# Temporären COR-Generator erstellen
|
|
class TempGenerator:
|
|
def __init__(self, pts):
|
|
self.cor_points = pts
|
|
|
|
def write_cor_file(self, path):
|
|
lines = [f"{p.name},{p.x:.4f},{p.y:.4f},{p.z:.4f}" for p in self.cor_points]
|
|
with open(path, 'w', encoding='utf-8') as f:
|
|
f.write("\n".join(lines))
|
|
|
|
def export_csv(self, path):
|
|
self.write_cor_file(path) # Gleiches Format
|
|
|
|
def export_txt(self, path):
|
|
lines = [f"{p.name}\t{p.x:.4f}\t{p.y:.4f}\t{p.z:.4f}" for p in self.cor_points]
|
|
with open(path, 'w', encoding='utf-8') as f:
|
|
f.write("\n".join(lines))
|
|
|
|
def export_dxf(self, path):
|
|
lines = ["0", "SECTION", "2", "ENTITIES"]
|
|
for p in self.cor_points:
|
|
lines.extend([
|
|
"0", "POINT", "8", "POINTS",
|
|
"10", f"{p.x:.4f}", "20", f"{p.y:.4f}", "30", f"{p.z:.4f}",
|
|
"0", "TEXT", "8", "NAMES",
|
|
"10", f"{p.x + 0.5:.4f}", "20", f"{p.y + 0.5:.4f}", "30", f"{p.z:.4f}",
|
|
"40", "0.5", "1", p.name
|
|
])
|
|
lines.extend(["0", "ENDSEC", "0", "EOF"])
|
|
with open(path, 'w', encoding='utf-8') as f:
|
|
f.write("\n".join(lines))
|
|
|
|
temp_gen = TempGenerator(points)
|
|
return show_export_dialog_and_save(parent, temp_gen, default_name)
|
|
|
|
|
|
# =============================================================================
|
|
# JXL-Analyse Tab
|
|
# =============================================================================
|
|
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 = {}
|
|
self.control_point_checkboxes = {}
|
|
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(120)
|
|
summary_layout.addWidget(self.summary_text)
|
|
splitter.addWidget(summary_group)
|
|
|
|
# Stationierungen TreeView
|
|
stations_group = QGroupBox("Stationierungen und Messungen (TreeView)")
|
|
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.setMinimumHeight(200)
|
|
|
|
# Spaltenbreiten
|
|
self.stations_tree.setColumnWidth(0, 250)
|
|
self.stations_tree.setColumnWidth(1, 120)
|
|
self.stations_tree.setColumnWidth(2, 140)
|
|
self.stations_tree.setColumnWidth(3, 100)
|
|
self.stations_tree.setColumnWidth(4, 60)
|
|
|
|
stations_layout.addWidget(self.stations_tree)
|
|
|
|
# Info-Label
|
|
info_label = QLabel(
|
|
"💡 Prismenkonstanten können direkt geändert werden. "
|
|
"Bei freien Stationierungen: Passpunkte mit Checkbox aktivieren/deaktivieren."
|
|
)
|
|
info_label.setStyleSheet("color: #666; font-style: italic;")
|
|
info_label.setWordWrap(True)
|
|
stations_layout.addWidget(info_label)
|
|
|
|
splitter.addWidget(stations_group)
|
|
|
|
# Punkte-Tabelle
|
|
points_group = QGroupBox("Alle Punkte (Übersicht)")
|
|
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)
|
|
|
|
# Splitter-Größen
|
|
splitter.setSizes([120, 300, 250])
|
|
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())
|
|
|
|
# TreeView aktualisieren
|
|
self.update_stations_tree()
|
|
|
|
# Punkte-Tabelle
|
|
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
|
|
|
|
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
|
|
if station.station_type == "ReflineStationSetup":
|
|
station_item.setBackground(0, QBrush(QColor(200, 230, 200)))
|
|
elif station.station_type == "StandardResection":
|
|
station_item.setBackground(0, QBrush(QColor(200, 200, 230)))
|
|
|
|
self.stations_tree.addTopLevelItem(station_item)
|
|
|
|
# Messungen
|
|
measurements = parser.get_measurements_from_station(station_id)
|
|
|
|
# Qualitätsberechnung für freie Stationierung
|
|
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
|
|
if meas.classification == "BackSight":
|
|
meas_type = "Passpunkt"
|
|
else:
|
|
meas_type = "Messung"
|
|
meas_item.setText(1, meas_type)
|
|
|
|
# Prismenkonstante 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)
|
|
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: Qualität und Checkbox
|
|
if station.station_type == "StandardResection" and meas.classification == "BackSight":
|
|
# Qualitätswert
|
|
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']
|
|
|
|
quality_label = QLabel(f"{quality_value:.1f} mm")
|
|
if rank == 1:
|
|
quality_label.setStyleSheet("background-color: #90EE90; padding: 2px;")
|
|
elif rank == total:
|
|
quality_label.setStyleSheet("background-color: #FFB6C1; padding: 2px;")
|
|
else:
|
|
quality_label.setStyleSheet("background-color: #FFFF99; padding: 2px;")
|
|
|
|
self.stations_tree.setItemWidget(meas_item, 3, quality_label)
|
|
|
|
# Checkbox
|
|
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 Qualität der Passpunkte"""
|
|
parser = self.main_window.parser
|
|
control_points = []
|
|
|
|
for meas in measurements:
|
|
if meas.classification == "BackSight" and not meas.deleted:
|
|
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:
|
|
dx = target_point.east - station.east
|
|
dy = target_point.north - station.north
|
|
calc_dist = (dx**2 + dy**2)**0.5
|
|
residual = abs(meas.edm_distance - calc_dist) * 1000
|
|
control_points.append((meas.name, residual))
|
|
|
|
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):
|
|
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):
|
|
is_active = state == Qt.Checked
|
|
self.main_window.statusBar().showMessage(
|
|
f"Passpunkt {point_name} {'aktiviert' if is_active else 'deaktiviert'}")
|
|
|
|
def update_points_table(self):
|
|
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()
|
|
|
|
|
|
# =============================================================================
|
|
# COR-Generator Tab
|
|
# =============================================================================
|
|
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)
|
|
|
|
layout.addWidget(options_group)
|
|
|
|
# Generieren Button
|
|
generate_btn = QPushButton("Punkte generieren")
|
|
generate_btn.clicked.connect(self.generate_cor)
|
|
generate_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
|
|
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 Button (mit Dialog)
|
|
export_btn = QPushButton("📥 Punkte exportieren...")
|
|
export_btn.clicked.connect(self.export_with_dialog)
|
|
export_btn.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold; font-size: 14px; padding: 10px;")
|
|
layout.addWidget(export_btn)
|
|
|
|
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}"))
|
|
|
|
self.stats_text.setText(self.cor_generator.get_statistics())
|
|
self.main_window.statusBar().showMessage(f"{len(points)} Punkte generiert")
|
|
|
|
def export_with_dialog(self):
|
|
if not self.cor_generator or not self.cor_generator.cor_points:
|
|
QMessageBox.warning(self, "Fehler", "Bitte zuerst Punkte generieren!")
|
|
return
|
|
|
|
show_export_dialog_and_save(self, self.cor_generator, "koordinaten")
|
|
|
|
|
|
# =============================================================================
|
|
# Transformation Tab (Y-Richtung entfernt)
|
|
# =============================================================================
|
|
class TransformationTab(QWidget):
|
|
"""Tab für Koordinatentransformation - Y-Richtung entfernt"""
|
|
|
|
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 (OHNE Y-Richtung!)
|
|
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("Z-Nullpunkt (0):"), 1, 0)
|
|
self.z_origin_combo = QComboBox()
|
|
twopoint_layout.addWidget(self.z_origin_combo, 1, 1)
|
|
|
|
refresh_btn = QPushButton("Punktliste aktualisieren")
|
|
refresh_btn.clicked.connect(self.refresh_point_lists)
|
|
twopoint_layout.addWidget(refresh_btn, 2, 0, 1, 2)
|
|
|
|
self.twopoint_group.setVisible(False)
|
|
layout.addWidget(self.twopoint_group)
|
|
|
|
# Transformation berechnen
|
|
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
|
|
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.z_origin_combo.clear()
|
|
|
|
default_xy = None
|
|
default_z = None
|
|
|
|
for name in sorted(points):
|
|
self.xy_origin_combo.addItem(name)
|
|
self.z_origin_combo.addItem(name)
|
|
|
|
if name == "7001":
|
|
default_xy = name
|
|
if name == "7002":
|
|
default_z = name
|
|
|
|
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)
|
|
|
|
def execute_transformation(self):
|
|
if not self.main_window.parser:
|
|
QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei laden!")
|
|
return
|
|
|
|
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()
|
|
zref = self.z_origin_combo.currentText()
|
|
|
|
# Transformation zum Nullpunkt (nur Translation, keine Rotation)
|
|
if not self.transformer.compute_translation_only(origin, zref):
|
|
QMessageBox.warning(self, "Fehler", "Punkt nicht gefunden!")
|
|
return
|
|
|
|
self.transformer.transform()
|
|
|
|
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")
|
|
|
|
def apply_transformation(self):
|
|
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",
|
|
"Transformierte Koordinaten auf alle Punkte anwenden?",
|
|
QMessageBox.Yes | QMessageBox.No)
|
|
|
|
if reply == QMessageBox.No:
|
|
return
|
|
|
|
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
|
|
|
|
jxl_tab = self.main_window.tabs.widget(0)
|
|
if hasattr(jxl_tab, 'update_display'):
|
|
jxl_tab.update_display()
|
|
|
|
QMessageBox.information(self, "Erfolg",
|
|
f"{len(self.transformer.transformed_points)} Punkte transformiert!")
|
|
|
|
self.main_window.statusBar().showMessage("Transformation angewendet")
|
|
|
|
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!")
|
|
return
|
|
|
|
export_points_with_dialog(self, self.transformer.transformed_points, "transformiert")
|
|
|
|
|
|
# =============================================================================
|
|
# Georeferenzierung Tab (KOMPLETT NEU)
|
|
# =============================================================================
|
|
class GeoreferencingTab(QWidget):
|
|
"""Tab für Georeferenzierung - NEUER WORKFLOW mit Punktdatei-Laden"""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.main_window = parent
|
|
self.georeferencer = Georeferencer()
|
|
self.loaded_target_points = {} # {name: (x, y, z)}
|
|
self.point_assignments = {} # {row: (target_name, jxl_name)}
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Schritt 1: Punktdatei laden
|
|
load_group = QGroupBox("Schritt 1: Soll-Koordinaten laden")
|
|
load_layout = QVBoxLayout(load_group)
|
|
|
|
load_btn_layout = QHBoxLayout()
|
|
self.load_file_btn = QPushButton("📂 Punktdatei laden (COR/CSV)")
|
|
self.load_file_btn.clicked.connect(self.load_target_file)
|
|
self.load_file_btn.setStyleSheet("background-color: #FF9800; color: white; font-weight: bold;")
|
|
load_btn_layout.addWidget(self.load_file_btn)
|
|
|
|
self.loaded_file_label = QLabel("Keine Datei geladen")
|
|
self.loaded_file_label.setStyleSheet("color: #666;")
|
|
load_btn_layout.addWidget(self.loaded_file_label)
|
|
load_btn_layout.addStretch()
|
|
|
|
load_layout.addLayout(load_btn_layout)
|
|
layout.addWidget(load_group)
|
|
|
|
# Schritt 2: Punkt-Zuordnungstabelle
|
|
assign_group = QGroupBox("Schritt 2: Punkt-Zuordnung (Soll → Ist aus JXL)")
|
|
assign_layout = QVBoxLayout(assign_group)
|
|
|
|
info_label = QLabel(
|
|
"💡 Wählen Sie für jeden geladenen Punkt den entsprechenden Punkt aus der JXL-Datei."
|
|
)
|
|
info_label.setStyleSheet("color: #666; font-style: italic;")
|
|
assign_layout.addWidget(info_label)
|
|
|
|
self.assign_table = QTableWidget()
|
|
self.assign_table.setColumnCount(8)
|
|
self.assign_table.setHorizontalHeaderLabels([
|
|
"Soll-Punkt", "X_Soll", "Y_Soll", "Z_Soll",
|
|
"JXL-Punkt ⬇", "X_Ist", "Y_Ist", "Z_Ist"
|
|
])
|
|
self.assign_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
assign_layout.addWidget(self.assign_table)
|
|
|
|
layout.addWidget(assign_group)
|
|
|
|
# Schritt 3: Berechnung
|
|
calc_group = QGroupBox("Schritt 3: Georeferenzierung durchführen")
|
|
calc_layout = QHBoxLayout(calc_group)
|
|
|
|
calc_btn = QPushButton("🔄 Transformation berechnen")
|
|
calc_btn.clicked.connect(self.calculate_transformation)
|
|
calc_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; font-size: 14px; padding: 10px;")
|
|
calc_layout.addWidget(calc_btn)
|
|
|
|
apply_btn = QPushButton("✓ Auf alle Punkte anwenden")
|
|
apply_btn.clicked.connect(self.apply_to_all_points)
|
|
apply_btn.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold;")
|
|
calc_layout.addWidget(apply_btn)
|
|
|
|
layout.addWidget(calc_group)
|
|
|
|
# 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("📥 Punkte exportieren...")
|
|
export_points_btn.clicked.connect(self.export_transformed_points)
|
|
export_layout.addWidget(export_points_btn)
|
|
|
|
results_layout.addLayout(export_layout)
|
|
|
|
layout.addWidget(results_group)
|
|
|
|
def load_target_file(self):
|
|
"""Lädt eine COR/CSV-Datei mit Soll-Koordinaten"""
|
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
self, "Soll-Koordinaten laden", "",
|
|
"Koordinatendateien (*.cor *.csv *.txt);;All Files (*)")
|
|
|
|
if not file_path:
|
|
return
|
|
|
|
try:
|
|
self.loaded_target_points.clear()
|
|
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
lines = f.readlines()
|
|
|
|
for line in lines:
|
|
line = line.strip()
|
|
if not line or line.startswith('#'):
|
|
continue
|
|
|
|
# Komma oder Semikolon als Trenner
|
|
parts = line.replace(';', ',').split(',')
|
|
if len(parts) >= 4:
|
|
name = parts[0].strip()
|
|
try:
|
|
x = float(parts[1].strip())
|
|
y = float(parts[2].strip())
|
|
z = float(parts[3].strip())
|
|
self.loaded_target_points[name] = (x, y, z)
|
|
except ValueError:
|
|
continue # Header oder ungültige Zeile überspringen
|
|
|
|
self.loaded_file_label.setText(f"✓ {os.path.basename(file_path)} ({len(self.loaded_target_points)} Punkte)")
|
|
self.loaded_file_label.setStyleSheet("color: green; font-weight: bold;")
|
|
|
|
# Tabelle aktualisieren
|
|
self.update_assignment_table()
|
|
|
|
QMessageBox.information(self, "Erfolg",
|
|
f"{len(self.loaded_target_points)} Punkte geladen!")
|
|
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Fehler", f"Fehler beim Laden: {e}")
|
|
|
|
def update_assignment_table(self):
|
|
"""Aktualisiert die Zuordnungstabelle"""
|
|
self.assign_table.setRowCount(len(self.loaded_target_points))
|
|
|
|
# JXL-Punkte für Dropdown
|
|
jxl_points = ["-- Nicht zugeordnet --"]
|
|
if self.main_window.parser:
|
|
jxl_points.extend(sorted(self.main_window.parser.get_active_points().keys()))
|
|
|
|
for row, (name, (x, y, z)) in enumerate(sorted(self.loaded_target_points.items())):
|
|
# Soll-Punkt Name
|
|
self.assign_table.setItem(row, 0, QTableWidgetItem(name))
|
|
|
|
# Soll-Koordinaten
|
|
self.assign_table.setItem(row, 1, QTableWidgetItem(f"{x:.4f}"))
|
|
self.assign_table.setItem(row, 2, QTableWidgetItem(f"{y:.4f}"))
|
|
self.assign_table.setItem(row, 3, QTableWidgetItem(f"{z:.4f}"))
|
|
|
|
# JXL-Punkt Dropdown
|
|
combo = QComboBox()
|
|
combo.addItems(jxl_points)
|
|
|
|
# Automatische Zuordnung: gleicher Name?
|
|
if name in jxl_points:
|
|
idx = combo.findText(name)
|
|
if idx >= 0:
|
|
combo.setCurrentIndex(idx)
|
|
|
|
combo.currentTextChanged.connect(
|
|
lambda text, r=row: self.on_jxl_point_selected(r, text))
|
|
self.assign_table.setCellWidget(row, 4, combo)
|
|
|
|
# Ist-Koordinaten (werden bei Auswahl gefüllt)
|
|
self.assign_table.setItem(row, 5, QTableWidgetItem(""))
|
|
self.assign_table.setItem(row, 6, QTableWidgetItem(""))
|
|
self.assign_table.setItem(row, 7, QTableWidgetItem(""))
|
|
|
|
# Initiale Zuordnung auslösen
|
|
self.on_jxl_point_selected(row, combo.currentText())
|
|
|
|
def on_jxl_point_selected(self, row, jxl_name):
|
|
"""Wird aufgerufen, wenn ein JXL-Punkt ausgewählt wird"""
|
|
if jxl_name == "-- Nicht zugeordnet --" or not self.main_window.parser:
|
|
self.assign_table.setItem(row, 5, QTableWidgetItem(""))
|
|
self.assign_table.setItem(row, 6, QTableWidgetItem(""))
|
|
self.assign_table.setItem(row, 7, QTableWidgetItem(""))
|
|
return
|
|
|
|
if jxl_name in self.main_window.parser.points:
|
|
p = self.main_window.parser.points[jxl_name]
|
|
self.assign_table.setItem(row, 5, QTableWidgetItem(f"{p.east:.4f}" if p.east else ""))
|
|
self.assign_table.setItem(row, 6, QTableWidgetItem(f"{p.north:.4f}" if p.north else ""))
|
|
self.assign_table.setItem(row, 7, QTableWidgetItem(f"{p.elevation:.4f}" if p.elevation else ""))
|
|
|
|
def calculate_transformation(self):
|
|
"""Berechnet die Transformation basierend auf den Punkt-Paaren"""
|
|
if not self.loaded_target_points:
|
|
QMessageBox.warning(self, "Fehler", "Bitte zuerst eine Punktdatei laden!")
|
|
return
|
|
|
|
if not self.main_window.parser:
|
|
QMessageBox.warning(self, "Fehler", "Bitte zuerst eine JXL-Datei laden!")
|
|
return
|
|
|
|
self.georeferencer.clear_control_points()
|
|
|
|
# Punkt-Paare sammeln
|
|
valid_pairs = 0
|
|
for row in range(self.assign_table.rowCount()):
|
|
combo = self.assign_table.cellWidget(row, 4)
|
|
if not combo:
|
|
continue
|
|
|
|
jxl_name = combo.currentText()
|
|
if jxl_name == "-- Nicht zugeordnet --":
|
|
continue
|
|
|
|
# Soll-Koordinaten
|
|
target_name = self.assign_table.item(row, 0).text()
|
|
if target_name not in self.loaded_target_points:
|
|
continue
|
|
|
|
target_x, target_y, target_z = self.loaded_target_points[target_name]
|
|
|
|
# Ist-Koordinaten aus JXL
|
|
if jxl_name not in self.main_window.parser.points:
|
|
continue
|
|
|
|
p = self.main_window.parser.points[jxl_name]
|
|
if p.east is None or p.north is None:
|
|
continue
|
|
|
|
local_x = p.east
|
|
local_y = p.north
|
|
local_z = p.elevation or 0
|
|
|
|
self.georeferencer.add_control_point(
|
|
jxl_name, local_x, local_y, local_z, target_x, target_y, target_z
|
|
)
|
|
valid_pairs += 1
|
|
|
|
if valid_pairs < 2:
|
|
QMessageBox.warning(self, "Fehler",
|
|
f"Mindestens 2 gültige Punkt-Paare erforderlich!\n"
|
|
f"Aktuell: {valid_pairs}")
|
|
return
|
|
|
|
try:
|
|
self.georeferencer.compute_transformation()
|
|
report = self.georeferencer.get_transformation_report()
|
|
self.results_text.setText(report)
|
|
self.main_window.statusBar().showMessage(
|
|
f"Georeferenzierung berechnet ({valid_pairs} Passpunkte)")
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Fehler", f"Berechnung fehlgeschlagen: {e}")
|
|
|
|
def apply_to_all_points(self):
|
|
"""Wendet die Transformation auf alle JXL-Punkte an"""
|
|
if self.georeferencer.result is None:
|
|
QMessageBox.warning(self, "Fehler", "Bitte zuerst Transformation berechnen!")
|
|
return
|
|
|
|
if not self.main_window.parser:
|
|
return
|
|
|
|
reply = QMessageBox.question(
|
|
self, "Bestätigung",
|
|
"Transformation auf alle Punkte anwenden?",
|
|
QMessageBox.Yes | QMessageBox.No)
|
|
|
|
if reply == QMessageBox.No:
|
|
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()
|
|
|
|
# In Parser übernehmen
|
|
for tp in transformed:
|
|
if tp.name in self.main_window.parser.points:
|
|
p = self.main_window.parser.points[tp.name]
|
|
p.east = tp.x
|
|
p.north = tp.y
|
|
p.elevation = tp.z
|
|
|
|
# GUI aktualisieren
|
|
jxl_tab = self.main_window.tabs.widget(0)
|
|
if hasattr(jxl_tab, 'update_display'):
|
|
jxl_tab.update_display()
|
|
|
|
QMessageBox.information(self, "Erfolg",
|
|
f"{len(transformed)} Punkte georeferenziert!")
|
|
|
|
self.main_window.statusBar().showMessage("Georeferenzierung angewendet")
|
|
|
|
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_transformed_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 für Export
|
|
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_points_with_dialog(self, transformed, "georeferenziert")
|
|
|
|
|
|
# =============================================================================
|
|
# Netzausgleichung Tab
|
|
# =============================================================================
|
|
class NetworkAdjustmentTab(QWidget):
|
|
"""Tab für Netzausgleichung"""
|
|
|
|
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 = QLabel(
|
|
"💡 Automatische Erkennung:\n"
|
|
" • Festpunkte: Stationspunkte und Anschlusspunkte\n"
|
|
" • Messpunkte: 3000er Punkte"
|
|
)
|
|
info_label.setStyleSheet("color: #666; background-color: #f0f0f0; padding: 10px;")
|
|
points_layout.addWidget(info_label)
|
|
|
|
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(150)
|
|
points_layout.addWidget(self.points_table)
|
|
|
|
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):
|
|
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()
|
|
|
|
for station_id, station in parser.stations.items():
|
|
if station.name:
|
|
self.fixed_points.add(station.name)
|
|
|
|
for bb_id, bb in parser.backbearings.items():
|
|
if bb.station_record_id == station_id and bb.backsight:
|
|
self.fixed_points.add(bb.backsight)
|
|
|
|
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)
|
|
|
|
for name in parser.get_active_points().keys():
|
|
if name.startswith("3"):
|
|
self.measurement_points.add(name)
|
|
|
|
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):
|
|
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)):
|
|
self.points_table.setItem(row, 0, QTableWidgetItem(name))
|
|
|
|
if name in self.fixed_points:
|
|
type_item = QTableWidgetItem("Festpunkt")
|
|
type_item.setBackground(QBrush(QColor(200, 230, 200)))
|
|
else:
|
|
type_item = QTableWidgetItem("Messpunkt")
|
|
type_item.setBackground(QBrush(QColor(200, 200, 230)))
|
|
self.points_table.setItem(row, 1, type_item)
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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()
|
|
|
|
self.adjustment.extract_observations()
|
|
self.adjustment.initialize_points()
|
|
|
|
for point_name in self.fixed_points:
|
|
self.adjustment.set_fixed_point(point_name)
|
|
|
|
try:
|
|
result = self.adjustment.adjust()
|
|
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 {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):
|
|
if not self.adjustment or not self.adjustment.result:
|
|
return "Keine Ergebnisse."
|
|
|
|
lines = []
|
|
lines.append("=" * 80)
|
|
lines.append("NETZAUSGLEICHUNG - ERGEBNISBERICHT")
|
|
lines.append("=" * 80)
|
|
lines.append("")
|
|
lines.append(f"Festpunkte: {len(self.fixed_points)}")
|
|
lines.append(f"Messpunkte: {len(self.measurement_points)}")
|
|
lines.append(f"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(f"Sigma-0 a-post.: {self.adjustment.result.sigma_0_posteriori:.4f}")
|
|
lines.append("")
|
|
lines.append("-" * 80)
|
|
lines.append(f"{'Punkt':<12} {'Typ':<12} {'X [m]':>14} {'Y [m]':>14} {'Z [m]':>12}")
|
|
lines.append("-" * 80)
|
|
|
|
for name in sorted(list(self.fixed_points) + list(self.measurement_points)):
|
|
if name in self.adjustment.points:
|
|
p = self.adjustment.points[name]
|
|
typ = "Festpunkt" if name in self.fixed_points else "Messpunkt"
|
|
lines.append(f"{name:<12} {typ:<12} {p.x:>14.4f} {p.y:>14.4f} {p.z:>12.4f}")
|
|
|
|
return "\n".join(lines)
|
|
|
|
def export_report(self):
|
|
if not self.adjustment:
|
|
QMessageBox.warning(self, "Fehler", "Keine Ergebnisse!")
|
|
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"Gespeichert: {file_path}")
|
|
|
|
def export_points(self):
|
|
if not self.adjustment or not self.adjustment.result:
|
|
QMessageBox.warning(self, "Fehler", "Keine Ergebnisse!")
|
|
return
|
|
|
|
points = []
|
|
for name, p in self.adjustment.points.items():
|
|
points.append(CORPoint(name=name, x=p.x, y=p.y, z=p.z))
|
|
|
|
export_points_with_dialog(self, points, "ausgeglichen")
|
|
|
|
|
|
# =============================================================================
|
|
# Referenzpunkt-Anpassung Tab
|
|
# =============================================================================
|
|
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
|
|
new_coords_group = QGroupBox("Neue Koordinaten")
|
|
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)
|
|
|
|
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, 3, 0, 1, 2)
|
|
|
|
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")
|
|
preview_btn.clicked.connect(self.preview_transformation)
|
|
preview_btn.setStyleSheet("background-color: #4CAF50; color: white;")
|
|
actions_layout.addWidget(preview_btn)
|
|
|
|
apply_btn = QPushButton("Anwenden")
|
|
apply_btn.clicked.connect(self.apply_transformation)
|
|
apply_btn.setStyleSheet("background-color: #2196F3; color: white;")
|
|
actions_layout.addWidget(apply_btn)
|
|
|
|
export_btn = QPushButton("📥 Exportieren...")
|
|
export_btn.clicked.connect(self.export_points)
|
|
export_btn.setStyleSheet("background-color: #FF9800; color: white;")
|
|
actions_layout.addWidget(export_btn)
|
|
|
|
layout.addWidget(actions_group)
|
|
|
|
# Vorschau-Tabelle
|
|
preview_group = QGroupBox("Vorschau")
|
|
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("Bericht")
|
|
report_layout = QVBoxLayout(report_group)
|
|
|
|
self.report_text = QTextEdit()
|
|
self.report_text.setReadOnly(True)
|
|
self.report_text.setFont(QFont("Courier", 9))
|
|
self.report_text.setMaximumHeight(150)
|
|
report_layout.addWidget(self.report_text)
|
|
|
|
layout.addWidget(report_group)
|
|
|
|
def load_reference_point(self):
|
|
if not self.main_window.parser:
|
|
QMessageBox.warning(self, "Fehler", "Bitte zuerst JXL 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", 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")
|
|
|
|
self.new_east_spin.setValue(info['east'])
|
|
self.new_north_spin.setValue(info['north'])
|
|
self.new_elev_spin.setValue(info['elevation'])
|
|
|
|
def update_delta(self):
|
|
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):
|
|
if not self.main_window.parser:
|
|
QMessageBox.warning(self, "Fehler", "Bitte zuerst JXL laden!")
|
|
return
|
|
|
|
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"{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()
|
|
|
|
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}"))
|
|
|
|
self.report_text.setText(self.adjuster.get_summary_report())
|
|
|
|
def apply_transformation(self):
|
|
if not self.main_window.parser:
|
|
QMessageBox.warning(self, "Fehler", "Bitte zuerst JXL laden!")
|
|
return
|
|
|
|
if not self.adjuster.affected_points:
|
|
QMessageBox.warning(self, "Fehler", "Bitte zuerst Vorschau berechnen!")
|
|
return
|
|
|
|
reply = QMessageBox.question(self, "Bestätigung",
|
|
f"Transformation auf {len(self.adjuster.affected_points)} Punkte anwenden?",
|
|
QMessageBox.Yes | QMessageBox.No)
|
|
|
|
if reply == QMessageBox.No:
|
|
return
|
|
|
|
if self.adjuster.apply_transformation():
|
|
self.report_text.setText(self.adjuster.get_summary_report())
|
|
|
|
jxl_tab = self.main_window.tabs.widget(0)
|
|
if hasattr(jxl_tab, 'update_display'):
|
|
jxl_tab.update_display()
|
|
|
|
QMessageBox.information(self, "Erfolg", "Transformation angewendet!")
|
|
|
|
def export_points(self):
|
|
if not self.main_window.parser:
|
|
QMessageBox.warning(self, "Fehler", "Keine Punkte!")
|
|
return
|
|
|
|
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))
|
|
|
|
export_points_with_dialog(self, points, "angepasst")
|
|
|
|
|
|
# =============================================================================
|
|
# Hauptfenster
|
|
# =============================================================================
|
|
class MainWindow(QMainWindow):
|
|
"""Hauptfenster der Anwendung"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.parser = None
|
|
self.setWindowTitle("Trimble Geodesy Tool v2.1")
|
|
self.setMinimumSize(1100, 800)
|
|
self.setup_ui()
|
|
self.setup_menu()
|
|
|
|
def setup_ui(self):
|
|
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")
|
|
|
|
main_layout.addWidget(self.tabs)
|
|
|
|
# Status Bar
|
|
self.setStatusBar(QStatusBar())
|
|
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("JXL öffnen...", self)
|
|
open_action.setShortcut("Ctrl+O")
|
|
open_action.triggered.connect(self.open_jxl_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_jxl_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",
|
|
"Trimble Geodesy Tool v2.1\n\n"
|
|
"Geodätische Vermessungsarbeiten mit JXL-Dateien\n\n"
|
|
"Features:\n"
|
|
"• JXL-Datei Analyse mit TreeView\n"
|
|
"• COR/CSV/TXT/DXF Export\n"
|
|
"• Koordinatentransformation\n"
|
|
"• Georeferenzierung\n"
|
|
"• Netzausgleichung\n"
|
|
"• Referenzpunkt-Anpassung")
|
|
|
|
|
|
def main():
|
|
app = QApplication(sys.argv)
|
|
app.setStyle("Fusion")
|
|
|
|
# Dunkleres Theme
|
|
palette = QPalette()
|
|
palette.setColor(QPalette.Window, QColor(240, 240, 240))
|
|
palette.setColor(QPalette.WindowText, QColor(0, 0, 0))
|
|
palette.setColor(QPalette.Base, QColor(255, 255, 255))
|
|
palette.setColor(QPalette.AlternateBase, QColor(245, 245, 245))
|
|
palette.setColor(QPalette.ToolTipBase, QColor(255, 255, 220))
|
|
palette.setColor(QPalette.ToolTipText, QColor(0, 0, 0))
|
|
palette.setColor(QPalette.Text, QColor(0, 0, 0))
|
|
palette.setColor(QPalette.Button, QColor(240, 240, 240))
|
|
palette.setColor(QPalette.ButtonText, QColor(0, 0, 0))
|
|
palette.setColor(QPalette.BrightText, QColor(255, 0, 0))
|
|
palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
|
|
palette.setColor(QPalette.HighlightedText, QColor(255, 255, 255))
|
|
app.setPalette(palette)
|
|
|
|
window = MainWindow()
|
|
window.show()
|
|
|
|
sys.exit(app.exec_())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|