siistemata la visualizzazione della tabella dei target durante la simulazione
This commit is contained in:
parent
e08466c77f
commit
0c347be5c8
@ -389,7 +389,7 @@
|
|||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"target_id": 0,
|
"target_id": 0,
|
||||||
"active": false,
|
"active": true,
|
||||||
"traceable": true,
|
"traceable": true,
|
||||||
"trajectory": [
|
"trajectory": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -70,6 +70,7 @@ class Target:
|
|||||||
current_azimuth_deg: float = field(init=False, default=0.0)
|
current_azimuth_deg: float = field(init=False, default=0.0)
|
||||||
current_altitude_ft: float = field(init=False, default=0.0)
|
current_altitude_ft: float = field(init=False, default=0.0)
|
||||||
current_velocity_fps: float = field(init=False, default=0.0)
|
current_velocity_fps: float = field(init=False, default=0.0)
|
||||||
|
current_vertical_velocity_fps: float = field(init=False, default=0.0)
|
||||||
current_heading_deg: float = field(init=False, default=0.0)
|
current_heading_deg: float = field(init=False, default=0.0)
|
||||||
current_pitch_deg: float = field(init=False, default=0.0)
|
current_pitch_deg: float = field(init=False, default=0.0)
|
||||||
_pos_x_ft: float = field(init=False, repr=False, default=0.0)
|
_pos_x_ft: float = field(init=False, repr=False, default=0.0)
|
||||||
@ -123,19 +124,22 @@ class Target:
|
|||||||
math.degrees(math.atan2(dz, dist_2d)) if dist_2d > 0.1 else 0.0
|
math.degrees(math.atan2(dz, dist_2d)) if dist_2d > 0.1 else 0.0
|
||||||
)
|
)
|
||||||
self.current_velocity_fps = dist_3d / dt if dt > 0 else 0.0
|
self.current_velocity_fps = dist_3d / dt if dt > 0 else 0.0
|
||||||
|
self.current_vertical_velocity_fps = dz / dt if dt > 0 else 0.0
|
||||||
else:
|
else:
|
||||||
(
|
(
|
||||||
self.current_heading_deg,
|
self.current_heading_deg,
|
||||||
self.current_pitch_deg,
|
self.current_pitch_deg,
|
||||||
self.current_velocity_fps,
|
self.current_velocity_fps,
|
||||||
) = (0.0, 0.0, 0.0)
|
self.current_vertical_velocity_fps,
|
||||||
|
) = (0.0, 0.0, 0.0, 0.0)
|
||||||
else:
|
else:
|
||||||
self._pos_x_ft, self._pos_y_ft, self._pos_z_ft = 0.0, 0.0, 0.0
|
self._pos_x_ft, self._pos_y_ft, self._pos_z_ft = 0.0, 0.0, 0.0
|
||||||
(
|
(
|
||||||
self.current_velocity_fps,
|
self.current_velocity_fps,
|
||||||
|
self.current_vertical_velocity_fps,
|
||||||
self.current_heading_deg,
|
self.current_heading_deg,
|
||||||
self.current_pitch_deg,
|
self.current_pitch_deg,
|
||||||
) = (0.0, 0.0, 0.0)
|
) = (0.0, 0.0, 0.0, 0.0)
|
||||||
self._update_current_polar_coords()
|
self._update_current_polar_coords()
|
||||||
self.active = bool(self.trajectory)
|
self.active = bool(self.trajectory)
|
||||||
|
|
||||||
@ -341,7 +345,8 @@ class Target:
|
|||||||
final_pos[2],
|
final_pos[2],
|
||||||
final_pos[3],
|
final_pos[3],
|
||||||
)
|
)
|
||||||
self.current_velocity_fps = 0.0
|
# Do not reset velocity to 0, keep the last computed value.
|
||||||
|
self.current_vertical_velocity_fps = 0.0
|
||||||
if self._sim_time_s >= self._total_duration_s:
|
if self._sim_time_s >= self._total_duration_s:
|
||||||
self.active = False
|
self.active = False
|
||||||
else:
|
else:
|
||||||
@ -370,6 +375,7 @@ class Target:
|
|||||||
)
|
)
|
||||||
if delta_time_s > 0:
|
if delta_time_s > 0:
|
||||||
self.current_velocity_fps = dist_3d / delta_time_s
|
self.current_velocity_fps = dist_3d / delta_time_s
|
||||||
|
self.current_vertical_velocity_fps = dz / delta_time_s
|
||||||
if dist_2d > 0.1:
|
if dist_2d > 0.1:
|
||||||
# Restore original update_state heading computation using atan2(dx, dy).
|
# Restore original update_state heading computation using atan2(dx, dy).
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -155,10 +155,13 @@ class SimulationEngine(threading.Thread):
|
|||||||
|
|
||||||
if self.simulation_hub:
|
if self.simulation_hub:
|
||||||
for target in active_targets:
|
for target in active_targets:
|
||||||
|
# Include velocities in the state tuple for the hub
|
||||||
state_tuple = (
|
state_tuple = (
|
||||||
getattr(target, "_pos_x_ft", 0.0),
|
getattr(target, "_pos_x_ft", 0.0),
|
||||||
getattr(target, "_pos_y_ft", 0.0),
|
getattr(target, "_pos_y_ft", 0.0),
|
||||||
getattr(target, "_pos_z_ft", 0.0),
|
getattr(target, "_pos_z_ft", 0.0),
|
||||||
|
getattr(target, "current_velocity_fps", 0.0),
|
||||||
|
getattr(target, "current_vertical_velocity_fps", 0.0),
|
||||||
)
|
)
|
||||||
self.simulation_hub.add_simulated_state(
|
self.simulation_hub.add_simulated_state(
|
||||||
target.target_id, tick_timestamp, state_tuple
|
target.target_id, tick_timestamp, state_tuple
|
||||||
|
|||||||
@ -588,9 +588,6 @@ class MainView(tk.Tk):
|
|||||||
self.ppi_widget.update_simulated_targets(display_data.get("simulated", []))
|
self.ppi_widget.update_simulated_targets(display_data.get("simulated", []))
|
||||||
self.ppi_widget.update_real_targets(display_data.get("real", []))
|
self.ppi_widget.update_real_targets(display_data.get("real", []))
|
||||||
|
|
||||||
# Update the new active targets table
|
|
||||||
self.simulation_controls.update_targets_table(display_data.get("simulated", []))
|
|
||||||
|
|
||||||
if self.simulation_hub:
|
if self.simulation_hub:
|
||||||
# Update antenna sweep line
|
# Update antenna sweep line
|
||||||
az_deg, az_ts = self.simulation_hub.get_antenna_azimuth()
|
az_deg, az_ts = self.simulation_hub.get_antenna_azimuth()
|
||||||
@ -605,8 +602,11 @@ class MainView(tk.Tk):
|
|||||||
self.simulation_controls.update_ownship_display(ownship_state)
|
self.simulation_controls.update_ownship_display(ownship_state)
|
||||||
else:
|
else:
|
||||||
# Ensure display is cleared if no ownship data is present
|
# Ensure display is cleared if no ownship data is present
|
||||||
|
ownship_state = {}
|
||||||
self.simulation_controls.update_ownship_display({})
|
self.simulation_controls.update_ownship_display({})
|
||||||
|
|
||||||
|
# Update the new active targets table, providing ownship state for context
|
||||||
|
self.simulation_controls.update_targets_table(display_data.get("simulated", []), ownship_state)
|
||||||
|
|
||||||
if sim_is_running_now:
|
if sim_is_running_now:
|
||||||
if self.simulation_engine and self.simulation_engine.scenario:
|
if self.simulation_engine and self.simulation_engine.scenario:
|
||||||
|
|||||||
@ -8,10 +8,10 @@ from target_simulator.analysis.simulation_state_hub import SimulationStateHub
|
|||||||
|
|
||||||
def build_display_data(
|
def build_display_data(
|
||||||
simulation_hub: SimulationStateHub,
|
simulation_hub: SimulationStateHub,
|
||||||
scenario: Optional['Scenario'] = None,
|
scenario: Optional["Scenario"] = None,
|
||||||
engine: Optional['SimulationEngine'] = None,
|
engine: Optional["SimulationEngine"] = None,
|
||||||
ppi_widget: Optional['PPIDisplay'] = None,
|
ppi_widget: Optional["PPIDisplay"] = None,
|
||||||
logger: Optional['Logger'] = None,
|
logger: Optional["Logger"] = None,
|
||||||
) -> Dict[str, List[Target]]:
|
) -> Dict[str, List[Target]]:
|
||||||
"""
|
"""
|
||||||
Builds PPI display data from the simulation hub, converting absolute
|
Builds PPI display data from the simulation hub, converting absolute
|
||||||
@ -41,12 +41,20 @@ def build_display_data(
|
|||||||
if history.get("simulated"):
|
if history.get("simulated"):
|
||||||
# Simulated data is generated relative to (0,0), so no transformation is needed.
|
# Simulated data is generated relative to (0,0), so no transformation is needed.
|
||||||
last_sim_state = history["simulated"][-1]
|
last_sim_state = history["simulated"][-1]
|
||||||
|
|
||||||
|
# Unpack the state tuple, now expecting velocities
|
||||||
|
if len(last_sim_state) >= 6:
|
||||||
|
_ts, x_ft, y_ft, z_ft, vel_fps, vert_vel_fps = last_sim_state[:6]
|
||||||
|
else: # Fallback for old data format
|
||||||
_ts, x_ft, y_ft, z_ft = last_sim_state
|
_ts, x_ft, y_ft, z_ft = last_sim_state
|
||||||
|
vel_fps, vert_vel_fps = 0.0, 0.0
|
||||||
|
|
||||||
sim_target = Target(target_id=tid, trajectory=[])
|
sim_target = Target(target_id=tid, trajectory=[])
|
||||||
setattr(sim_target, "_pos_x_ft", x_ft)
|
setattr(sim_target, "_pos_x_ft", x_ft)
|
||||||
setattr(sim_target, "_pos_y_ft", y_ft)
|
setattr(sim_target, "_pos_y_ft", y_ft)
|
||||||
setattr(sim_target, "_pos_z_ft", z_ft)
|
setattr(sim_target, "_pos_z_ft", z_ft)
|
||||||
|
sim_target.current_velocity_fps = vel_fps
|
||||||
|
sim_target.current_vertical_velocity_fps = vert_vel_fps
|
||||||
sim_target._update_current_polar_coords()
|
sim_target._update_current_polar_coords()
|
||||||
|
|
||||||
# Preserve heading information from the engine/scenario if available
|
# Preserve heading information from the engine/scenario if available
|
||||||
@ -77,7 +85,7 @@ def build_display_data(
|
|||||||
# --- Process Real Data (transforming from absolute to relative) ---
|
# --- Process Real Data (transforming from absolute to relative) ---
|
||||||
if history.get("real"):
|
if history.get("real"):
|
||||||
last_real_state = history["real"][-1]
|
last_real_state = history["real"][-1]
|
||||||
_ts, abs_x_ft, abs_y_ft, abs_z_ft = last_real_state
|
_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
|
rel_x_ft, rel_y_ft = abs_x_ft, abs_y_ft
|
||||||
if ownship_pos_xy_ft:
|
if ownship_pos_xy_ft:
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List
|
||||||
|
import math
|
||||||
|
|
||||||
from target_simulator.core.models import FPS_TO_KNOTS, Target
|
from target_simulator.core.models import FPS_TO_KNOTS, Target
|
||||||
|
|
||||||
@ -199,18 +200,18 @@ class SimulationControls(ttk.LabelFrame):
|
|||||||
self.targets_tree.heading("id", text="ID")
|
self.targets_tree.heading("id", text="ID")
|
||||||
self.targets_tree.heading("lat", text="Latitude")
|
self.targets_tree.heading("lat", text="Latitude")
|
||||||
self.targets_tree.heading("lon", text="Longitude")
|
self.targets_tree.heading("lon", text="Longitude")
|
||||||
self.targets_tree.heading("alt", text="Altitude")
|
self.targets_tree.heading("alt", text="Altitude (ft)")
|
||||||
self.targets_tree.heading("hdg", text="Heading")
|
self.targets_tree.heading("hdg", text="Heading (°)")
|
||||||
self.targets_tree.heading("gnd_speed", text="Ground Speed")
|
self.targets_tree.heading("gnd_speed", text="Speed (kn)")
|
||||||
self.targets_tree.heading("vert_speed", text="Vertical Speed")
|
self.targets_tree.heading("vert_speed", text="Vert. Speed (ft/s)")
|
||||||
|
|
||||||
# Define column properties
|
# Define column properties
|
||||||
self.targets_tree.column("id", width=40, anchor=tk.CENTER, stretch=False)
|
self.targets_tree.column("id", width=30, anchor=tk.CENTER, stretch=False)
|
||||||
self.targets_tree.column("lat", width=100, anchor=tk.W)
|
self.targets_tree.column("lat", width=100, anchor=tk.W)
|
||||||
self.targets_tree.column("lon", width=100, anchor=tk.W)
|
self.targets_tree.column("lon", width=100, anchor=tk.W)
|
||||||
self.targets_tree.column("alt", width=100, anchor=tk.E)
|
self.targets_tree.column("alt", width=80, anchor=tk.E)
|
||||||
self.targets_tree.column("hdg", width=80, anchor=tk.E)
|
self.targets_tree.column("hdg", width=80, anchor=tk.E)
|
||||||
self.targets_tree.column("gnd_speed", width=100, anchor=tk.E)
|
self.targets_tree.column("gnd_speed", width=80, anchor=tk.E)
|
||||||
self.targets_tree.column("vert_speed", width=100, anchor=tk.E)
|
self.targets_tree.column("vert_speed", width=100, anchor=tk.E)
|
||||||
|
|
||||||
# Layout Treeview and Scrollbar
|
# Layout Treeview and Scrollbar
|
||||||
@ -270,37 +271,57 @@ class SimulationControls(ttk.LabelFrame):
|
|||||||
self.gnd_speed_var.set(f"{gnd_speed_kn:.1f} kn")
|
self.gnd_speed_var.set(f"{gnd_speed_kn:.1f} kn")
|
||||||
|
|
||||||
# Vertical Speed
|
# Vertical Speed
|
||||||
vz_fps = state.get("velocity_z_fps", 0.0) # Assuming 'vz' is part of state
|
vz_fps = state.get("velocity_z_fps", 0.0)
|
||||||
self.vert_speed_var.set(f"{vz_fps:+.1f} ft/s")
|
self.vert_speed_var.set(f"{vz_fps:+.1f} ft/s")
|
||||||
|
|
||||||
def update_targets_table(self, targets: List[Target]):
|
def update_targets_table(self, targets: List[Target], ownship_state: Dict[str, Any]):
|
||||||
"""Clears and repopulates the active targets table."""
|
"""Clears and repopulates the active targets table with geographic data."""
|
||||||
# Clear existing items
|
|
||||||
for item in self.targets_tree.get_children():
|
for item in self.targets_tree.get_children():
|
||||||
self.targets_tree.delete(item)
|
self.targets_tree.delete(item)
|
||||||
|
|
||||||
# Insert new items
|
# Get ownship data needed for conversion
|
||||||
|
own_lat = ownship_state.get("latitude")
|
||||||
|
own_lon = ownship_state.get("longitude")
|
||||||
|
own_pos_xy_ft = ownship_state.get("position_xy_ft")
|
||||||
|
|
||||||
for target in sorted(targets, key=lambda t: t.target_id):
|
for target in sorted(targets, key=lambda t: t.target_id):
|
||||||
if not target.active:
|
if not target.active:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# This data is not directly in the Target model, so we calculate it
|
lat_str = "N/A"
|
||||||
# based on its relative position for display purposes.
|
lon_str = "N/A"
|
||||||
# NOTE: For "real" targets, lat/lon are not directly known without
|
|
||||||
# ownship data. The current implementation shows polar coords.
|
|
||||||
# This can be expanded later if geo-referenced data becomes available.
|
|
||||||
lat_str = f"{target.current_range_nm:.2f} NM" # Placeholder
|
|
||||||
lon_str = f"{target.current_azimuth_deg:.2f}°" # Placeholder
|
|
||||||
|
|
||||||
alt_str = f"{target.current_altitude_ft:.1f} ft"
|
# Calculate geographic position if possible
|
||||||
hdg_str = f"{target.current_heading_deg:.2f}°"
|
if own_lat is not None and own_lon is not None and own_pos_xy_ft:
|
||||||
|
target_x_ft = getattr(target, "_pos_x_ft", 0.0)
|
||||||
|
target_y_ft = getattr(target, "_pos_y_ft", 0.0)
|
||||||
|
own_x_ft, own_y_ft = own_pos_xy_ft
|
||||||
|
|
||||||
# Assuming ground speed is the 2D velocity for simplicity
|
# Delta from ownship's current position in meters
|
||||||
|
delta_east_m = (target_x_ft - own_x_ft) * 0.3048
|
||||||
|
delta_north_m = (target_y_ft - own_y_ft) * 0.3048
|
||||||
|
|
||||||
|
# Equirectangular approximation for lat/lon calculation
|
||||||
|
earth_radius_m = 6378137.0
|
||||||
|
dlat = (delta_north_m / earth_radius_m) * (180.0 / math.pi)
|
||||||
|
dlon = (delta_east_m / (earth_radius_m * math.cos(math.radians(own_lat)))) * (180.0 / math.pi)
|
||||||
|
|
||||||
|
target_lat = own_lat + dlat
|
||||||
|
target_lon = own_lon + dlon
|
||||||
|
|
||||||
|
lat_str = f"{abs(target_lat):.5f}° {'N' if target_lat >= 0 else 'S'}"
|
||||||
|
lon_str = f"{abs(target_lon):.5f}° {'E' if target_lon >= 0 else 'W'}"
|
||||||
|
|
||||||
|
alt_str = f"{target.current_altitude_ft:.1f}"
|
||||||
|
hdg_str = f"{target.current_heading_deg:.2f}"
|
||||||
|
|
||||||
|
# Use the now-correct velocity values from the Target object
|
||||||
gnd_speed_kn = target.current_velocity_fps * FPS_TO_KNOTS
|
gnd_speed_kn = target.current_velocity_fps * FPS_TO_KNOTS
|
||||||
gnd_speed_str = f"{gnd_speed_kn:.1f} kn"
|
gnd_speed_str = f"{gnd_speed_kn:.1f}"
|
||||||
|
|
||||||
|
vert_speed_fps = target.current_vertical_velocity_fps
|
||||||
|
vert_speed_str = f"{vert_speed_fps:+.1f}"
|
||||||
|
|
||||||
# Vertical speed is not directly in the simple Target model
|
|
||||||
vert_speed_str = "N/A"
|
|
||||||
|
|
||||||
values = (
|
values = (
|
||||||
target.target_id,
|
target.target_id,
|
||||||
|
|||||||
4
todo.md
4
todo.md
@ -15,8 +15,12 @@
|
|||||||
- [ ] aprire l'analisi direttamente cliccando sulla riga della tabella
|
- [ ] aprire l'analisi direttamente cliccando sulla riga della tabella
|
||||||
- [ ] creare una procedura di allineamento tra server e client usando il comando di ping da implementare anche sul server
|
- [ ] creare una procedura di allineamento tra server e client usando il comando di ping da implementare anche sul server
|
||||||
|
|
||||||
|
- [ ] funzione di sincronizzazione: è stato aggiunto al server la possibilità di gestire dei messaggi che sono di tipo SY (tag) che sono fatti per gestire il sincronismo tra client e server. In questa nuova tipologia di messaggi io invio un mio timetag che poi il server mi restituirà subito appena lo riceve, facendo così sappiamo in quanto tempo il messaggio che spedisco è arrivato al server, viene letto, e viene risposto il mio numero con anche il timetag del server. Facendo così misurando i delta posso scroprire esattamente il tempo che intercorre tra inviare un messaggio al server e ricevere una risposta. Per come è fatto il server il tempo di applicazione dei nuovi valori per i target sarà al massimo di 1 batch, che può essere variabile, ma a quel punto lo potremmo calibrare in altro modo. Con l'analisi sui sync possiamo sapere come allineare gli orologi.
|
||||||
|
|
||||||
# FIXME List
|
# FIXME List
|
||||||
|
|
||||||
- [ ] sistemare la visualizzazione nella tabe simulator, per poter vedere quale scenario è stato selezionato
|
- [ ] sistemare la visualizzazione nella tabe simulator, per poter vedere quale scenario è stato selezionato
|
||||||
- [ ] sistemare l'animazione della antenna che adesso non si muove più
|
- [ ] sistemare l'animazione della antenna che adesso non si muove più
|
||||||
- [ ] rivedere la visualizzazione della combobox per scegliere lo scenario da usare.
|
- [ ] rivedere la visualizzazione della combobox per scegliere lo scenario da usare.
|
||||||
|
- [ ] quando è finita la simulazione i target nella tabella si devono fermare all'ultima posizione scambiata.
|
||||||
|
- [ ] quando la traiettoria si ferma deve comparire la x gialla e non deve sparire a fine simulazione
|
||||||
Loading…
Reference in New Issue
Block a user