178 lines
7.2 KiB
Python
178 lines
7.2 KiB
Python
"""PPI adapter utilities.
|
|
|
|
This module converts absolute states stored in the SimulationStateHub into
|
|
lightweight Target objects positioned relative to ownship and suitable for
|
|
consumption by the :class:`target_simulator.gui.ppi_display.PPIDisplay`.
|
|
|
|
Public API:
|
|
- build_display_data(simulation_hub, ...): returns a dict with keys
|
|
``'simulated'`` and ``'real'`` containing lists of targets for display.
|
|
"""
|
|
|
|
from typing import Dict, List, Optional, TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
# Avoid runtime imports; enable type checking for forward references
|
|
from target_simulator.core.simulation_engine import SimulationEngine
|
|
from target_simulator.gui.ppi_display import PPIDisplay
|
|
from target_simulator.core.models import Scenario
|
|
from logging import Logger
|
|
import math
|
|
from target_simulator.core.models import Target
|
|
from target_simulator.analysis.simulation_state_hub import SimulationStateHub
|
|
|
|
|
|
def build_display_data(
|
|
simulation_hub: SimulationStateHub,
|
|
scenario: Optional["Scenario"] = None,
|
|
engine: Optional["SimulationEngine"] = None,
|
|
ppi_widget: Optional["PPIDisplay"] = None,
|
|
logger: Optional["Logger"] = None,
|
|
) -> Dict[str, List[Target]]:
|
|
"""
|
|
Builds PPI display data from the simulation hub, converting absolute
|
|
'real' coordinates to relative coordinates based on the ownship's position.
|
|
|
|
Returns a dict with keys 'simulated' and 'real' containing lightweight
|
|
Target objects suitable for passing to PPIDisplay.
|
|
"""
|
|
simulated_targets_for_ppi: List[Target] = []
|
|
real_targets_for_ppi: List[Target] = []
|
|
|
|
if not simulation_hub:
|
|
return {"simulated": [], "real": []}
|
|
|
|
# Get ownship state for coordinate transformation
|
|
ownship_state = simulation_hub.get_ownship_state()
|
|
ownship_pos_xy_ft = ownship_state.get("position_xy_ft")
|
|
|
|
# Get the fixed simulation origin state (ownship state at T=0)
|
|
simulation_origin = simulation_hub.get_simulation_origin()
|
|
|
|
target_ids = simulation_hub.get_all_target_ids()
|
|
|
|
for tid in target_ids:
|
|
history = simulation_hub.get_target_history(tid)
|
|
if not history:
|
|
continue
|
|
|
|
# --- Process Simulated Data (transforming from simulation frame to world frame) ---
|
|
if history.get("simulated"):
|
|
last_sim_state = history["simulated"][-1]
|
|
|
|
if len(last_sim_state) >= 6:
|
|
_ts, x_sim_ft, y_sim_ft, z_sim_ft, vel_fps, vert_vel_fps = (
|
|
last_sim_state[:6]
|
|
)
|
|
else:
|
|
_ts, x_sim_ft, y_sim_ft, z_sim_ft = last_sim_state
|
|
vel_fps, vert_vel_fps = 0.0, 0.0
|
|
|
|
# Default to identity transformation if origin is not set
|
|
x_origin_ft, y_origin_ft = 0.0, 0.0
|
|
heading_origin_rad = 0.0
|
|
|
|
if simulation_origin:
|
|
origin_pos = simulation_origin.get("position_xy_ft", (0.0, 0.0))
|
|
x_origin_ft, y_origin_ft = origin_pos
|
|
heading_origin_rad = math.radians(
|
|
simulation_origin.get("heading_deg", 0.0)
|
|
)
|
|
|
|
# 1. Rotate the simulated position by the initial heading of the ownship.
|
|
# This aligns the simulation frame with the world frame (North-up).
|
|
cos_h = math.cos(heading_origin_rad)
|
|
sin_h = math.sin(heading_origin_rad)
|
|
x_rotated = x_sim_ft * cos_h - y_sim_ft * sin_h
|
|
y_rotated = x_sim_ft * sin_h + y_sim_ft * cos_h
|
|
|
|
# 2. Translate by the initial position of the ownship.
|
|
# This gives the absolute world position of the simulated target.
|
|
x_abs_ft = x_rotated + x_origin_ft
|
|
y_abs_ft = y_rotated + y_origin_ft
|
|
|
|
# 3. Convert absolute world coordinates to coordinates relative to current ownship.
|
|
rel_x_ft, rel_y_ft = x_abs_ft, y_abs_ft
|
|
if ownship_pos_xy_ft:
|
|
rel_x_ft = x_abs_ft - ownship_pos_xy_ft[0]
|
|
rel_y_ft = y_abs_ft - ownship_pos_xy_ft[1]
|
|
|
|
sim_target = Target(target_id=tid, trajectory=[])
|
|
setattr(sim_target, "_pos_x_ft", rel_x_ft)
|
|
setattr(sim_target, "_pos_y_ft", rel_y_ft)
|
|
setattr(sim_target, "_pos_z_ft", z_sim_ft)
|
|
sim_target.current_velocity_fps = vel_fps
|
|
sim_target.current_vertical_velocity_fps = vert_vel_fps
|
|
sim_target._update_current_polar_coords()
|
|
|
|
try:
|
|
heading = None
|
|
if engine and getattr(engine, "scenario", None):
|
|
t = engine.scenario.get_target(tid)
|
|
if t:
|
|
# The target's heading is also in the simulation frame.
|
|
# It must be rotated by the origin heading to be in the world frame.
|
|
sim_heading_deg = getattr(t, "current_heading_deg", 0.0)
|
|
world_heading_deg = (
|
|
sim_heading_deg + math.degrees(heading_origin_rad)
|
|
) % 360
|
|
heading = world_heading_deg
|
|
|
|
if heading is None and scenario:
|
|
t2 = scenario.get_target(tid)
|
|
if t2:
|
|
sim_heading_deg = getattr(t2, "current_heading_deg", 0.0)
|
|
world_heading_deg = (
|
|
sim_heading_deg + math.degrees(heading_origin_rad)
|
|
) % 360
|
|
heading = world_heading_deg
|
|
|
|
if heading is not None:
|
|
sim_target.current_heading_deg = float(heading)
|
|
|
|
except Exception:
|
|
pass
|
|
|
|
sim_target.active = True
|
|
if engine and getattr(engine, "scenario", None):
|
|
t_engine = engine.scenario.get_target(tid)
|
|
if t_engine is not None:
|
|
sim_target.active = bool(getattr(t_engine, "active", True))
|
|
|
|
simulated_targets_for_ppi.append(sim_target)
|
|
|
|
# --- Process Real Data (transforming from absolute to relative) ---
|
|
if history.get("real"):
|
|
last_real_state = history["real"][-1]
|
|
_ts, abs_x_ft, abs_y_ft, abs_z_ft = last_real_state[:4]
|
|
|
|
rel_x_ft, rel_y_ft = abs_x_ft, abs_y_ft
|
|
if ownship_pos_xy_ft:
|
|
rel_x_ft = abs_x_ft - ownship_pos_xy_ft[0]
|
|
rel_y_ft = abs_y_ft - ownship_pos_xy_ft[1]
|
|
|
|
ownship_alt_ft = ownship_state.get("altitude_ft", 0.0)
|
|
rel_z_ft = abs_z_ft - ownship_alt_ft
|
|
|
|
real_target = Target(target_id=tid, trajectory=[])
|
|
setattr(real_target, "_pos_x_ft", rel_x_ft)
|
|
setattr(real_target, "_pos_y_ft", rel_y_ft)
|
|
setattr(real_target, "_pos_z_ft", rel_z_ft)
|
|
real_target._update_current_polar_coords()
|
|
|
|
hdg = simulation_hub.get_real_heading(tid)
|
|
if hdg is not None:
|
|
real_target.current_heading_deg = float(hdg) % 360
|
|
|
|
real_target.active = True
|
|
real_targets_for_ppi.append(real_target)
|
|
|
|
if logger:
|
|
logger.debug(
|
|
"PPI Adapter: Built display data (simulated=%d, real=%d)",
|
|
len(simulated_targets_for_ppi),
|
|
len(real_targets_for_ppi),
|
|
)
|
|
|
|
return {"simulated": simulated_targets_for_ppi, "real": real_targets_for_ppi}
|