trimble_geodesy/modules/reference_point_adjuster.py

324 lines
12 KiB
Python

"""
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")