322 lines
11 KiB
Python
322 lines
11 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 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)
|