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