trimble_geodesy/modules/transformation.py

358 lines
12 KiB
Python

"""
Koordinatentransformations-Modul
Unterstützt Rotation, Translation in XY und Z
KEINE Maßstabsänderung (wie vom Benutzer gefordert)
"""
import math
import numpy as np
from typing import List, Tuple, Optional, Dict
from dataclasses import dataclass
from .cor_generator import CORPoint
@dataclass
class TransformationParameters:
"""Parameter für die Koordinatentransformation"""
# Translation
dx: float = 0.0 # Verschiebung in X (East)
dy: float = 0.0 # Verschiebung in Y (North)
dz: float = 0.0 # Verschiebung in Z (Höhe)
# Rotation um Z-Achse (in Gon)
rotation_gon: float = 0.0
# Drehpunkt (für Rotation)
pivot_x: float = 0.0
pivot_y: float = 0.0
def rotation_rad(self) -> float:
"""Gibt Rotation in Radiant zurück"""
return self.rotation_gon * math.pi / 200.0
class CoordinateTransformer:
"""Transformiert Koordinaten: Rotation und Translation"""
def __init__(self):
self.params = TransformationParameters()
self.original_points: List[CORPoint] = []
self.transformed_points: List[CORPoint] = []
def set_points(self, points: List[CORPoint]):
"""Setzt die zu transformierenden Punkte"""
self.original_points = points.copy()
self.transformed_points = []
def set_manual_parameters(self, dx: float, dy: float, dz: float,
rotation_gon: float, pivot_x: float = 0.0,
pivot_y: float = 0.0):
"""Setzt Transformationsparameter manuell"""
self.params.dx = dx
self.params.dy = dy
self.params.dz = dz
self.params.rotation_gon = rotation_gon
self.params.pivot_x = pivot_x
self.params.pivot_y = pivot_y
def compute_from_two_points(self,
point1_name: str,
point2_name: str,
z_reference_name: Optional[str] = None) -> bool:
"""
Berechnet Transformation aus zwei Punkten:
- point1 wird zum Ursprung (0,0)
- point2 definiert die Y-Richtung (Nordrichtung)
- z_reference (optional) definiert Z=0
"""
# Finde Punkte
point1 = None
point2 = None
z_ref = None
for p in self.original_points:
if p.name == point1_name:
point1 = p
elif p.name == point2_name:
point2 = p
if z_reference_name and p.name == z_reference_name:
z_ref = p
if point1 is None or point2 is None:
return False
# Drehpunkt ist point1
self.params.pivot_x = point1.x
self.params.pivot_y = point1.y
# Translation: point1 zum Ursprung
self.params.dx = -point1.x
self.params.dy = -point1.y
# Rotation: point2 soll auf der positiven Y-Achse liegen
# Berechne Richtung von point1 zu point2
dx_12 = point2.x - point1.x
dy_12 = point2.y - point1.y
# Aktueller Winkel zur Y-Achse (Nordrichtung)
current_angle_rad = math.atan2(dx_12, dy_12) # atan2(x,y) für Azimut
# Rotation um diesen Winkel (negativ, um auf Y-Achse zu drehen)
self.params.rotation_gon = -current_angle_rad * 200.0 / math.pi
# Z-Verschiebung
if z_ref:
self.params.dz = -z_ref.z
else:
self.params.dz = -point1.z
return True
def compute_translation_only(self,
xy_origin_name: str,
z_reference_name: Optional[str] = None) -> bool:
"""
Berechnet nur Translation (KEINE Rotation):
- xy_origin_name: Punkt wird zum Ursprung (0,0)
- z_reference_name: Punkt definiert Z=0
"""
# Finde Punkte
xy_origin = None
z_ref = None
for p in self.original_points:
if p.name == xy_origin_name:
xy_origin = p
if z_reference_name and p.name == z_reference_name:
z_ref = p
if xy_origin is None:
return False
# Nur Translation, keine Rotation
self.params.dx = -xy_origin.x
self.params.dy = -xy_origin.y
self.params.rotation_gon = 0.0
self.params.pivot_x = 0.0
self.params.pivot_y = 0.0
# Z-Verschiebung
if z_ref:
self.params.dz = -z_ref.z
else:
self.params.dz = -xy_origin.z
return True
def transform(self) -> List[CORPoint]:
"""Führt die Transformation durch"""
self.transformed_points = []
rot_rad = self.params.rotation_rad()
cos_r = math.cos(rot_rad)
sin_r = math.sin(rot_rad)
for p in self.original_points:
# 1. Zum Drehpunkt verschieben
x_shifted = p.x - self.params.pivot_x
y_shifted = p.y - self.params.pivot_y
# 2. Rotation anwenden
x_rotated = x_shifted * cos_r - y_shifted * sin_r
y_rotated = x_shifted * sin_r + y_shifted * cos_r
# 3. Translation anwenden
x_final = x_rotated + self.params.pivot_x + self.params.dx
y_final = y_rotated + self.params.pivot_y + self.params.dy
z_final = p.z + self.params.dz
self.transformed_points.append(CORPoint(
name=p.name,
x=x_final,
y=y_final,
z=z_final
))
return self.transformed_points
def transform_single_point(self, x: float, y: float, z: float) -> Tuple[float, float, float]:
"""Transformiert einen einzelnen Punkt"""
rot_rad = self.params.rotation_rad()
cos_r = math.cos(rot_rad)
sin_r = math.sin(rot_rad)
# Zum Drehpunkt verschieben
x_shifted = x - self.params.pivot_x
y_shifted = y - self.params.pivot_y
# Rotation
x_rotated = x_shifted * cos_r - y_shifted * sin_r
y_rotated = x_shifted * sin_r + y_shifted * cos_r
# Translation
x_final = x_rotated + self.params.pivot_x + self.params.dx
y_final = y_rotated + self.params.pivot_y + self.params.dy
z_final = z + self.params.dz
return x_final, y_final, z_final
def inverse_transform(self, x: float, y: float, z: float) -> Tuple[float, float, float]:
"""Inverse Transformation"""
# Inverse Translation
x_inv = x - self.params.dx - self.params.pivot_x
y_inv = y - self.params.dy - self.params.pivot_y
z_inv = z - self.params.dz
# Inverse Rotation
rot_rad = -self.params.rotation_rad()
cos_r = math.cos(rot_rad)
sin_r = math.sin(rot_rad)
x_rotated = x_inv * cos_r - y_inv * sin_r
y_rotated = x_inv * sin_r + y_inv * cos_r
# Zurück vom Drehpunkt
x_final = x_rotated + self.params.pivot_x
y_final = y_rotated + self.params.pivot_y
return x_final, y_final, z_inv
def get_transformation_matrix(self) -> np.ndarray:
"""Gibt die 4x4 Transformationsmatrix zurück"""
rot_rad = self.params.rotation_rad()
cos_r = math.cos(rot_rad)
sin_r = math.sin(rot_rad)
# Translation zum Drehpunkt
T1 = np.array([
[1, 0, 0, -self.params.pivot_x],
[0, 1, 0, -self.params.pivot_y],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
# Rotation
R = np.array([
[cos_r, -sin_r, 0, 0],
[sin_r, cos_r, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
# Translation zurück und zusätzliche Verschiebung
T2 = np.array([
[1, 0, 0, self.params.pivot_x + self.params.dx],
[0, 1, 0, self.params.pivot_y + self.params.dy],
[0, 0, 1, self.params.dz],
[0, 0, 0, 1]
])
return T2 @ R @ T1
def get_parameters_report(self) -> str:
"""Gibt einen Bericht über die Transformationsparameter zurück"""
report = []
report.append("=" * 50)
report.append("KOORDINATENTRANSFORMATION - PARAMETER")
report.append("=" * 50)
report.append("")
report.append("Translation:")
report.append(f" dX (East): {self.params.dx:+.4f} m")
report.append(f" dY (North): {self.params.dy:+.4f} m")
report.append(f" dZ (Höhe): {self.params.dz:+.4f} m")
report.append("")
report.append("Rotation:")
report.append(f" Winkel: {self.params.rotation_gon:+.6f} gon")
report.append(f" Winkel: {self.params.rotation_gon * 0.9:+.6f}°")
report.append(f" Winkel: {self.params.rotation_rad():+.8f} rad")
report.append("")
report.append("Drehpunkt:")
report.append(f" X: {self.params.pivot_x:.4f} m")
report.append(f" Y: {self.params.pivot_y:.4f} m")
report.append("")
report.append("Hinweis: Keine Maßstabsänderung (Maßstab = 1.0)")
report.append("=" * 50)
return "\n".join(report)
def get_comparison_table(self) -> str:
"""Erstellt eine Vergleichstabelle Original vs. Transformiert"""
if not self.original_points or not self.transformed_points:
return "Keine Daten verfügbar."
lines = []
lines.append("=" * 90)
lines.append("KOORDINATENVERGLEICH: ORIGINAL → TRANSFORMIERT")
lines.append("=" * 90)
lines.append(f"{'Punkt':<10} {'X_orig':>12} {'Y_orig':>12} {'Z_orig':>10} | "
f"{'X_trans':>12} {'Y_trans':>12} {'Z_trans':>10}")
lines.append("-" * 90)
for orig, trans in zip(self.original_points, self.transformed_points):
lines.append(f"{orig.name:<10} {orig.x:>12.4f} {orig.y:>12.4f} {orig.z:>10.4f} | "
f"{trans.x:>12.4f} {trans.y:>12.4f} {trans.z:>10.4f}")
lines.append("=" * 90)
return "\n".join(lines)
class LocalSystemTransformer:
"""
Spezielle Transformation für lokale Systeme
Transformiert in ein System mit definiertem Ursprung und Ausrichtung
"""
def __init__(self):
self.origin_point: Optional[str] = None
self.direction_point: Optional[str] = None
self.z_reference_point: Optional[str] = None
self.transformer = CoordinateTransformer()
def setup_local_system(self, points: List[CORPoint],
origin_name: str,
direction_name: str,
z_ref_name: Optional[str] = None) -> bool:
"""
Richtet ein lokales Koordinatensystem ein:
- origin_name: Punkt bei (0,0)
- direction_name: Punkt definiert Y-Richtung (liegt auf positiver Y-Achse)
- z_ref_name: Punkt definiert Z=0
"""
self.origin_point = origin_name
self.direction_point = direction_name
self.z_reference_point = z_ref_name
self.transformer.set_points(points)
success = self.transformer.compute_from_two_points(
origin_name,
direction_name,
z_ref_name
)
if success:
self.transformer.transform()
return success
def get_transformed_points(self) -> List[CORPoint]:
"""Gibt die transformierten Punkte zurück"""
return self.transformer.transformed_points
def get_report(self) -> str:
"""Gibt einen vollständigen Bericht zurück"""
report = []
report.append("=" * 60)
report.append("LOKALES KOORDINATENSYSTEM")
report.append("=" * 60)
report.append(f"Ursprung (0,0): {self.origin_point}")
report.append(f"Y-Richtung: {self.direction_point}")
if self.z_reference_point:
report.append(f"Z-Referenz (0): {self.z_reference_point}")
report.append("")
report.append(self.transformer.get_parameters_report())
report.append("")
report.append(self.transformer.get_comparison_table())
return "\n".join(report)