Add reference point adjuster module and requirements.txt
- Added requirements.txt with all Python dependencies - Created new module: modules/reference_point_adjuster.py - Identifies reference point 5001 from ReflineStationSetup - Allows entering new coordinates for the reference point - Recalculates all dependent points using translation - Exports modified JXL file - Extended GUI with new tab 'Referenzpunkt anpassen' - Shows current reference point coordinates - Input fields for new coordinates with live delta display - Preview transformation before applying - Apply transformation and export buttons - Detailed transformation report - Updated README.md with new features and installation instructions - Added test files for validation
This commit is contained in:
parent
6af2c0333f
commit
27489a1d94
21
README.md
21
README.md
|
|
@ -44,6 +44,14 @@ Ein vollständiges Python-Programm mit grafischer Benutzeroberfläche (GUI) für
|
||||||
- Residuen
|
- Residuen
|
||||||
- Qualitätsparametern (Sigma-0, Chi-Quadrat, RMSE)
|
- Qualitätsparametern (Sigma-0, Chi-Quadrat, RMSE)
|
||||||
|
|
||||||
|
### 6. Referenzpunkt-Anpassung (NEU)
|
||||||
|
- Anpassung des Referenzpunktes 5001 (bei Referenzlinien-Stationierung)
|
||||||
|
- Eingabe neuer Koordinaten (X, Y, Z) für den Referenzpunkt
|
||||||
|
- Automatische Neuberechnung aller abhängigen Punkte
|
||||||
|
- Vorschau der Transformation mit allen betroffenen Punkten
|
||||||
|
- Export der transformierten JXL-Datei
|
||||||
|
- Validierung der Eingaben mit Warnungen bei problematischen Werten
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Voraussetzungen
|
### Voraussetzungen
|
||||||
|
|
@ -54,6 +62,14 @@ Ein vollständiges Python-Programm mit grafischer Benutzeroberfläche (GUI) für
|
||||||
- lxml
|
- lxml
|
||||||
|
|
||||||
### Installation der Abhängigkeiten
|
### Installation der Abhängigkeiten
|
||||||
|
|
||||||
|
**Option 1: Mit requirements.txt (empfohlen)**
|
||||||
|
```bash
|
||||||
|
cd /home/ubuntu/trimble_geodesy
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Manuelle Installation**
|
||||||
```bash
|
```bash
|
||||||
pip install PyQt5 numpy scipy lxml
|
pip install PyQt5 numpy scipy lxml
|
||||||
```
|
```
|
||||||
|
|
@ -72,19 +88,22 @@ python3 main.py
|
||||||
3. **Transformation Tab**: Koordinatensystem rotieren/verschieben
|
3. **Transformation Tab**: Koordinatensystem rotieren/verschieben
|
||||||
4. **Georeferenzierung Tab**: Mit Passpunkten transformieren
|
4. **Georeferenzierung Tab**: Mit Passpunkten transformieren
|
||||||
5. **Netzausgleichung Tab**: Netzausgleichung durchführen
|
5. **Netzausgleichung Tab**: Netzausgleichung durchführen
|
||||||
|
6. **Referenzpunkt anpassen Tab**: Referenzpunkt 5001 ändern und JXL neu berechnen
|
||||||
|
|
||||||
## Dateistruktur
|
## Dateistruktur
|
||||||
|
|
||||||
```
|
```
|
||||||
trimble_geodesy/
|
trimble_geodesy/
|
||||||
├── main.py # Hauptprogramm mit GUI
|
├── main.py # Hauptprogramm mit GUI
|
||||||
|
├── requirements.txt # Python-Abhängigkeiten
|
||||||
├── modules/
|
├── modules/
|
||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
│ ├── jxl_parser.py # JXL-Datei Parser
|
│ ├── jxl_parser.py # JXL-Datei Parser
|
||||||
│ ├── cor_generator.py # COR-Datei Generator
|
│ ├── cor_generator.py # COR-Datei Generator
|
||||||
│ ├── transformation.py # Koordinatentransformation
|
│ ├── transformation.py # Koordinatentransformation
|
||||||
│ ├── georeferencing.py # Georeferenzierung
|
│ ├── georeferencing.py # Georeferenzierung
|
||||||
│ └── network_adjustment.py # Netzausgleichung
|
│ ├── network_adjustment.py # Netzausgleichung
|
||||||
|
│ └── reference_point_adjuster.py # Referenzpunkt-Anpassung (NEU)
|
||||||
├── output/ # Ausgabeverzeichnis
|
├── output/ # Ausgabeverzeichnis
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
|
||||||
278
main.py
278
main.py
|
|
@ -28,6 +28,7 @@ from modules.cor_generator import CORGenerator, CORPoint
|
||||||
from modules.transformation import CoordinateTransformer, LocalSystemTransformer
|
from modules.transformation import CoordinateTransformer, LocalSystemTransformer
|
||||||
from modules.georeferencing import Georeferencer, ControlPoint
|
from modules.georeferencing import Georeferencer, ControlPoint
|
||||||
from modules.network_adjustment import NetworkAdjustment
|
from modules.network_adjustment import NetworkAdjustment
|
||||||
|
from modules.reference_point_adjuster import ReferencePointAdjuster, TransformationResult
|
||||||
|
|
||||||
|
|
||||||
class JXLAnalysisTab(QWidget):
|
class JXLAnalysisTab(QWidget):
|
||||||
|
|
@ -948,6 +949,280 @@ class NetworkAdjustmentTab(QWidget):
|
||||||
QMessageBox.information(self, "Erfolg", f"Koordinaten gespeichert: {file_path}")
|
QMessageBox.information(self, "Erfolg", f"Koordinaten gespeichert: {file_path}")
|
||||||
|
|
||||||
|
|
||||||
|
class ReferencePointAdjusterTab(QWidget):
|
||||||
|
"""Tab für Referenzpunkt-Anpassung"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.main_window = parent
|
||||||
|
self.adjuster = ReferencePointAdjuster()
|
||||||
|
self.setup_ui()
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
|
||||||
|
# Info-Gruppe
|
||||||
|
info_group = QGroupBox("Aktueller Referenzpunkt")
|
||||||
|
info_layout = QFormLayout(info_group)
|
||||||
|
|
||||||
|
self.ref_point_name = QLabel("Nicht geladen")
|
||||||
|
self.ref_point_name.setStyleSheet("font-weight: bold; font-size: 14px;")
|
||||||
|
info_layout.addRow("Punktname:", self.ref_point_name)
|
||||||
|
|
||||||
|
self.current_east = QLabel("0.000")
|
||||||
|
self.current_north = QLabel("0.000")
|
||||||
|
self.current_elev = QLabel("0.000")
|
||||||
|
|
||||||
|
info_layout.addRow("East (X):", self.current_east)
|
||||||
|
info_layout.addRow("North (Y):", self.current_north)
|
||||||
|
info_layout.addRow("Elevation (Z):", self.current_elev)
|
||||||
|
|
||||||
|
refresh_btn = QPushButton("Referenzpunkt aktualisieren")
|
||||||
|
refresh_btn.clicked.connect(self.load_reference_point)
|
||||||
|
info_layout.addRow("", refresh_btn)
|
||||||
|
|
||||||
|
layout.addWidget(info_group)
|
||||||
|
|
||||||
|
# Neue Koordinaten Gruppe
|
||||||
|
new_coords_group = QGroupBox("Neue Koordinaten für Referenzpunkt")
|
||||||
|
new_coords_layout = QGridLayout(new_coords_group)
|
||||||
|
|
||||||
|
new_coords_layout.addWidget(QLabel("East (X):"), 0, 0)
|
||||||
|
self.new_east_spin = QDoubleSpinBox()
|
||||||
|
self.new_east_spin.setRange(-10000000, 10000000)
|
||||||
|
self.new_east_spin.setDecimals(4)
|
||||||
|
self.new_east_spin.setSuffix(" m")
|
||||||
|
new_coords_layout.addWidget(self.new_east_spin, 0, 1)
|
||||||
|
|
||||||
|
new_coords_layout.addWidget(QLabel("North (Y):"), 1, 0)
|
||||||
|
self.new_north_spin = QDoubleSpinBox()
|
||||||
|
self.new_north_spin.setRange(-10000000, 10000000)
|
||||||
|
self.new_north_spin.setDecimals(4)
|
||||||
|
self.new_north_spin.setSuffix(" m")
|
||||||
|
new_coords_layout.addWidget(self.new_north_spin, 1, 1)
|
||||||
|
|
||||||
|
new_coords_layout.addWidget(QLabel("Elevation (Z):"), 2, 0)
|
||||||
|
self.new_elev_spin = QDoubleSpinBox()
|
||||||
|
self.new_elev_spin.setRange(-10000, 10000)
|
||||||
|
self.new_elev_spin.setDecimals(4)
|
||||||
|
self.new_elev_spin.setSuffix(" m")
|
||||||
|
new_coords_layout.addWidget(self.new_elev_spin, 2, 1)
|
||||||
|
|
||||||
|
# Translation anzeigen
|
||||||
|
new_coords_layout.addWidget(QLabel(""), 3, 0)
|
||||||
|
self.delta_label = QLabel("ΔX: 0.000 m | ΔY: 0.000 m | ΔZ: 0.000 m")
|
||||||
|
self.delta_label.setStyleSheet("color: blue;")
|
||||||
|
new_coords_layout.addWidget(self.delta_label, 4, 0, 1, 2)
|
||||||
|
|
||||||
|
# Koordinaten-Änderungen live berechnen
|
||||||
|
self.new_east_spin.valueChanged.connect(self.update_delta)
|
||||||
|
self.new_north_spin.valueChanged.connect(self.update_delta)
|
||||||
|
self.new_elev_spin.valueChanged.connect(self.update_delta)
|
||||||
|
|
||||||
|
layout.addWidget(new_coords_group)
|
||||||
|
|
||||||
|
# Aktionen
|
||||||
|
actions_group = QGroupBox("Aktionen")
|
||||||
|
actions_layout = QHBoxLayout(actions_group)
|
||||||
|
|
||||||
|
preview_btn = QPushButton("Vorschau berechnen")
|
||||||
|
preview_btn.clicked.connect(self.preview_transformation)
|
||||||
|
preview_btn.setStyleSheet("background-color: #4CAF50; color: white;")
|
||||||
|
actions_layout.addWidget(preview_btn)
|
||||||
|
|
||||||
|
apply_btn = QPushButton("Transformation anwenden")
|
||||||
|
apply_btn.clicked.connect(self.apply_transformation)
|
||||||
|
apply_btn.setStyleSheet("background-color: #2196F3; color: white;")
|
||||||
|
actions_layout.addWidget(apply_btn)
|
||||||
|
|
||||||
|
export_btn = QPushButton("Neue JXL exportieren")
|
||||||
|
export_btn.clicked.connect(self.export_jxl)
|
||||||
|
export_btn.setStyleSheet("background-color: #FF9800; color: white;")
|
||||||
|
actions_layout.addWidget(export_btn)
|
||||||
|
|
||||||
|
layout.addWidget(actions_group)
|
||||||
|
|
||||||
|
# Vorschau-Tabelle
|
||||||
|
preview_group = QGroupBox("Vorschau der betroffenen Punkte")
|
||||||
|
preview_layout = QVBoxLayout(preview_group)
|
||||||
|
|
||||||
|
self.preview_table = QTableWidget()
|
||||||
|
self.preview_table.setColumnCount(7)
|
||||||
|
self.preview_table.setHorizontalHeaderLabels(
|
||||||
|
["Punkt", "Alt X", "Alt Y", "Alt Z", "Neu X", "Neu Y", "Neu Z"])
|
||||||
|
self.preview_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||||
|
preview_layout.addWidget(self.preview_table)
|
||||||
|
|
||||||
|
layout.addWidget(preview_group)
|
||||||
|
|
||||||
|
# Bericht
|
||||||
|
report_group = QGroupBox("Transformationsbericht")
|
||||||
|
report_layout = QVBoxLayout(report_group)
|
||||||
|
|
||||||
|
self.report_text = QTextEdit()
|
||||||
|
self.report_text.setReadOnly(True)
|
||||||
|
self.report_text.setFont(QFont("Courier", 9))
|
||||||
|
report_layout.addWidget(self.report_text)
|
||||||
|
|
||||||
|
layout.addWidget(report_group)
|
||||||
|
|
||||||
|
def load_reference_point(self):
|
||||||
|
"""Lädt die Informationen zum Referenzpunkt"""
|
||||||
|
if not self.main_window.parser:
|
||||||
|
QMessageBox.warning(self, "Fehler",
|
||||||
|
"Bitte zuerst eine JXL-Datei im 'JXL-Analyse' Tab laden!")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.adjuster.set_parser(self.main_window.parser)
|
||||||
|
info = self.adjuster.get_reference_point_info()
|
||||||
|
|
||||||
|
if not info["found"]:
|
||||||
|
QMessageBox.warning(self, "Fehler",
|
||||||
|
f"Referenzpunkt nicht gefunden: {info['message']}")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.ref_point_name.setText(info["name"])
|
||||||
|
self.current_east.setText(f"{info['east']:.4f} m")
|
||||||
|
self.current_north.setText(f"{info['north']:.4f} m")
|
||||||
|
self.current_elev.setText(f"{info['elevation']:.4f} m")
|
||||||
|
|
||||||
|
# Setze aktuelle Werte als Standard für neue Koordinaten
|
||||||
|
self.new_east_spin.setValue(info['east'])
|
||||||
|
self.new_north_spin.setValue(info['north'])
|
||||||
|
self.new_elev_spin.setValue(info['elevation'])
|
||||||
|
|
||||||
|
self.main_window.statusBar().showMessage(
|
||||||
|
f"Referenzpunkt '{info['name']}' geladen")
|
||||||
|
|
||||||
|
def update_delta(self):
|
||||||
|
"""Aktualisiert die Anzeige der Verschiebung"""
|
||||||
|
if not self.adjuster.parser:
|
||||||
|
return
|
||||||
|
|
||||||
|
dx = self.new_east_spin.value() - self.adjuster.original_coords[0]
|
||||||
|
dy = self.new_north_spin.value() - self.adjuster.original_coords[1]
|
||||||
|
dz = self.new_elev_spin.value() - self.adjuster.original_coords[2]
|
||||||
|
|
||||||
|
self.delta_label.setText(
|
||||||
|
f"ΔX: {dx:+.4f} m | ΔY: {dy:+.4f} m | ΔZ: {dz:+.4f} m")
|
||||||
|
|
||||||
|
def preview_transformation(self):
|
||||||
|
"""Zeigt eine Vorschau der Transformation"""
|
||||||
|
if not self.main_window.parser:
|
||||||
|
QMessageBox.warning(self, "Fehler",
|
||||||
|
"Bitte zuerst eine JXL-Datei laden!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Validierung
|
||||||
|
valid, message = self.adjuster.validate_input(
|
||||||
|
self.new_east_spin.value(),
|
||||||
|
self.new_north_spin.value(),
|
||||||
|
self.new_elev_spin.value()
|
||||||
|
)
|
||||||
|
|
||||||
|
if not valid:
|
||||||
|
reply = QMessageBox.warning(self, "Warnung",
|
||||||
|
f"Mögliche Probleme erkannt:\n\n{message}\n\nTrotzdem fortfahren?",
|
||||||
|
QMessageBox.Yes | QMessageBox.No)
|
||||||
|
if reply == QMessageBox.No:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.adjuster.set_new_coordinates(
|
||||||
|
self.new_east_spin.value(),
|
||||||
|
self.new_north_spin.value(),
|
||||||
|
self.new_elev_spin.value()
|
||||||
|
)
|
||||||
|
|
||||||
|
results = self.adjuster.preview_transformation()
|
||||||
|
|
||||||
|
# Tabelle aktualisieren
|
||||||
|
self.preview_table.setRowCount(len(results))
|
||||||
|
for i, result in enumerate(results):
|
||||||
|
self.preview_table.setItem(i, 0, QTableWidgetItem(result.original_point))
|
||||||
|
self.preview_table.setItem(i, 1, QTableWidgetItem(f"{result.original_coords[0]:.4f}"))
|
||||||
|
self.preview_table.setItem(i, 2, QTableWidgetItem(f"{result.original_coords[1]:.4f}"))
|
||||||
|
self.preview_table.setItem(i, 3, QTableWidgetItem(f"{result.original_coords[2]:.4f}"))
|
||||||
|
self.preview_table.setItem(i, 4, QTableWidgetItem(f"{result.new_coords[0]:.4f}"))
|
||||||
|
self.preview_table.setItem(i, 5, QTableWidgetItem(f"{result.new_coords[1]:.4f}"))
|
||||||
|
self.preview_table.setItem(i, 6, QTableWidgetItem(f"{result.new_coords[2]:.4f}"))
|
||||||
|
|
||||||
|
# Bericht aktualisieren
|
||||||
|
self.report_text.setText(self.adjuster.get_summary_report())
|
||||||
|
|
||||||
|
self.main_window.statusBar().showMessage(
|
||||||
|
f"Vorschau berechnet: {len(results)} Punkte betroffen")
|
||||||
|
|
||||||
|
def apply_transformation(self):
|
||||||
|
"""Wendet die Transformation auf die JXL-Datei an"""
|
||||||
|
if not self.main_window.parser:
|
||||||
|
QMessageBox.warning(self, "Fehler",
|
||||||
|
"Bitte zuerst eine JXL-Datei laden!")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.adjuster.affected_points:
|
||||||
|
QMessageBox.warning(self, "Fehler",
|
||||||
|
"Bitte zuerst eine Vorschau berechnen!")
|
||||||
|
return
|
||||||
|
|
||||||
|
reply = QMessageBox.question(self, "Bestätigung",
|
||||||
|
f"Soll die Transformation auf {len(self.adjuster.affected_points)} Punkte angewendet werden?\n\n"
|
||||||
|
"Die Änderungen werden in der geladenen JXL-Datei gespeichert.",
|
||||||
|
QMessageBox.Yes | QMessageBox.No)
|
||||||
|
|
||||||
|
if reply == QMessageBox.No:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.adjuster.apply_transformation():
|
||||||
|
# Bericht aktualisieren
|
||||||
|
self.report_text.setText(self.adjuster.get_summary_report())
|
||||||
|
|
||||||
|
QMessageBox.information(self, "Erfolg",
|
||||||
|
"Transformation erfolgreich angewendet!\n\n"
|
||||||
|
"Die Koordinaten wurden aktualisiert.\n"
|
||||||
|
"Verwenden Sie 'Neue JXL exportieren' zum Speichern.")
|
||||||
|
|
||||||
|
self.main_window.statusBar().showMessage("Transformation angewendet")
|
||||||
|
else:
|
||||||
|
QMessageBox.critical(self, "Fehler",
|
||||||
|
"Transformation konnte nicht angewendet werden!")
|
||||||
|
|
||||||
|
def export_jxl(self):
|
||||||
|
"""Exportiert die modifizierte JXL-Datei"""
|
||||||
|
if not self.main_window.parser:
|
||||||
|
QMessageBox.warning(self, "Fehler",
|
||||||
|
"Bitte zuerst eine JXL-Datei laden!")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.adjuster.transformation_applied:
|
||||||
|
reply = QMessageBox.warning(self, "Warnung",
|
||||||
|
"Die Transformation wurde noch nicht angewendet.\n"
|
||||||
|
"Möchten Sie die Originaldatei exportieren?",
|
||||||
|
QMessageBox.Yes | QMessageBox.No)
|
||||||
|
if reply == QMessageBox.No:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Standard-Dateiname vorschlagen
|
||||||
|
original_path = self.main_window.parser.file_path
|
||||||
|
if original_path:
|
||||||
|
import os
|
||||||
|
base, ext = os.path.splitext(original_path)
|
||||||
|
default_name = f"{base}_transformed{ext}"
|
||||||
|
else:
|
||||||
|
default_name = "transformed.jxl"
|
||||||
|
|
||||||
|
file_path, _ = QFileDialog.getSaveFileName(
|
||||||
|
self, "JXL-Datei exportieren", default_name, "JXL Files (*.jxl)")
|
||||||
|
|
||||||
|
if file_path:
|
||||||
|
if self.adjuster.export_jxl(file_path):
|
||||||
|
QMessageBox.information(self, "Erfolg",
|
||||||
|
f"JXL-Datei exportiert:\n{file_path}")
|
||||||
|
self.main_window.statusBar().showMessage(f"Exportiert: {file_path}")
|
||||||
|
else:
|
||||||
|
QMessageBox.critical(self, "Fehler",
|
||||||
|
"Fehler beim Exportieren der JXL-Datei!")
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
"""Hauptfenster der Anwendung"""
|
"""Hauptfenster der Anwendung"""
|
||||||
|
|
||||||
|
|
@ -990,6 +1265,9 @@ class MainWindow(QMainWindow):
|
||||||
self.adjust_tab = NetworkAdjustmentTab(self)
|
self.adjust_tab = NetworkAdjustmentTab(self)
|
||||||
self.tabs.addTab(self.adjust_tab, "📐 Netzausgleichung")
|
self.tabs.addTab(self.adjust_tab, "📐 Netzausgleichung")
|
||||||
|
|
||||||
|
self.refpoint_tab = ReferencePointAdjusterTab(self)
|
||||||
|
self.tabs.addTab(self.refpoint_tab, "📍 Referenzpunkt anpassen")
|
||||||
|
|
||||||
layout.addWidget(self.tabs)
|
layout.addWidget(self.tabs)
|
||||||
|
|
||||||
def setup_menu(self):
|
def setup_menu(self):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,323 @@
|
||||||
|
"""
|
||||||
|
Reference Point Adjuster Module - Referenzpunkt-Anpassung für JXL-Dateien
|
||||||
|
Ermöglicht die Änderung des Referenzpunktes 5001 und Neuberechnung aller Koordinaten
|
||||||
|
"""
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Dict, Optional, Tuple
|
||||||
|
import math
|
||||||
|
import copy
|
||||||
|
import os
|
||||||
|
|
||||||
|
from modules.jxl_parser import JXLParser, Point, Station
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TransformationResult:
|
||||||
|
"""Ergebnis der Koordinatentransformation"""
|
||||||
|
original_point: str
|
||||||
|
original_coords: Tuple[float, float, float]
|
||||||
|
new_coords: Tuple[float, float, float]
|
||||||
|
delta: Tuple[float, float, float]
|
||||||
|
|
||||||
|
|
||||||
|
class ReferencePointAdjuster:
|
||||||
|
"""Klasse zur Anpassung des Referenzpunktes und Neuberechnung der JXL-Datei"""
|
||||||
|
|
||||||
|
def __init__(self, parser: JXLParser = None):
|
||||||
|
self.parser = parser
|
||||||
|
self.reference_point_name = "5001"
|
||||||
|
self.original_coords: Tuple[float, float, float] = (0.0, 0.0, 0.0)
|
||||||
|
self.new_coords: Tuple[float, float, float] = (0.0, 0.0, 0.0)
|
||||||
|
self.affected_points: List[TransformationResult] = []
|
||||||
|
self.transformation_applied = False
|
||||||
|
|
||||||
|
def set_parser(self, parser: JXLParser):
|
||||||
|
"""Setzt den Parser für die JXL-Datei"""
|
||||||
|
self.parser = parser
|
||||||
|
self.find_reference_point()
|
||||||
|
|
||||||
|
def find_reference_point(self) -> Optional[Point]:
|
||||||
|
"""Findet den Referenzpunkt 5001 in der JXL-Datei"""
|
||||||
|
if not self.parser:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Suche nach dem Punkt mit Name "5001"
|
||||||
|
for name, point in self.parser.points.items():
|
||||||
|
if name == self.reference_point_name or name == "5001":
|
||||||
|
self.reference_point_name = name
|
||||||
|
self.original_coords = (
|
||||||
|
point.east if point.east is not None else 0.0,
|
||||||
|
point.north if point.north is not None else 0.0,
|
||||||
|
point.elevation if point.elevation is not None else 0.0
|
||||||
|
)
|
||||||
|
return point
|
||||||
|
|
||||||
|
# Falls 5001 nicht gefunden, suche nach dem ersten Punkt mit Coordinates/KeyedIn
|
||||||
|
for name, point in self.parser.points.items():
|
||||||
|
if point.method == "Coordinates" and point.survey_method == "KeyedIn":
|
||||||
|
self.reference_point_name = name
|
||||||
|
self.original_coords = (
|
||||||
|
point.east if point.east is not None else 0.0,
|
||||||
|
point.north if point.north is not None else 0.0,
|
||||||
|
point.elevation if point.elevation is not None else 0.0
|
||||||
|
)
|
||||||
|
return point
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_reference_point_info(self) -> Dict:
|
||||||
|
"""Gibt Informationen zum Referenzpunkt zurück"""
|
||||||
|
if not self.parser:
|
||||||
|
return {"found": False, "message": "Kein Parser geladen"}
|
||||||
|
|
||||||
|
point = self.find_reference_point()
|
||||||
|
if not point:
|
||||||
|
return {"found": False, "message": "Referenzpunkt nicht gefunden"}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"found": True,
|
||||||
|
"name": self.reference_point_name,
|
||||||
|
"east": self.original_coords[0],
|
||||||
|
"north": self.original_coords[1],
|
||||||
|
"elevation": self.original_coords[2],
|
||||||
|
"method": point.method,
|
||||||
|
"survey_method": point.survey_method
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_new_coordinates(self, east: float, north: float, elevation: float):
|
||||||
|
"""Setzt neue Koordinaten für den Referenzpunkt"""
|
||||||
|
self.new_coords = (east, north, elevation)
|
||||||
|
|
||||||
|
def calculate_translation(self) -> Tuple[float, float, float]:
|
||||||
|
"""Berechnet die Translation zwischen alten und neuen Koordinaten"""
|
||||||
|
delta_x = self.new_coords[0] - self.original_coords[0]
|
||||||
|
delta_y = self.new_coords[1] - self.original_coords[1]
|
||||||
|
delta_z = self.new_coords[2] - self.original_coords[2]
|
||||||
|
return (delta_x, delta_y, delta_z)
|
||||||
|
|
||||||
|
def preview_transformation(self) -> List[TransformationResult]:
|
||||||
|
"""Zeigt eine Vorschau der Transformation für alle betroffenen Punkte"""
|
||||||
|
if not self.parser:
|
||||||
|
return []
|
||||||
|
|
||||||
|
delta = self.calculate_translation()
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for name, point in self.parser.points.items():
|
||||||
|
if point.east is None or point.north is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
original = (
|
||||||
|
point.east,
|
||||||
|
point.north,
|
||||||
|
point.elevation if point.elevation is not None else 0.0
|
||||||
|
)
|
||||||
|
|
||||||
|
new = (
|
||||||
|
original[0] + delta[0],
|
||||||
|
original[1] + delta[1],
|
||||||
|
original[2] + delta[2]
|
||||||
|
)
|
||||||
|
|
||||||
|
results.append(TransformationResult(
|
||||||
|
original_point=name,
|
||||||
|
original_coords=original,
|
||||||
|
new_coords=new,
|
||||||
|
delta=delta
|
||||||
|
))
|
||||||
|
|
||||||
|
self.affected_points = results
|
||||||
|
return results
|
||||||
|
|
||||||
|
def apply_transformation(self) -> bool:
|
||||||
|
"""Wendet die Transformation auf alle Punkte in der JXL-Datei an"""
|
||||||
|
if not self.parser or not self.parser.raw_xml:
|
||||||
|
return False
|
||||||
|
|
||||||
|
delta = self.calculate_translation()
|
||||||
|
root = self.parser.raw_xml.getroot()
|
||||||
|
fieldbook = root.find('FieldBook')
|
||||||
|
|
||||||
|
if fieldbook is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
modified_count = 0
|
||||||
|
|
||||||
|
# Durchsuche alle PointRecords und aktualisiere die Koordinaten
|
||||||
|
for element in fieldbook:
|
||||||
|
if element.tag == 'PointRecord':
|
||||||
|
modified = self._update_point_coordinates(element, delta)
|
||||||
|
if modified:
|
||||||
|
modified_count += 1
|
||||||
|
|
||||||
|
# Aktualisiere auch die internen Parser-Daten
|
||||||
|
for name, point in self.parser.points.items():
|
||||||
|
if point.east is not None:
|
||||||
|
point.east += delta[0]
|
||||||
|
if point.north is not None:
|
||||||
|
point.north += delta[1]
|
||||||
|
if point.elevation is not None:
|
||||||
|
point.elevation += delta[2]
|
||||||
|
|
||||||
|
self.transformation_applied = True
|
||||||
|
return modified_count > 0
|
||||||
|
|
||||||
|
def _update_point_coordinates(self, element: ET.Element, delta: Tuple[float, float, float]) -> bool:
|
||||||
|
"""Aktualisiert die Koordinaten eines einzelnen Punktes im XML"""
|
||||||
|
modified = False
|
||||||
|
|
||||||
|
# Grid-Koordinaten aktualisieren
|
||||||
|
grid = element.find('Grid')
|
||||||
|
if grid is not None:
|
||||||
|
modified |= self._update_grid_element(grid, delta)
|
||||||
|
|
||||||
|
# ComputedGrid-Koordinaten aktualisieren
|
||||||
|
computed_grid = element.find('ComputedGrid')
|
||||||
|
if computed_grid is not None:
|
||||||
|
modified |= self._update_grid_element(computed_grid, delta)
|
||||||
|
|
||||||
|
return modified
|
||||||
|
|
||||||
|
def _update_grid_element(self, grid: ET.Element, delta: Tuple[float, float, float]) -> bool:
|
||||||
|
"""Aktualisiert ein Grid oder ComputedGrid Element"""
|
||||||
|
modified = False
|
||||||
|
|
||||||
|
east = grid.find('East')
|
||||||
|
if east is not None and east.text:
|
||||||
|
try:
|
||||||
|
old_value = float(east.text)
|
||||||
|
new_value = old_value + delta[0]
|
||||||
|
east.text = str(new_value)
|
||||||
|
modified = True
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
north = grid.find('North')
|
||||||
|
if north is not None and north.text:
|
||||||
|
try:
|
||||||
|
old_value = float(north.text)
|
||||||
|
new_value = old_value + delta[1]
|
||||||
|
north.text = str(new_value)
|
||||||
|
modified = True
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
elevation = grid.find('Elevation')
|
||||||
|
if elevation is not None and elevation.text:
|
||||||
|
try:
|
||||||
|
old_value = float(elevation.text)
|
||||||
|
new_value = old_value + delta[2]
|
||||||
|
elevation.text = str(new_value)
|
||||||
|
modified = True
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return modified
|
||||||
|
|
||||||
|
def export_jxl(self, output_path: str) -> bool:
|
||||||
|
"""Exportiert die modifizierte JXL-Datei"""
|
||||||
|
if not self.parser or not self.parser.raw_xml:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# XML mit Deklaration schreiben
|
||||||
|
tree = self.parser.raw_xml
|
||||||
|
tree.write(output_path, encoding='UTF-8', xml_declaration=True)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Exportieren: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_summary_report(self) -> str:
|
||||||
|
"""Erstellt einen zusammenfassenden Bericht der Transformation"""
|
||||||
|
delta = self.calculate_translation()
|
||||||
|
|
||||||
|
report = []
|
||||||
|
report.append("=" * 60)
|
||||||
|
report.append("REFERENZPUNKT-ANPASSUNG - ZUSAMMENFASSUNG")
|
||||||
|
report.append("=" * 60)
|
||||||
|
report.append("")
|
||||||
|
report.append(f"Referenzpunkt: {self.reference_point_name}")
|
||||||
|
report.append("")
|
||||||
|
report.append("Ursprüngliche Koordinaten:")
|
||||||
|
report.append(f" East (X): {self.original_coords[0]:12.4f} m")
|
||||||
|
report.append(f" North (Y): {self.original_coords[1]:12.4f} m")
|
||||||
|
report.append(f" Elevation (Z):{self.original_coords[2]:12.4f} m")
|
||||||
|
report.append("")
|
||||||
|
report.append("Neue Koordinaten:")
|
||||||
|
report.append(f" East (X): {self.new_coords[0]:12.4f} m")
|
||||||
|
report.append(f" North (Y): {self.new_coords[1]:12.4f} m")
|
||||||
|
report.append(f" Elevation (Z):{self.new_coords[2]:12.4f} m")
|
||||||
|
report.append("")
|
||||||
|
report.append("Translation (Delta):")
|
||||||
|
report.append(f" ΔX: {delta[0]:+12.4f} m")
|
||||||
|
report.append(f" ΔY: {delta[1]:+12.4f} m")
|
||||||
|
report.append(f" ΔZ: {delta[2]:+12.4f} m")
|
||||||
|
report.append("")
|
||||||
|
report.append("-" * 60)
|
||||||
|
report.append(f"Anzahl betroffener Punkte: {len(self.affected_points)}")
|
||||||
|
report.append("-" * 60)
|
||||||
|
|
||||||
|
if self.affected_points:
|
||||||
|
report.append("")
|
||||||
|
report.append(f"{'Punkt':<10} {'Alt X':>12} {'Alt Y':>12} {'Alt Z':>10} -> {'Neu X':>12} {'Neu Y':>12} {'Neu Z':>10}")
|
||||||
|
report.append("-" * 90)
|
||||||
|
|
||||||
|
for result in self.affected_points[:20]: # Max 20 Punkte anzeigen
|
||||||
|
report.append(
|
||||||
|
f"{result.original_point:<10} "
|
||||||
|
f"{result.original_coords[0]:>12.4f} "
|
||||||
|
f"{result.original_coords[1]:>12.4f} "
|
||||||
|
f"{result.original_coords[2]:>10.4f} -> "
|
||||||
|
f"{result.new_coords[0]:>12.4f} "
|
||||||
|
f"{result.new_coords[1]:>12.4f} "
|
||||||
|
f"{result.new_coords[2]:>10.4f}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(self.affected_points) > 20:
|
||||||
|
report.append(f"... und {len(self.affected_points) - 20} weitere Punkte")
|
||||||
|
|
||||||
|
report.append("")
|
||||||
|
report.append("=" * 60)
|
||||||
|
|
||||||
|
if self.transformation_applied:
|
||||||
|
report.append("✓ Transformation wurde angewendet")
|
||||||
|
else:
|
||||||
|
report.append("⚠ Transformation noch nicht angewendet")
|
||||||
|
|
||||||
|
report.append("=" * 60)
|
||||||
|
|
||||||
|
return "\n".join(report)
|
||||||
|
|
||||||
|
def validate_input(self, east: float, north: float, elevation: float) -> Tuple[bool, str]:
|
||||||
|
"""Validiert die eingegebenen Koordinaten"""
|
||||||
|
warnings = []
|
||||||
|
|
||||||
|
# Prüfe auf extrem große Werte (möglicher Eingabefehler)
|
||||||
|
if abs(east) > 1000000:
|
||||||
|
warnings.append(f"East-Koordinate ({east}) ist sehr groß - bitte überprüfen")
|
||||||
|
if abs(north) > 1000000:
|
||||||
|
warnings.append(f"North-Koordinate ({north}) ist sehr groß - bitte überprüfen")
|
||||||
|
if abs(elevation) > 10000:
|
||||||
|
warnings.append(f"Elevation ({elevation}) ist sehr groß - bitte überprüfen")
|
||||||
|
|
||||||
|
# Berechne Translation
|
||||||
|
delta = (
|
||||||
|
east - self.original_coords[0],
|
||||||
|
north - self.original_coords[1],
|
||||||
|
elevation - self.original_coords[2]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Warnung bei sehr großer Translation
|
||||||
|
total_shift = math.sqrt(delta[0]**2 + delta[1]**2)
|
||||||
|
if total_shift > 100000:
|
||||||
|
warnings.append(f"Horizontale Verschiebung ({total_shift:.1f} m) ist sehr groß")
|
||||||
|
|
||||||
|
if abs(delta[2]) > 1000:
|
||||||
|
warnings.append(f"Höhenverschiebung ({delta[2]:.1f} m) ist sehr groß")
|
||||||
|
|
||||||
|
if warnings:
|
||||||
|
return (False, "\n".join(warnings))
|
||||||
|
return (True, "Koordinaten sind plausibel")
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Trimble Geodesy Tool - Python Dependencies
|
||||||
|
# Installation: pip install -r requirements.txt
|
||||||
|
|
||||||
|
# GUI Framework
|
||||||
|
PyQt5>=5.15.0
|
||||||
|
|
||||||
|
# Numerische Berechnungen
|
||||||
|
numpy>=1.21.0
|
||||||
|
scipy>=1.7.0
|
||||||
|
|
||||||
|
# XML Processing
|
||||||
|
lxml>=4.6.0
|
||||||
|
|
||||||
|
# Optional: für erweiterte Funktionen
|
||||||
|
matplotlib>=3.4.0 # Für Diagramme und Visualisierungen (optional)
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
5001,0.000,0.000,0.000
|
||||||
|
5002,0.000,11.407,-0.035
|
||||||
|
1001,22.788,13.565,-1.952
|
||||||
|
2001,0.014,11.747,-1.361
|
||||||
|
2002,20.214,30.965,-1.663
|
||||||
|
2003,33.782,13.097,-2.616
|
||||||
|
2004,20.490,-15.429,-1.438
|
||||||
|
3001,33.838,13.220,-2.575
|
||||||
|
3002,26.308,30.140,-1.903
|
||||||
|
3003,26.357,-3.293,-2.115
|
||||||
|
3004,0.048,-0.530,-3.021
|
||||||
|
3005,-0.038,6.789,-3.414
|
||||||
|
6001,-18.196,13.913,-2.176
|
||||||
|
6002,-15.677,12.648,-2.286
|
||||||
|
6003,-26.318,12.550,-2.274
|
||||||
|
6004,-24.723,13.845,-1.559
|
||||||
|
3006,1.182,13.131,1.845
|
||||||
|
3007,0.867,16.745,1.841
|
||||||
|
1002,-20.883,13.008,-1.742
|
||||||
|
2005,-13.138,18.366,-1.730
|
||||||
|
2006,-19.762,10.592,-1.668
|
||||||
|
2007,-29.788,15.304,-1.497
|
||||||
|
2008,-20.421,22.326,-1.798
|
||||||
|
3008,-13.174,11.080,-0.280
|
||||||
|
3009,-13.168,10.756,3.783
|
||||||
|
3010,-14.042,4.481,1.836
|
||||||
|
3011,-13.166,13.790,5.689
|
||||||
|
3012,-13.164,11.191,7.558
|
||||||
|
3013,-13.164,10.602,10.922
|
||||||
|
2009,-13.144,12.601,-1.895
|
||||||
|
7002,-7.487,13.193,-3.675
|
||||||
|
7001,-13.134,19.746,-0.424
|
||||||
|
3014,-10.454,12.968,-3.593
|
||||||
|
3015,-5.752,14.319,-3.597
|
||||||
|
3016,-2.168,12.842,-3.598
|
||||||
|
3017,-16.960,9.871,-1.307
|
||||||
|
3018,-13.143,8.257,-1.713
|
||||||
|
3019,-21.129,4.481,-1.106
|
||||||
|
3020,-31.198,9.253,-2.128
|
||||||
|
3021,-29.609,16.923,-2.185
|
||||||
|
3022,-30.145,21.896,-3.368
|
||||||
|
3023,-20.860,18.943,-3.376
|
||||||
|
6005,-25.633,20.878,-2.243
|
||||||
|
1003,23.070,14.027,-1.969
|
||||||
|
3024,1.188,12.854,5.607
|
||||||
|
3025,1.213,3.983,5.615
|
||||||
|
3026,-0.029,17.401,9.376
|
||||||
|
3027,0.002,9.449,13.119
|
||||||
|
3028,-0.915,16.758,13.728
|
||||||
|
3029,1.096,3.374,9.955
|
||||||
|
1004,-22.696,19.765,-1.966
|
||||||
|
6006,-48.153,24.806,-2.092
|
||||||
|
6007,-48.551,23.493,-1.983
|
||||||
|
6008,-42.900,22.586,-2.096
|
||||||
|
1005,-46.031,23.575,-1.951
|
||||||
|
2010,-40.871,25.207,-1.886
|
||||||
|
2011,-42.736,16.007,-1.438
|
||||||
|
2012,-51.007,10.054,-1.947
|
||||||
|
2013,-46.596,26.451,-1.927
|
||||||
|
3030,-41.629,20.671,-2.199
|
||||||
|
3031,-42.358,17.069,-2.207
|
||||||
|
3032,-43.915,9.539,-2.218
|
||||||
|
3033,-39.002,22.932,-3.451
|
||||||
|
1006,22.923,13.878,-1.904
|
||||||
|
3034,-2.259,19.010,16.788
|
||||||
|
3035,-2.039,11.679,17.155
|
||||||
|
3036,-2.401,0.498,16.947
|
||||||
|
1007,-21.634,18.968,-1.815
|
||||||
|
3037,-29.302,18.141,1.265
|
||||||
|
3038,-30.284,13.473,-0.502
|
||||||
|
3039,-30.223,13.843,2.923
|
||||||
|
3040,-30.164,14.114,6.326
|
||||||
|
3041,-30.261,13.648,9.537
|
||||||
|
1008,-46.567,23.450,-1.900
|
||||||
|
3042,-43.596,11.396,4.659
|
||||||
|
3043,-41.019,23.777,4.663
|
||||||
|
1009,-17.132,16.548,-1.900
|
||||||
|
3044,-31.755,22.820,14.965
|
||||||
|
3045,-33.719,12.836,14.653
|
||||||
|
3046,-34.899,6.876,14.549
|
||||||
|
3047,-31.140,9.336,8.091
|
||||||
|
3048,-29.550,17.086,8.072
|
||||||
|
3049,-28.629,21.467,8.076
|
||||||
|
3050,-30.750,11.180,1.169
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue